[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# How to use Jedis Github Issue\n\n* Github issues SHOULD BE USED to report bugs and for DETAILED feature requests. Everything else belongs in the [Jedis Google Group](https://groups.google.com/g/jedis_redis) or [Jedis Github Discussions](https://github.com/redis/jedis/discussions).\n\nPlease post general questions to Google Groups or Github discussions. These can be closed without response when posted to Github issues.\n\n# How to contribute by Pull Request\n\n1. Fork Jedis repo on github ([how to fork a repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo))\n2. Create a topic branch (`git checkout -b my_branch`)\n3. Push to your remote branch (`git push origin my_branch`)\n4. Create a pull request on github ([how to create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request))\n\nCreate a branch with meaningful name, and do not modify the master branch directly.\n\nPlease add unit tests to validate your changes work, then ensure your changes pass all unit tests.\n\n# Jedis Test Environment\n\nJedis uses a Docker-based test environment as the primary method for running tests. A simplified local environment is also available for basic testing.\n\nJedis integration tests use many Redis instances, so we use a `Makefile` to prepare the environment.\n\n## Quick Start (Docker - Recommended)\n\nStart tests with `make test`. This will:\n1. Start the Docker-based test environment\n2. Run all tests\n3. Stop and clean up the environment\n\nSet up test environments with `make start`, tear down those environments with `make stop`.\n\n# Jedis Test Environment Using Docker\n\nThis guide explains how to bootstrap and manage a test environment for Jedis using Docker Compose.\n\n## Workflow Steps\n1. **Start the test environment** by running `make start` (examples below).\n2. **Run tests** through your IDE, Maven, or other testing tools as needed.\n3. **Stop the test environment** by running `make stop`.\n   - This will stop and tear down the Docker containers running the Redis service.\n\n# Start the Test Environment Using Docker\n\nYou can bootstrap the test environment for supported versions of Redis using the provided `make` targets.\n\n## Option 1: Using `make` Targets\nTo bring up the test environment for a specific Redis version use the following command:\n```bash\nmake start version=8.0  # Replace with desired version\n```\nTo stop test environment:\n```bash\nmake stop\n```\nTo run tests using the Docker environment:\n```bash\nmake test\n```\n\n## Option 2: Using docker compose commands directly\nDocker compose file can be found in `src/test/resources/env` folder.\n- **Redis 8.4 (or other versions without custom env file)**\n```bash\nrm -rf /tmp/redis-env-work\nexport REDIS_VERSION=8.4\ndocker compose --env-file .env -f src/test/resources/env/docker-compose.yml up\n```\n- **Redis 7.4, 7.2, 6.2 (versions with custom env files)**\n```bash\nrm -rf /tmp/redis-env-work\nexport REDIS_VERSION=6.2\ndocker compose --env-file .env --env-file .env.v6.2 -f src/test/resources/env/docker-compose.yml up\n```\n\n# Local Test Environment (Simplified)\n\nFor basic testing with a minimal local Redis setup (requires Redis to be installed locally):\n\n```bash\nmake start-local   # Start local Redis instances (standalone + Unix socket)\nmake test-local    # Run tests against local environment\nmake stop-local    # Stop local Redis instances\n```\n\n**Note:** The local environment provides only the `standalone-0` endpoint and a Unix socket instance. For full test coverage, use the Docker-based environment.\n\n\n# Some rules of Jedis source code\n\n## Code Convention\n\n* Jedis uses HBase Formatter introduced by [HBASE-5961](https://issues.apache.org/jira/browse/HBASE-5961)\n* You can import code style file (located to hbase-formatter.xml) to Eclipse, IntelliJ\n  * line break by column count seems not working with IntelliJ\n* <strike>You can run ```make format``` anytime to reformat without IDEs</strike>\n* DO NOT format the source codes within `io.redis.examples` test package.\n* A test class name MUST NOT end with `Example`.\n\n## Adding commands\n\n* Jedis uses many interfaces to structure commands\n  * planned to write documentation about it, contribution is more welcome!\n* We need to add commands to all interfaces which have responsibility to expose\n  * ex) We need to add ping() command to BasicCommands, and provide implementation to all of classes which implemented BasicCommands\n\n## type <-> byte array conversion\n\n* string <-> byte array : use SafeEncoder.encode()\n  * Caution: use String.toBytes() directly will break GBK support!\n* boolean, int, long, double -> byte array : use Protocol.toByteArray()\n\nThanks!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE",
    "content": "<!--\nGithub issues should be used to report bugs and for detailed feature requests. \nEverything else belongs in the [Jedis Google Group](https://groups.google.com/g/jedis_redis) \nor [Jedis Github Discussions](https://github.com/redis/jedis/discussions).\n\nPlease post general questions to Google Groups or Github discussions. \nThese can be closed without response when posted to Github issues.\n-->\n\n### Expected behavior\n\nWrite here what you're expecting ...\n\n### Actual behavior\n\nWrite here what happens instead ...\n\n### Steps to reproduce:\n\nPlease create a reproducible case of your problem. Make sure\nthat case repeats consistently and it's not random\n1.\n2.\n3.\n\n### Redis / Jedis Configuration\n\n#### Jedis version:\n\n#### Redis version:\n\n#### Java version:\n\n\n\n\n"
  },
  {
    "path": ".github/actions/run-tests/action.yml",
    "content": "# Note: this action is used as a part of redis oss release and test automation in\n# redis-developer/redis-oss-release-automation repo\nname: 'Run Jedis Tests'\ndescription: 'Run Jedis tests in a containerized environment'\n\ninputs:\n  redis_version:\n    description: 'Redis version to test against'\n    required: false\n  client_libs_test_image_tag:\n    description: 'Custom client libs test image tag to use instead of redis_version'\n    required: false\n    default: ''\n  client_libs_test_image:\n    description: 'Custom client libs test image name to use'\n    required: false\n    default: ''\n  java_version:\n    description: 'Java version to use'\n    required: false\n    default: '8'\n  java_distribution:\n    description: 'Java distribution to use'\n    required: false\n    default: 'temurin'\n  specific_test:\n    description: 'Run specific test(s) (optional)'\n    required: false\n    default: ''\n  codecov_token:\n    description: 'Codecov token for uploading coverage'\n    required: false\n    default: ''\n  # repository and ref are reqired for correct checkout when using action\n  # externally (e.g.: in redis-developer/redis-oss-release-automation)\n  repository:\n    description: 'Git repository to checkout'\n    required: false\n    default: ''\n  ref:\n    description: 'Git ref to checkout'\n    required: false\n    default: ''\n  redis_env_work_dir:\n    description: 'Redis env work directory'\n    required: false\n    default: ''\n  redis_env_conf_dir:\n    description: 'Redis env conf directory'\n    required: false\n    default: ''\n\nruns:\n  using: 'composite'\n  steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        repository: ${{ inputs.repository }}\n        ref: ${{ inputs.ref }}\n\n    - name: Validate inputs and prepare environment\n      shell: bash\n      id: args\n      env:\n        REDIS_VERSION: ${{ inputs.redis_version }}\n        CLIENT_LIBS_TEST_IMAGE: ${{ inputs.client_libs_test_image }}\n        CLIENT_LIBS_TEST_IMAGE_TAG: ${{ inputs.client_libs_test_image_tag }}\n        REDIS_ENV_WORK_DIR: ${{ inputs.redis_env_work_dir }}\n        REDIS_ENV_CONF_DIR: ${{ inputs.redis_env_conf_dir }}\n        REDIS_VERSION_LABEL: ${{ inputs.client_libs_test_image_tag || inputs.redis_version }}\n      run: |\n        make_args=()\n\n        if [ -n \"$CLIENT_LIBS_TEST_IMAGE\" ]; then\n          make_args+=(CLIENT_LIBS_TEST_IMAGE=\"$CLIENT_LIBS_TEST_IMAGE\")\n        fi\n\n        if [ -n \"$CLIENT_LIBS_TEST_IMAGE_TAG\" ]; then\n          make_args+=(CLIENT_LIBS_TEST_IMAGE_TAG=\"$CLIENT_LIBS_TEST_IMAGE_TAG\")\n        elif [ -n \"$REDIS_VERSION\" ]; then\n          make_args+=(version=\"$REDIS_VERSION\")\n        else\n          echo \"Error: redis_version or client_libs_test_image_tag input is required\"\n          exit 1\n        fi\n\n        echo \"make_args=${make_args[*]}\" | tee -a ${GITHUB_OUTPUT}\n        # either custom docker tag name or actual redis version\n        echo \"redis_version_label=$REDIS_VERSION_LABEL\" | tee -a ${GITHUB_OUTPUT}\n\n        if [ -z \"$REDIS_ENV_CONF_DIR\" ]; then\n          REDIS_ENV_CONF_DIR=$(readlink -f \"${{ github.action_path }}/../../../src/test/resources/env\")\n        fi\n        echo \"redis_env_conf_dir=$REDIS_ENV_CONF_DIR\" | tee -a ${GITHUB_OUTPUT}\n\n        if [ -n \"$REDIS_ENV_WORK_DIR\" ]; then\n          echo \"redis_env_work_dir=$REDIS_ENV_WORK_DIR\" | tee -a ${GITHUB_OUTPUT}\n        else\n          REDIS_ENV_WORK_DIR=$(mktemp -du)\n          echo \"redis_env_work_dir=$REDIS_ENV_WORK_DIR\" | tee -a ${GITHUB_OUTPUT}\n        fi\n\n    - name: Set up Java\n      uses: actions/setup-java@v4\n      with:\n        java-version: ${{ inputs.java_version }}\n        distribution: ${{ inputs.java_distribution }}\n\n    - name: System setup\n      shell: bash\n      run: |\n        sudo apt update\n        sudo apt install -y make\n        make compile-module\n\n    - name: Cache dependencies\n      uses: actions/cache@v4\n      with:\n        path: |\n          ~/.m2/repository\n          /var/cache/apt\n        key: jedis-${{hashFiles('**/pom.xml')}}\n\n    - name: Set up Docker Compose environment\n      shell: bash\n      run: |\n        mkdir -m 777 $REDIS_ENV_WORK_DIR\n        make start ${{ steps.args.outputs.make_args }}\n      env:\n        REDIS_ENV_CONF_DIR: ${{ steps.args.outputs.redis_env_conf_dir }}\n        REDIS_ENV_WORK_DIR: ${{ steps.args.outputs.redis_env_work_dir }}\n\n    - name: Maven offline\n      shell: bash\n      run: |\n        mvn -q dependency:go-offline\n\n    - name: Build docs\n      shell: bash\n      run: |\n        mvn javadoc:jar\n\n    - name: Run Maven tests\n      shell: bash\n      run: |\n        export TEST_ENV_PROVIDER=oss-docker\n        export TEST_WORK_FOLDER=$REDIS_ENV_WORK_DIR\n        echo $TEST_WORK_FOLDER\n        if [ -z \"$TESTS\" ]; then\n          mvn -B -Dwith-param-names=true clean compile verify\n        else\n          mvn -B -Dwith-param-names=true -Dtest=$TESTS clean verify\n        fi\n      env:\n        REDIS_ENV_WORK_DIR: ${{ steps.args.outputs.redis_env_work_dir }}\n        JVM_OPTS: \"-XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError -XX:HeapDumpPath=${{ runner.temp }}/heapdump-${{ steps.args.outputs.redis_version_label }}.hprof\"\n        TESTS: ${{ inputs.specific_test }}\n\n    - name: Upload Heap Dumps\n      if: failure()\n      uses: actions/upload-artifact@v4\n      with:\n        name: heap-dumps-${{ steps.args.outputs.redis_version_label }}\n        path: ${{ runner.temp }}/heapdump-${{ steps.args.outputs.redis_version_label }}.hprof\n        retention-days: 5\n\n    - name: Upload Surefire Dump File\n      uses: actions/upload-artifact@v4\n      with:\n        name: surefire-dumpstream\n        path: target/surefire-reports/*.dumpstream\n\n    - name: Publish Test Results\n      uses: EnricoMi/publish-unit-test-result-action@v2\n      if: github.actor != 'dependabot[bot]'\n      with:\n        files: |\n          target/surefire-reports/**/*.xml\n          target/failsafe-reports/**/*.xml\n\n    - name: Collect logs on failure\n      if: failure()\n      shell: bash\n      run: |\n        echo \"Collecting logs from $REDIS_ENV_WORK_DIR...\"\n        ls -la $REDIS_ENV_WORK_DIR\n      env:\n        REDIS_ENV_WORK_DIR: ${{ steps.args.outputs.redis_env_work_dir }}\n\n    - name: Upload logs on failure\n      if: failure()\n      uses: actions/upload-artifact@v4\n      with:\n        name: redis-env-work-logs-${{ steps.args.outputs.redis_version_label }}\n        path: ${{ steps.args.outputs.redis_env_work_dir }}\n\n    - name: Tear down Docker Compose environment\n      if: always()\n      shell: bash\n      run: |\n        make stop\n      continue-on-error: true\n\n    - name: Upload merged coverage to Codecov\n      if: inputs.codecov_token != ''\n      uses: codecov/codecov-action@v5\n      with:\n        files: ./target/site/jacoco/jacoco.xml\n        flags: docker-${{ steps.args.outputs.redis_version_label }}\n        name: merged-coverage\n        fail_ci_if_error: false\n        token: ${{ inputs.codecov_token }}\n\n    - name: Upload test results to Codecov (unit + IT)\n      if: inputs.codecov_token != '' && !cancelled() && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch')\n      uses: codecov/test-results-action@v1\n      with:\n        fail_ci_if_error: false\n        files: ./target/surefire-reports/TEST*,./target/failsafe-reports/TEST*\n        flags: test-results-docker-${{ steps.args.outputs.redis_version_label }}\n        token: ${{ inputs.codecov_token }}\n\n"
  },
  {
    "path": ".github/codecov.yml",
    "content": "codecov:                                    # see https://docs.codecov.com/docs/codecovyml-reference\n  branch: master\n\ncoverage:\n  status:                                   # see https://docs.codecov.com/docs/commit-status\n    project:\n      default:\n        target: auto                        # minimum coverage ratio that the commit must meet to be considered a success\n        threshold: 5                        # Allow the coverage to drop by <number>%, and posting a success status\n        branches:\n          - master\n          - '[0-9].*'\n\ncomment:                                    # see https://docs.codecov.com/docs/pull-request-comments\n  layout: \"condensed_header, condensed_files, condensed_footer\"\n  behavior: new\n  require_changes: true\n\nignore:\n  - \"**/*.txt\"\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: \"maven\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/release-drafter-config.yml",
    "content": "name-template: '$NEXT_MINOR_VERSION'\ntag-template: 'v$NEXT_MINOR_VERSION'\nfilter-by-commitish: true\ncommitish: master\nautolabeler:\n  - label: 'maintenance'\n    files:\n      - '*.md'\n      - '.github/*'\n  - label: 'bug'\n    branch:\n      - '/bug-.+'\n  - label: 'maintenance'\n    branch:\n      - '/maintenance-.+'\n  - label: 'feature'\n    branch:\n      - '/feature-.+'\ncategories:\n  - title: '🔥 Breaking Changes'\n    labels:\n      - 'breakingchange'\n  - title: '🧪 Experimental Features'\n    labels:\n      - 'experimental'\n  - title: '🚀 New Features'\n    labels:\n      - 'feature'\n      - 'enhancement'\n  - title: '🐛 Bug Fixes'\n    labels:\n      - 'fix'\n      - 'bugfix'\n      - 'bug'\n      - 'BUG'\n  - title: '🧰 Maintenance'\n    labels:\n      - 'maintenance'\n      - 'dependencies'\n      - 'documentation'\n      - 'docs'\n      - 'testing'\nchange-template: '- $TITLE (#$NUMBER)'\nexclude-labels:\n  - 'skip-changelog'\ntemplate: |\n  # Changes\n\n  $CHANGES\n\n  ## Contributors\n  We'd like to thank all the contributors who worked on this release!\n\n  $CONTRIBUTORS\n\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    - cron: '31 4 * * 4'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      actions: read\n      contents: read\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: java\n        dependency-caching: true\n        build-mode: manual\n\n    - name: Set up JDK 8\n      uses: actions/setup-java@v4\n      with:\n        distribution: temurin\n        java-version: '8'\n        cache: maven\n\n    - name: Build\n      run: mvn -B package\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Publish Docs\non:\n  push:\n    branches: [\"master\"]\npermissions:\n  contents: read\n  pages: write\n  id-token: write\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\njobs:\n  build-and-deploy:\n    concurrency: ci-${{ github.ref }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.13\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r docs/requirements.txt\n      - name: Build docs\n        run: |\n          mkdocs build -d docsbuild\n      - name: Setup Pages\n        uses: actions/configure-pages@v3\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: 'docsbuild'\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/doctests.yml",
    "content": "name: Documentation Tests\n\non:\n  push:\n    branches:\n      - master\n      - 'emb-examples'\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  doctests:\n    runs-on: ubuntu-latest\n    services:\n      redis:\n        image: redis:latest\n        options: >-\n          --health-cmd \"redis-cli ping\" --health-interval 10s --health-timeout 5s --health-retries 5\n        ports:\n          - 6379:6379\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.m2/repository\n            /var/cache/apt\n          key: jedis-${{hashFiles('**/pom.xml')}}\n      - name: Set up Java\n        uses: actions/setup-java@v4\n        with:\n          java-version: '11'\n          distribution: 'temurin'\n      - name: Maven offline\n        run: |\n          mvn -q dependency:go-offline\n      - name: Run doctests\n        run: |\n          mvn -Pdoctests clean compile test\n"
  },
  {
    "path": ".github/workflows/format_check.yml",
    "content": "name: Java Format Check\n\non:\n  workflow_dispatch:\n  pull_request:\n    paths:\n      - '**/*.java' # Only trigger for Java file changes\n\njobs:\n  check-format:\n    runs-on: ubuntu-latest\n\n    steps:\n      # Step 1: Checkout the PR code\n      - name: Checkout code\n        uses: actions/checkout@v3 \n\n      # Step 2: Set up Java (if needed for format check tools)\n      - name: Set up JDK\n        uses: actions/setup-java@v4\n        with:\n          java-version: '11'\n          distribution: 'temurin'\n\n      # Step 3: Fetch latest changes\n      - name: Fetch latest changes\n        run: git fetch origin\n\n      - name: Get changed Java files\n        id: changed_files\n        run: |\n          echo \"::group::Changed Java Files\"\n          echo Base Branch: ${{ github.event.pull_request.base.ref }}\n          CHANGED_FILES=$(git diff --name-only --diff-filter=A origin/${{ github.event.pull_request.base.ref }} | grep '\\.java$' || true)\n          echo \"$CHANGED_FILES\"\n          echo \"::endgroup::\"\n          # Write the multiline content to a file\n          echo \"$CHANGED_FILES\" > changed_files.txt\n\n      # Step 4: Get a list of changed Java files in the PR\n      - name: Check Java file format\n        run: |\n          # Check if the changed_files.txt exists\n          if [ ! -f changed_files.txt ]; then\n            echo \"No changed files found.\"\n            exit 0\n          fi\n\n          # Read the multiline content from the file\n          CHANGED_FILES=$(cat changed_files.txt)\n\n          # Ensure there are changed files\n          if [ -z \"$CHANGED_FILES\" ]; then\n            echo \"No Java files changed.\"\n          else\n            echo \"Processing the following changed Java files:\"\n\n            # Iterate over the CHANGED_FILES variable, assuming files are separated by newlines\n            while IFS= read -r FILE; do\n              # Skip empty lines if any\n              if [ -n \"$FILE\" ]; then\n                FILE_NAME=$(basename \"$FILE\")\n                echo \"Checking for $FILE_NAME\"\n                \n                # Run your formatter validation for each file\n                mvn formatter:validate -f formatter-pom.xml \"-Dformatter.includes=**/$FILE_NAME\"\n              fi\n            done <<< \"$CHANGED_FILES\"\n          fi\n"
  },
  {
    "path": ".github/workflows/integration.yml",
    "content": "---\n\nname: Build and Test using local environment\n\non:\n  push:\n    paths-ignore:\n      - 'docs/**'\n      - '**/*.md'\n      - '**/*.rst'\n    branches:\n      - master\n      - '[0-9].*'\n      - 'topic/**'\n  pull_request:\n    branches:\n      - master\n      - '[0-9].*'\n  schedule:\n    - cron: '0 1 * * *' # nightly build\n  workflow_dispatch:\n\njobs:\n\n  build:\n    name: Build and Test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up publishing to maven central\n        uses: actions/setup-java@v4\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n      - name: System setup\n        run: |\n          sudo apt update\n          sudo apt install -y make\n          make system-setup\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.m2/repository\n            /var/cache/apt\n          key: jedis-${{hashFiles('**/pom.xml')}}\n      - name: Maven offline\n        run: |\n          mvn -q dependency:go-offline\n      - name: Build docs\n        run: |\n          mvn javadoc:jar\n      - name: Run tests\n        run: |\n          export TEST_ENV_PROVIDER=oss-local\n          make test-local\n        env:\n          JVM_OPTS: -Xmx3200m\n          TERM: dumb\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    # branches to consider in the event; optional, defaults to all\n    branches:\n      - master\n  workflow_dispatch:\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      # Drafts your next Release notes as Pull Requests are merged into \"master\"\n      - uses: release-drafter/release-drafter@v5\n        with:\n          # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml\n           config-name: release-drafter-config.yml\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n"
  },
  {
    "path": ".github/workflows/snapshot.yml",
    "content": "---\n\nname: Publish Snapshot\n\non:\n  push:\n    branches:\n      - master\n      - '[0-9].x'\n  workflow_dispatch:\n\njobs:\n\n  snapshot:\n    name: Deploy Snapshot\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up publishing to maven central\n        uses: actions/setup-java@v4\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n          server-id: central\n          server-username: MAVEN_USERNAME\n          server-password: MAVEN_PASSWORD\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.m2/repository\n            /var/cache/apt\n          key: jedis-${{hashFiles('**/pom.xml')}}\n      - name: mvn offline\n        run: |\n          mvn -q dependency:go-offline\n      - name: deploy\n        run: |\n          mvn --no-transfer-progress \\\n            -DskipTests deploy\n        env:\n          MAVEN_USERNAME: ${{secrets.OSSH_USERNAME}}\n          MAVEN_PASSWORD: ${{secrets.OSSH_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/stale-issues.yml",
    "content": "name: \"Close stale issues\"\non:\n  schedule:\n  - cron: \"0 0 * * *\"\n\npermissions: {}\njobs:\n  stale:\n    permissions:\n      issues: write  #  to close stale issues (actions/stale)\n      pull-requests: write  #  to close stale PRs (actions/stale)\n\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/stale@v3\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        stale-issue-message: 'This issue is marked stale. It will be closed in 30 days if it is not updated.'\n        stale-pr-message: 'This pull request is marked stale. It will be closed in 30 days if it is not updated.'\n        days-before-stale: 30\n        days-before-close: 30\n        stale-issue-label: \"stale\"\n        stale-pr-label: \"stale\"\n        operations-per-run: 10\n        remove-stale-when-updated: true\n        only-labels: 'waiting-for-feedback'\n        exempt-issue-labels: 'feedback-provided'\n        exempt-pr-labels: 'feedback-provided'\n        exempt-all-milestones: true"
  },
  {
    "path": ".github/workflows/test-on-docker.yml",
    "content": "---\n\nname: Build and Test using a containerized environment\nrun-name: \"Build and Test using ${{ github.event.inputs.client_libs_test_image_tag != '' && format('image: {0}', github.event.inputs.client_libs_test_image_tag) || 'a containerized environment' }}\"\n\non:\n  push:\n    paths-ignore:\n      - 'docs/**'\n      - '**/*.md'\n      - '**/*.rst'\n    branches:\n      - master\n      - '[0-9].*'\n      - 'feature/**'\n      - 'topic/**'\n  pull_request:\n    branches:\n      - master\n      - '[0-9].*'\n      - 'feature/**'\n  schedule:\n    - cron: '0 1 * * *' # nightly build\n  workflow_dispatch:\n    inputs:\n      specific_test:\n        description: 'Run specific test(s) (optional)'\n        required: false\n        default: ''\n      client_libs_test_image_tag:\n        description: 'Custom client libs test image tag to use instead of redis_version'\n        required: false\n        default: ''\njobs:\n\n  build:\n    name: Build and Test\n    if: github.event.inputs.client_libs_test_image_tag == ''\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        redis_version:\n          - \"8.6\"\n          - \"8.4\"\n          - \"8.2\"\n          - \"8.0\"\n          - \"7.4\"\n          - \"7.2\"\n          # - \"6.2\"\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - uses: ./.github/actions/run-tests\n        with:\n          redis_version: ${{ matrix.redis_version }}\n          specific_test: ${{ github.event.inputs.specific_test || '' }}\n          codecov_token: ${{ secrets.CODECOV_TOKEN }}\n          redis_env_work_dir: ${{ github.workspace }}/redis-env-work\n          redis_env_conf_dir: ${{ github.workspace }}/src/test/resources/env\n\n  build_using_custom_image:\n    name: Build and Test using custom image\n    if: github.event.inputs.client_libs_test_image_tag != ''\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - uses: ./.github/actions/run-tests\n        with:\n          client_libs_test_image_tag: ${{ github.event.inputs.client_libs_test_image_tag }}\n          specific_test: ${{ github.event.inputs.specific_test || '' }}\n          codecov_token: ${{ secrets.CODECOV_TOKEN }}\n          redis_env_work_dir: ${{ github.workspace }}/redis-env-work\n          redis_env_conf_dir: ${{ github.workspace }}/src/test/resources/env\n"
  },
  {
    "path": ".github/workflows/version-and-release.yml",
    "content": "name: Release\n\non:\n  release:\n    types: [published]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: get version from tag\n        id: get_version\n        run: |\n          realversion=\"${GITHUB_REF/refs\\/tags\\//}\"\n          realversion=\"${realversion//v/}\"\n          echo \"VERSION=$realversion\" >> $GITHUB_OUTPUT\n\n      - name: Set up publishing to maven central\n        uses: actions/setup-java@v4\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n          server-id: central\n          server-username: MAVEN_USERNAME\n          server-password: MAVEN_PASSWORD\n\n      - name: mvn versions\n        run: mvn versions:set -DnewVersion=${{ steps.get_version.outputs.VERSION }}\n\n      - name: Install gpg key\n        run: |\n          cat <(echo -e \"${{ secrets.OSSH_GPG_SECRET_KEY }}\") | gpg --batch --import\n          gpg --list-secret-keys --keyid-format LONG\n\n      - name: Publish\n        run: |\n          mvn --no-transfer-progress \\\n            --batch-mode \\\n            -Dgpg.passphrase='${{ secrets.OSSH_GPG_SECRET_KEY_PASSWORD }}' \\\n            -DskipTests deploy -P release\n        env:\n          MAVEN_USERNAME: ${{secrets.OSSH_USERNAME}}\n          MAVEN_PASSWORD: ${{secrets.OSSH_TOKEN}}\n"
  },
  {
    "path": ".gitignore",
    "content": ".classpath\n*.iml\n*.ipr\n*.iws\nnb*\n.project\n.settings/\n.gradle/\ntarget/\nbuild/\nbin/\ntags\n.idea\n.run\n*.aof\n*.rdb\nredis-git\nappendonlydir/\n.DS_Store\n.vscode/settings.json\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-2023, Redis, inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "PATH := ./redis-git/src:${PATH}\n\n# Supported test env versions\nSUPPORTED_TEST_ENV_VERSIONS := 8.6 8.4 8.2 8.0 7.4 7.2 6.2\nDEFAULT_TEST_ENV_VERSION := 8.6\nREDIS_ENV_WORK_DIR := $(or ${REDIS_ENV_WORK_DIR},/tmp/redis-env-work)\nTOXIPROXY_IMAGE := ghcr.io/shopify/toxiproxy:2.8.0\n\ndefine REDIS1_CONF\ndaemonize yes\nprotected-mode no\nport 6379\nrequirepass foobared\nuser acljedis on allcommands allkeys >fizzbuzz\nuser deploy on allcommands allkeys >verify\npidfile /tmp/redis1.pid\nlogfile /tmp/redis1.log\nsave \"\"\nappendonly no\nenable-module-command yes\nclient-output-buffer-limit pubsub 256k 128k 5\nendef\n\n# UDS REDIS NODES\ndefine REDIS_UDS\ndaemonize yes\nprotected-mode no\nport 0\npidfile /tmp/redis_uds.pid\nlogfile /tmp/redis_uds.log\nunixsocket /tmp/redis_uds.sock\nunixsocketperm 777\nsave \"\"\nappendonly no\nendef\n\n# UNAVAILABLE REDIS NODES\ndefine REDIS_UNAVAILABLE_CONF\ndaemonize yes\nprotected-mode no\nport 6400\npidfile /tmp/redis_unavailable.pid\nlogfile /tmp/redis_unavailable.log\nsave \"\"\nappendonly no\nendef\n\nexport REDIS1_CONF\nexport REDIS_UDS\nexport REDIS_UNAVAILABLE_CONF\n\n\nstart-local: cleanup compile-module\n\t# Simple local test env that provides only \"standalone-0\" endpoint and an instance listening on Unix socket\n\texport TEST_ENV_PROVIDER=oss-source\n\techo \"$$REDIS1_CONF\" | redis-server -\n\techo \"$$REDIS_UDS\" | redis-server -\n\techo \"$$REDIS_UNAVAILABLE_CONF\" | redis-server -\n\ncleanup:\n\t- rm -vf /tmp/redis*.log 2>/dev/null\n\t- rm dump.rdb appendonly.aof - 2>/dev/null\n\nstop-local:\n\t@for pidfile in \\\n\t\t/tmp/redis1.pid \\\n\t\t/tmp/redis_uds.pid; do \\\n\t\tif [ -f $$pidfile ]; then \\\n\t\t\tpid=$$(cat $$pidfile); \\\n\t\t\tif kill -0 $$pid 2>/dev/null; then \\\n\t\t\t\techo \"Stopping process $$pid from $$pidfile\"; \\\n\t\t\t\tkill $$pid; \\\n\t\t\t\tsleep 1; \\\n\t\t\t\tif kill -0 $$pid 2>/dev/null; then \\\n\t\t\t\t\techo \"PID $$pid did not exit, forcing kill\"; \\\n\t\t\t\t\tkill -9 $$pid; \\\n\t\t\t\tfi; \\\n\t\t\tfi; \\\n\t\t\trm -f $$pidfile; \\\n\t\tfi; \\\n\tdone\n\t[ -f /tmp/redis_unavailable.pid ] && kill `cat /tmp/redis_unavailable.pid` || true\n\ntest-local: | start-local mvn-test-local stop-local\n\nmvn-test-local:\n\t@TEST_ENV_PROVIDER=oss-source mvn -Dwith-param-names=true -Dtest=${TEST} clean verify\n\nmvn-test:\n\tmvn -Dwith-param-names=true -Dtest=${TEST} clean verify\n\nformat:\n\tmvn java-formatter:format\n\nsystem-setup:\n\t# Install gcc with Homebrew (macOS) or apt (Linux)\n\tif [ \"$(shell uname)\" = \"Darwin\" ]; then \\\n\t\tbrew install gcc || true; \\\n\telse \\\n\t\tsudo apt install -y gcc g++; \\\n\tfi\n\t[ ! -e redis-git ] && git clone https://github.com/redis/redis.git --branch unstable --single-branch redis-git || true\n\t$(MAKE) -C redis-git clean\n\t$(MAKE) -C redis-git BUILD_TLS=yes\n\ncompile-module:\n\tgcc -shared -o /tmp/testmodule.so -fPIC src/test/resources/testmodule.c\n\n# Start test environment with specific version using predefined docker compose setup\n\nstart:\n\t@if [ -z \"$(version)\" ]; then \\\n\t\tversion=$(arg); \\\n\t\tif [ -z \"$$version\" ]; then \\\n\t\t\tversion=\"$(DEFAULT_TEST_ENV_VERSION)\"; \\\n\t\tfi; \\\n\tfi; \\\n\tif [ -n \"$$CLIENT_LIBS_TEST_IMAGE_TAG\" ]; then \\\n\t\techo \"Using custom image tag: $$CLIENT_LIBS_TEST_IMAGE_TAG\"; \\\n\t\tversion=\"\"; \\\n\telif ! echo \"$(SUPPORTED_TEST_ENV_VERSIONS)\" | grep -qw \"$$version\"; then \\\n\t\techo \"Error: Invalid version '$$version'. Supported versions are: $(SUPPORTED_TEST_ENV_VERSIONS).\"; \\\n\t\texit 1; \\\n\tfi; \\\n\tdefault_env_file=\"src/test/resources/env/.env\"; \\\n\tcustom_env_file=\"src/test/resources/env/.env.v$$version\"; \\\n\tenv_files=\"--env-file $$default_env_file\"; \\\n\tif [ -f \"$$custom_env_file\" ]; then \\\n\t\tenv_files=\"$$env_files --env-file $$custom_env_file\"; \\\n\tfi; \\\n\trm -rf \"$(REDIS_ENV_WORK_DIR)\"; \\\n\tmkdir -p \"$(REDIS_ENV_WORK_DIR)\"; \\\n\tdocker compose $$env_files -f src/test/resources/env/docker-compose.yml up -d --wait --quiet-pull; \\\n\techo \"Started test environment with Redis version $$version. \"\n\n# Stop the test environment\nstop:\n\tdocker compose -f src/test/resources/env/docker-compose.yml down; \\\n\trm -rf \"$(REDIS_ENV_WORK_DIR)\"; \\\n\techo \"Stopped test environment and performed cleanup.\"\n\ntest: | start mvn-test stop\n\n.PHONY: test test-local start start-local stop stop-local cleanup mvn-test-local mvn-test format system-setup compile-module\n"
  },
  {
    "path": "README.md",
    "content": "# Jedis\n\n[![Release](https://img.shields.io/github/release/redis/jedis.svg?sort=semver)](https://github.com/redis/jedis/releases/latest)\n[![Maven Central](https://img.shields.io/maven-central/v/redis.clients/jedis.svg)](https://central.sonatype.com/artifact/redis.clients/jedis)\n[![Javadocs](https://www.javadoc.io/badge/redis.clients/jedis.svg)](https://www.javadoc.io/doc/redis.clients/jedis)\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/redis/jedis/blob/master/LICENSE)\n[![codecov](https://codecov.io/gh/redis/jedis/branch/master/graph/badge.svg?token=pAstxAAjYo)](https://codecov.io/gh/redis/jedis)\n[![Discord](https://img.shields.io/discord/697882427875393627?style=flat-square)](https://discord.gg/redis)\n\n## What is Jedis?\n\nJedis is a Java client for [Redis](https://github.com/redis/redis \"Redis\") designed for performance and ease of use.\n\nAre you looking for a high-level library to handle object mapping? See [redis-om-spring](https://github.com/redis/redis-om-spring)!\n\n## How do I Redis?\n\n[Learn for free at Redis University](https://university.redis.io/academy/)\n\n[Try the Redis Cloud](https://redis.io/try-free/)\n\n[Dive in developer tutorials](https://redis.io/learn/)\n\n[Join the Redis community](https://redis.io/community/)\n\n[Work at Redis](https://redis.io/careers/jobs/)\n\n## Supported Redis versions\n\nThe most recent version of this library supports redis version \n[7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES),\n[7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES),\n[8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES),\n[8.2](https://github.com/redis/redis/blob/8.2/00-RELEASENOTES) and\n[8.4](https://github.com/redis/redis/blob/8.4/00-RELEASENOTES).\n\nThe table below highlights version compatibility of the most-recent library versions with Redis and JDK versions. Compatibility means communication features, and Redis command capabilities.\n\n\n| Jedis version | Supported Redis versions              | JDK Compatibility |\n|---------------|---------------------------------------|-------------------|\n| 3.9+          | 5.0 to 6.2 Family of releases         | 8, 11             |\n| >= 4.0        | Version 5.0 to 7.2 Family of releases | 8, 11, 17         |\n| >= 5.0        | Version 6.0 to current                | 8, 11, 17, 21     |\n| >= 5.2        | Version 7.2 to current                | 8, 11, 17, 21     |\n| >= 6.0        | Version 7.2 to current                | 8, 11, 17, 21     |\n| >= 7.0        | Version 7.2 to current                | 8, 11, 17, 21     |\n\n## Getting started\n\nTo get started with Jedis, first add it as a dependency in your Java project. If you're using Maven, that looks like this:\n\n```xml\n<dependency>\n    <groupId>redis.clients</groupId>\n    <artifactId>jedis</artifactId>\n    <version>7.1.0</version>\n</dependency>\n```\n\nTo use the cutting-edge Jedis, check [here](https://redis.github.io/jedis/jedis-maven/).\n\nNext, you'll need to connect to Redis. Consider installing a redis server with docker:\n\n```bash\ndocker run -p 6379:6379 -it redis:latest\n```\n\nYou can instantiate a RedisClient like so:\n\n```java\nRedisClient jedis = RedisClient.builder().hostAndPort(\"localhost\", 6379).build();\n```\n\nNow you can send commands:\n\n```java\njedis.sadd(\"planets\", \"Venus\");\n```\n\n## Connecting to a Redis cluster\n\nJedis lets you connect to Redis Clusters, supporting the [Redis Cluster Specification](https://redis.io/topics/cluster-spec).\nTo do this, you'll need to connect using `RedisClusterClient`. See the example below:\n\n```java\nSet<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();\njedisClusterNodes.add(new HostAndPort(\"127.0.0.1\", 7379));\njedisClusterNodes.add(new HostAndPort(\"127.0.0.1\", 7380));\nRedisClusterClient jedis = RedisClusterClient.builder().nodes(jedisClusterNodes).build();\n```\n\nNow you can use the `RedisClusterClient` instance and send commands like you would with a standard pooled connection:\n\n```java\njedis.sadd(\"planets\", \"Mars\");\n```\n\n## Support for Redis data types\n\nJedis includes support for all [Redis data types](https://redis.io/docs/latest/develop/data-types/) and features such as\n[JSON](https://redis.io/docs/latest/develop/data-types/json/) and [VectorSets](https://redis.io/docs/latest/develop/data-types/vector-sets/).\n\n## Failover\n\nJedis supports retry and failover for your Redis deployments. This is useful when:\n\n1. You have more than one Redis deployment. This might include two independent Redis servers or two or more Redis databases replicated across multiple [active-active Redis Enterprise](https://redis.io/docs/latest/operate/rs/databases/active-active/) clusters.\n2. You want your application to connect to one deployment at a time and to fail over to the next available deployment if the first deployment becomes unavailable.\n\nFor the complete failover configuration options and examples, see the [Jedis failover docs](https://redis.github.io/jedis/failover/).\n\n## Token-Based Authentication\n\nJedis supports Token-Based authentication (TBA) starting with 5.3.0 GA release. This feature is complemented by an extension library that enhances the developer experience and provides most of the components required for TBA functionality.\n\nNotably, the extension library includes built-in support for **Microsoft EntraID**, offering a seamless integration as part of the generic solution.\n\nFor more details and examples, please refer to the [Advanced Usage](https://redis.github.io/jedis/advanced-usage/) documentation.\n\n## Documentation\n\nThe [Jedis documentation site](https://redis.github.io/jedis/) contains several useful articles for using Jedis.\n\nYou can also check the [latest Jedis Javadocs](https://www.javadoc.io/doc/redis.clients/jedis/latest/index.html).\n\nSome specific use-case examples can be found in [`redis.clients.jedis.examples`\npackage](https://github.com/redis/jedis/tree/master/src/test/java/redis/clients/jedis/examples/) of the test source codes.\n\n## Troubleshooting\n\nIf you run into trouble or have any questions, we're here to help!\n\nHit us up on the [Redis Discord Server](http://discord.gg/redis) or \n[Jedis GitHub Discussions](https://github.com/redis/jedis/discussions).\n\n## Contributing\n\nWe'd love your contributions!\n\nBug reports are always welcome! [You can open a bug report on GitHub](https://github.com/redis/jedis/issues/new).\n\nYou can also contribute documentation -- or anything to improve Jedis. Please see\n[contribution guideline](https://github.com/redis/jedis/blob/master/.github/CONTRIBUTING.md) for more details.\n\n## License\n\nJedis is licensed under the [MIT license](https://github.com/redis/jedis/blob/master/LICENSE).\n\n## Sponsorship\n\n[![Redis Logo](https://raw.githubusercontent.com/redis/jedis/master/redis-logo-full-color-rgb.png)](https://redis.io/)\n"
  },
  {
    "path": "docs/Dockerfile",
    "content": "FROM squidfunk/mkdocs-material\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Jedis Documentation\n\nThis documentation uses [MkDocs](https://www.mkdocs.org/) to generate the static site.\n\nSee [mkdocs.yml](../mkdocs.yml) for the configuration. \n\nTo develop the documentation locally, you can use the included [Dockerfile](Dockerfile) to build a container with all the \ndependencies, and run it to preview your changes:\n\n```bash\n# in docs/\ndocker build -t squidfunk/mkdocs-material .\n# cd ..\ndocker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material \n```"
  },
  {
    "path": "docs/advanced-usage.md",
    "content": "# Advanced Usage\n\n## Transactions\n\nTo do transactions in Jedis, you have to wrap operations in a transaction block, very similar to pipelining:\n\n```java\njedis.watch (key1, key2, ...);\nTransaction t = jedis.multi();\nt.set(\"foo\", \"bar\");\nt.exec();\n```\n\nNote: when you have any method that returns values, you have to do like this:\n\n\n```java\nTransaction t = jedis.multi();\nt.set(\"fool\", \"bar\"); \nResponse<String> result1 = t.get(\"fool\");\n\nt.zadd(\"foo\", 1, \"barowitch\"); t.zadd(\"foo\", 0, \"barinsky\"); t.zadd(\"foo\", 0, \"barikoviev\");\nResponse<Set<String>> sose = t.zrange(\"foo\", 0, -1);   // get the entire sortedset\nt.exec();                                              // dont forget it\n\nString foolbar = result1.get();                       // use Response.get() to retrieve things from a Response\nint soseSize = sose.get().size();                      // on sose.get() you can directly call Set methods!\n\n// List<Object> allResults = t.exec();                 // you could still get all results at once, as before\n```\nNote that a Response Object does not contain the result before t.exec() is called (it is a kind of a Future). Forgetting exec gives you exceptions. In the last lines, you see how transactions/pipelines were dealt with before version 2. You can still do it that way, but then you need to extract objects from a list, which contains also Redis status messages.\n\nNote 2: Redis does not allow to use intermediate results of a transaction within that same transaction. This does not work:\n\n```java\n// this does not work! Intra-transaction dependencies are not supported by Redis!\njedis.watch(...);\nTransaction t = jedis.multi();\nif(t.get(\"key1\").equals(\"something\"))\n   t.set(\"key2\", \"value2\");\nelse \n   t.set(\"key\", \"value\");\n```\n\nHowever, there are some commands like setnx, that include such a conditional execution. Those are of course supported within transactions. You can build your own customized commands using EVAL / LUA scripting. \n\n\n## Pipelining\n\nSometimes you need to send a bunch of different commands. A very cool way to do that, and have better performance than doing it the naive way, is to use pipelining. This way you send commands without waiting for response, and you actually read the responses at the end, which is faster. \n\nHere is how to do it:\n\n```java\nPipeline p = jedis.pipelined();\np.set(\"fool\", \"bar\"); \np.zadd(\"foo\", 1, \"barowitch\");  p.zadd(\"foo\", 0, \"barinsky\"); p.zadd(\"foo\", 0, \"barikoviev\");\nResponse<String> pipeString = p.get(\"fool\");\nResponse<Set<String>> sose = p.zrange(\"foo\", 0, -1);\np.sync(); \n\nint soseSize = sose.get().size();\nSet<String> setBack = sose.get();\n```\nFor more explanations see code comments in the transaction section.\n\n\n## Publish/Subscribe\n\nTo subscribe to a channel in Redis, create an instance of JedisPubSub and call subscribe on the Jedis instance:\n\n```java\nclass MyListener extends JedisPubSub {\n        public void onMessage(String channel, String message) {\n        }\n\n        public void onSubscribe(String channel, int subscribedChannels) {\n        }\n\n        public void onUnsubscribe(String channel, int subscribedChannels) {\n        }\n\n        public void onPSubscribe(String pattern, int subscribedChannels) {\n        }\n\n        public void onPUnsubscribe(String pattern, int subscribedChannels) {\n        }\n\n        public void onPMessage(String pattern, String channel, String message) {\n        }\n}\n\nMyListener l = new MyListener();\n\njedis.subscribe(l, \"foo\");\n```\nNote that subscribe is a blocking operation because it will poll Redis for responses on the thread that calls subscribe.  A single JedisPubSub instance can be used to subscribe to multiple channels.  You can call subscribe or psubscribe on an existing JedisPubSub instance to change your subscriptions.\n\n\n## Monitoring\n\nTo use the monitor command you can do something like the following:\n\n```java\nnew Thread(new Runnable() {\n    public void run() {\n        Jedis j = new Jedis(\"localhost\");\n        for (int i = 0; i < 100; i++) {\n            j.incr(\"foobared\");\n            try {\n                Thread.sleep(200);\n            } catch (InterruptedException e) {\n            }\n        }\n        j.disconnect();\n    }\n}).start();\n\njedis.monitor(new JedisMonitor() {\n    public void onCommand(String command) {\n        System.out.println(command);\n    }\n});\n```\n## Token-Based Authentication\n\nStarting with version 5.3.0 GA, Jedis supports token-based authentication. The [redis-authx-entraid](https://github.com/redis/jvm-redis-authx-entraid) repository provides the necessary components that Jedis utilizes to enable this functionality. \n\nAdditionally, support for Microsoft EntraID has been fully implemented and is now available as an extension for Azure Managed Redis (AMR) and Azure Cache for Redis(ACR).\n\n### Using With Custom Identity Provider\nJedis provides a token-based authentication mechanism with a generic identity provider of your choice. \nFor custom use of this feature, you will need to provide an implementation of `IdentityProvider` and `IdentityProviderConfig` and configure it in the way Jedis expects. \n\nYou will have the required interfaces from transitive Jedis dependencies;\n\n```\n    <dependency>\n        <groupId>redis.clients.authentication</groupId>\n        <artifactId>redis-authx-core</artifactId>\n        <version>${version}</version>\n    </dependency>\n```\n\n**An example to get started:**\n```java\npublic class YourCustomIdentityProviderConfig implements IdentityProviderConfig {\n    ...\n}\n\npublic class YourCustomIdentityProvider implements IdentityProvider {\n    ...\n}\n```\n\nThen configure Jedis like this:\n```java\nIdentityProviderConfig yourCustomIdentityProviderConfig = new YourCustomIdentityProviderConfig();\nTokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder().identityProviderConfig(yourCustomIdentityProviderConfig);\n\nJedisClientConfig config = DefaultJedisClientConfig.builder()\n                .authXManager(new AuthXManager(tokenAuthConfig)).build();\n...\n```\n### Using With Microsoft EntraID\n\nExtension for EntraID is fully integrated and ready to use with [Azure Managed Redis](https://azure.microsoft.com/en-us/products/managed-redis)(AMR) or [Azure Cache for Redis](https://azure.microsoft.com/en-us/products/cache/)(ACR). All you need is to add the EntraID dependency and code for configuration for chosen authentication type with Microsoft EntraID service.\n\nTo get started, add the `redis-authx-entraid` extension as dependency;\n\n```\n    <dependency>\n        <groupId>redis.clients.authentication</groupId>\n        <artifactId>redis-authx-entraid</artifactId>\n        <version>${version}</version>\n    </dependency>\n```\n\nAfter adding the dependency, configure it using `EntraIDTokenAuthConfigBuilder`:\n\n```java\n...\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .expirationRefreshRatio(0.8F)\n        .clientId(\"yourClientId\")\n        .secret(\"yourClientSecret\")\n        .authority(\"yourAuthority\")\n        .scopes(\"yourRedisScopes\").build();\n\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n\n    JedisClientConfig config = DefaultJedisClientConfig.builder()\n        .authXManager(authXManager).build();\n...\n```\n\nHere you will see the `AuthXManager` class that is built into Jedis. Essentially it integrates the extension into Jedis and handles the authentication process.  \nFor other available configurations, detailed information and usage of Jedis with Microsoft EntraID, please refer to the [official guide](https://redis.io/docs/latest/develop/clients/jedis/amr/)\n\n**Setting Up AMR or ACR with Microsoft EntraID:**\n\nTo use Microsoft EntraID with AMR or ACR, for sure you will need to set up and configure your AMR/ACR services as well as Microsoft EntraID. The following resources provide useful information;\n\n[Azure Managed Redis](https://azure.microsoft.com/en-us/products/managed-redis)\n\n[Azure Cache for Redis](https://azure.microsoft.com/en-us/products/cache/)\n\n[Microsoft Entra ID for AMR authentication](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/managed-redis/managed-redis-entra-for-authentication)\n\n[Microsoft Entra ID for ACR authentication](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication)\n\n[Use Microsoft Entra](https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad?tabs=workforce-configuration)\n\n## HostAndPortMapper\n\nWhen running Jedis in certain network environments, such as behind a NAT gateway, or within container orchestration systems like Docker or Kubernetes, the host and port that the client needs to connect to might be different from the host and port that the Redis Cluster nodes report. To handle this discrepancy, Jedis provides the `HostAndPortMapper` interface.\n\nThis allows you to dynamically map the address reported by a Redis node to a different address that the client can actually reach. You can implement this either by creating a dedicated class or by using a concise lambda expression.\n\n### Example use case: NAT or Docker with different advertised ports\nSuppose you run a Redis cluster inside Docker on a remote host.\nInside the cluster config, nodes announce addresses like:\n```\n172.18.0.2:6379\n172.18.0.3:6379\n172.18.0.4:6379\n```\nBut externally, you reach them through the host IP with mapped ports:\n```\nmy-redis.example.com:7001\nmy-redis.example.com:7002\nmy-redis.example.com:7003\n```\n### Implementing with a Dedicated Class\n\nYou can provide your mapping logic by creating a class that implements the `HostAndPortMapper` interface. This approach is useful for more complex mapping logic or for reusability.\n\nFirst, define your custom mapper class:\n\n```java\npublic class DockerNATMapper implements HostAndPortMapper {\n\n    // Key: The address reported by Redis (internal).\n    // Value: The address the client should connect to (external).\n    private final Map<HostAndPort, HostAndPort> mapping;\n\n    public DockerNATMapper(Map<HostAndPort, HostAndPort> mapping) {\n        this.mapping = mapping;\n    }\n\n    @Override\n    public HostAndPort getHostAndPort(HostAndPort hostAndPort) {\n        return mapping.getOrDefault(hostAndPort, hostAndPort);\n    }\n}\n```\n\nThen, instantiate this class and pass it to the RedisClusterClient builder:\n\n```java\nMap<HostAndPort, HostAndPort> nodeMapping = new HashMap<>();\nnodeMapping.put(new HostAndPort(\"172.18.0.2\", 6379), new HostAndPort(\"my-redis.example.com\", 7001));\nnodeMapping.put(new HostAndPort(\"172.18.0.3\", 6379), new HostAndPort(\"my-redis.example.com\", 7002));\nnodeMapping.put(new HostAndPort(\"172.18.0.4\", 6379), new HostAndPort(\"my-redis.example.com\", 7002));\n\nSet<HostAndPort> initialNodes = new HashSet<>();\n// seed node\ninitialNodes.add(new HostAndPort(\"my-redis.example.com\", 7001));\n\nHostAndPortMapper mapper = new DockerNATMapper(nodeMapping);\n\nJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()\n        .user(\"myuser\")\n        .password(\"mypassword\")\n        .hostAndPortMapper(mapper)\n        .build();\n\nRedisClusterClient jedisCluster = RedisClusterClient.builder()\n        .nodes(initialNodes)\n        .clientConfig(jedisClientConfig)\n        .build();\n```\n\nNow, when RedisClusterClient discovers a node at \"172.18.0.2:6379\", the mapper will translate it to \"localhost:7001\" before attempting to connect.\n\n### Implementing with a Lambda Expression\nSince HostAndPortMapper is a functional interface (it has only one abstract method), you can also provide the implementation more concisely using a lambda expression. This is often preferred for simpler, inline mapping logic.\n\n```java\nMap<HostAndPort, HostAndPort> nodeMapping = new HashMap<>();\nnodeMapping.put(new HostAndPort(\"172.18.0.2\", 6379), new HostAndPort(\"my-redis.example.com\", 7001));\nnodeMapping.put(new HostAndPort(\"172.18.0.3\", 6379), new HostAndPort(\"my-redis.example.com\", 7002));\nnodeMapping.put(new HostAndPort(\"172.18.0.4\", 6379), new HostAndPort(\"my-redis.example.com\", 7002));\n\nSet<HostAndPort> initialNodes = new HashSet<>();\ninitialNodes.add(new HostAndPort(\"my-redis.example.com\", 7001));\n\nHostAndPortMapper mapper = internalAddress -> nodeMapping.getOrDefault(internalAddress, internalAddress);\n\nJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()\n        .user(\"myuser\")\n        .password(\"mypassword\")\n        .hostAndPortMapper(mapper)\n        .build();\n\nRedisClusterClient jedisCluster = RedisClusterClient.builder()\n        .nodes(initialNodes)\n        .clientConfig(jedisClientConfig)\n        .build();\n```\n\n## Miscellaneous \n\n### A note about String and Binary - what is native?\n\nRedis/Jedis talks a lot about Strings. And here [[http://redis.io/topics/internals]] it says Strings are the basic building block of Redis. However, this stress on strings may be misleading. Redis' \"String\" refer to the C char type (8 bit), which is incompatible with Java Strings (16-bit). Redis sees only 8-bit blocks of data of predefined length, so normally it doesn't interpret the data (it's \"binary safe\"). Therefore in Java, byte[] data is \"native\", whereas Strings have to be encoded before being sent, and decoded after being retrieved by the SafeEncoder. This has some minor performance impact.\nIn short: if you have binary data, don't encode it into String, but use the binary versions.\n\n### A note on Redis' master/slave distribution\n\nA Redis network consists of redis servers, which can be either masters or slaves. Slaves are synchronized to the master (master/slave replication). However, master and slaves look identical to a client, and slaves do accept write requests, but they will not be propagated \"up-hill\" and could eventually be overwritten by the master. It makes sense to route reads to slaves, and write demands to the master. Furthermore, being a slave doesn't prevent from being considered master by another slave.\n"
  },
  {
    "path": "docs/css/extra.css",
    "content": "/* extra.css */\n.md-header {\n    background-color: #FB2A2C; \n}\n"
  },
  {
    "path": "docs/failover.md",
    "content": "# Automatic Failover and Failback with Jedis\n\n> API was significantly changed in 7.0.0. Please follow the migration guide below.\n> \n> This feature is experimental and may change in future versions.\n\nJedis supports failover and failback for your Redis deployments. This is useful when:\n1. You have more than one Redis deployment. This might include two independent Redis servers or two or more Redis databases replicated across multiple [active-active Redis Enterprise](https://docs.redis.com/latest/rs/databases/active-active/) clusters.\n2. You want your application to connect to and use one deployment at a time.\n3. You want your application to fail over to the next available deployment if the current deployment becomes unavailable.\n4. You want your application to fail back to the original deployment when it becomes available again.\n\nJedis will fail over to a subsequent Redis deployment after reaching a configurable failure threshold.\nThis failure threshold is implemented using a [circuit breaker pattern](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern).\n\nYou can also configure Jedis to retry failed calls to Redis.\nOnce a maximum number of retries have been exhausted, the circuit breaker will record a failure.\nWhen the circuit breaker reaches its failure threshold, a failover will be triggered on the subsequent operation.\nIn the background, Jedis executes configured health checks to determine when a Redis deployment is available again.\nWhen this occurs, Jedis will fail back to the original deployment after a configurable grace period.\n\nThe remainder of this guide describes:\n\n* A basic failover and health check configuration\n* Supported retry and circuit breaker settings\n* Failback and the database selection API\n* Dynamic database management for adding and removing databases at runtime\n* Dynamic weight management for runtime priority adjustments (since 7.4.0)\n\nWe recommend that you read this guide carefully and understand the configuration settings before enabling Jedis failover\nin production.\n\n## Migration from 6.x to 7.x\n\nIn Jedis 6.x, failover was supported using special constructor for `UnifiedJedis`.\nIn Jedis 7.x, failover is supported using `MultiDbClient` and `MultiDbConfig.builder`:\n```java\n// Jedis 6.x\nJedisClientConfig config = DefaultJedisClientConfig.builder().user(\"cache\").password(\"secret\").build();\n\nClusterConfig[] clientConfigs = new ClusterConfig[2];\nclientConfigs[0] = new ClusterConfig(new HostAndPort(\"redis-east.example.com\", 14000), config);\nclientConfigs[1] = new ClusterConfig(new HostAndPort(\"redis-west.example.com\", 14000), config);\n\nMultiClusterClientConfig.Builder builder = new MultiClusterClientConfig.Builder(clientConfigs);\n// ...\nMultiClusterPooledConnectionProvider provider = new MultiClusterPooledConnectionProvider(builder.build());\nUnifiedJedis client = new UnifiedJedis(provider);\n\n// Jedis 7.x\n// MultiClusterClientConfig was renamed to MultiDbConfig and MultiDbClient with convenient builder was added\nMultiDbConfig multiConfig = MultiDbConfig.builder()\n        .database(DatabaseConfig.builder(east, config).weight(1.0f).build())\n        .database(DatabaseConfig.builder(west, config).weight(0.5f).build())\n        .build();\n// Use MultiDbClient instead of UnifiedJedis\nMultiDbClient multiDbClient = MultiDbClient.builder().multiDbConfig(multiConfig).build();\n```\nFor more details on configuration options see sections below.\n\n## Installing optional dependencies\n\nJedis failover support is provided by optional dependencies.\nTo use failover, add the following dependencies to your project:\n```xml\n<dependency>\n    <groupId>io.github.resilience4j</groupId>\n    <artifactId>resilience4j-all</artifactId>\n    <version>1.7.1</version>\n</dependency>\n<dependency>\n    <groupId>io.github.resilience4j</groupId>\n    <artifactId>resilience4j-circuitbreaker</artifactId>\n    <version>1.7.1</version>\n</dependency>\n<dependency>\n    <groupId>io.github.resilience4j</groupId>\n    <artifactId>resilience4j-retry</artifactId>\n    <version>1.7.1</version>\n</dependency>\n```\n\n## Basic usage\n\nTo configure Jedis for failover, you specify a weighted list of Redis databases.\nJedis will connect to the Redis database in the list with the highest weight. \nIf the highest-weighted database becomes unavailable,\nJedis will attempt to connect to the database with the next highest weight in the list, and so on.\n\nDatabase weights determine the priority for selecting which database becomes active. Weights can be configured at initialization and can also be changed dynamically at runtime (introduced in version 7.4.0), allowing you to adjust active database selection priorities without recreating the client.\n\nSuppose you run two Redis deployments.\nWe'll call them `redis-east` and `redis-west`.\nYou want your application to first connect to `redis-east`.\nIf `redis-east` becomes unavailable, you want your application to connect to `redis-west`.\n\nLet's look at one way of configuring Jedis for this scenario.\n\nFirst, start by defining the initial configuration for each Redis database available and prioritize them using weights.\n\n```java\nJedisClientConfig config = DefaultJedisClientConfig.builder()\n        .user(\"cache\").password(\"secret\")\n        .socketTimeoutMillis(5000).connectionTimeoutMillis(5000).build();\n\n// Custom pool config per database can be provided\nConnectionPoolConfig poolConfig = new ConnectionPoolConfig();\npoolConfig.setMaxTotal(8);\npoolConfig.setMaxIdle(8);\npoolConfig.setMinIdle(0);\npoolConfig.setBlockWhenExhausted(true);\npoolConfig.setMaxWait(Duration.ofSeconds(1));\npoolConfig.setTestWhileIdle(true);\npoolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(1));\n\nHostAndPort east = new HostAndPort(\"redis-east.example.com\", 14000);\nHostAndPort west = new HostAndPort(\"redis-west.example.com\", 14000);\n\nMultiDbConfig.Builder multiConfig = MultiDbConfig.builder()\n        .database(DatabaseConfig.builder(east, config).connectionPoolConfig(poolConfig).weight(1.0f).build())\n        .database(DatabaseConfig.builder(west, config).connectionPoolConfig(poolConfig).weight(0.5f).build());\n```\n\nThe configuration above represents your two Redis deployments: `redis-east` and `redis-west`.\n\nContinue using the `MultiDbConfig.Builder` builder to set your preferred retry and failover configuration.\nThen build a `MultiDbClient`:\n\n```java\n// Configure circuit breaker for failure detection\nmultiConfig\n        .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n                .slidingWindowSize(1000)        // Sliding window size in number of calls\n                .failureRateThreshold(50.0f)    // percentage of failures to trigger circuit breaker\n                .minNumOfFailures(500)          // Minimum number of failures before circuit breaker is tripped\n                .build())\n        .failbackSupported(true)                // Enable failback\n        .failbackCheckInterval(1000)            // Check every second the unhealthy database to see if it has recovered\n        .gracePeriod(10000)                     // Keep database disabled for 10 seconds after it becomes unhealthy\n        // Optional: configure retry settings\n        .commandRetry(MultiDbConfig.RetryConfig.builder()\n                .maxAttempts(3)                  // Maximum number of retry attempts (including the initial call)\n                .waitDuration(500)               // Number of milliseconds to wait between retry attempts\n                .exponentialBackoffMultiplier(2) // Exponential backoff factor multiplied against wait duration between retries\n                .build())\n        // Optional: configure fast failover\n        .fastFailover(true)                       // Force closing connections to unhealthy database on failover\n        .retryOnFailover(false);                  // Do not retry failed commands during failover\n\nMultiDbClient multiDbClient = MultiDbClient.builder()\n        .multiDbConfig(multiConfig.build())\n        .build();\n```\n\nIn the configuration here, we've set a sliding window size of 1000 and a failure rate threshold of 50%.\nThis means that a failover will be triggered only if both 500 out of any 1000 calls to Redis fail (i.e., the failure rate threshold is reached) and the minimum number of failures is also met.\n\nYou can now use this `MultiDbClient` instance in your application to execute Redis commands.\n\n## Configuration options\n\nUnder the hood, Jedis' failover support relies on [resilience4j](https://resilience4j.readme.io/docs/getting-started),\na fault-tolerance library that implements [retry](https://resilience4j.readme.io/docs/retry) and [circuit breakers](https://resilience4j.readme.io/docs/circuitbreaker).\n\nOnce you configure a `MultiDbClient`, each call to Redis is decorated with a resilience4j retry and circuit breaker.\n\nBy default, any call that throws a `JedisConnectionException` will be retried up to 3 times.\nIf the call fail then the circuit breaker will record a failure.\n\nThe circuit breaker maintains a record of failures in a sliding window data structure.\nIf the failure rate reaches a configured threshold (e.g., when 50% of the last 1000 calls have failed),\nthen the circuit breaker's state transitions from `CLOSED` to `OPEN`.\nWhen this occurs, Jedis will attempt to connect to the next Redis database with the highest weight in its client configuration list.\n\nThe supported retry and circuit breaker settings, and their default values, are described below.\nYou can configure any of these settings using the `MultiDbConfig.Builder` builder.\nRefer the basic usage above for an example of this.\n\n### Retry configuration\nConfiguration for command retry behavior is encapsulated in `MultiDbConfig.RetryConfig`.\nJedis uses the following retry settings:\n\n| Setting                          | Default value              | Description                                                                                                                                                                                                     |\n|----------------------------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Max retry attempts               | 3                          | Maximum number of retry attempts (including the initial call)                                                                                                                                                   |\n| Retry wait duration              | 500 ms                     | Number of milliseconds to wait between retry attempts                                                                                                                                                           |\n| Wait duration backoff multiplier | 2                          | Exponential backoff factor multiplied against wait duration between retries. For example, with a wait duration of 1 second and a multiplier of 2, the retries would occur after 1s, 2s, 4s, 8s, 16s, and so on. |\n| Retry included exception list    | [JedisConnectionException] | A list of Throwable classes that count as failures and should be retried.                                                                                                                                       |\n| Retry ignored exception list     | null                       | A list of Throwable classes to explicitly ignore for the purposes of retry.                                                                                                                                     |\n\nTo disable retry, set `maxAttempts` to 1.\n\n### Circuit breaker configuration\nFor failover, Jedis uses a circuit breaker to detect when a Redis database has failed.\nFailover configuration is encapsulated in `MultiDbConfig.CircuitBreakerConfig` and can be provided using the `MultiDbConfig.Builder.failureDetector()`.\nJedis uses the following circuit breaker settings:\n\n| Setting                                 | Default value              | Description                                                                                                              |\n|-----------------------------------------|----------------------------|--------------------------------------------------------------------------------------------------------------------------|\n| Sliding window size                     | 2                          | The size of the sliding window. Units depend on sliding window type. The size represents seconds.                        |\n| Threshold min number of failures        | 1000                       | Minimum number of failures before circuit breaker is tripped.                                                            |\n| Failure rate threshold                  | `10.0f`                    | Percentage of calls within the sliding window that must fail before the circuit breaker transitions to the `OPEN` state. |\n| Circuit breaker included exception list | [JedisConnectionException] | A list of Throwable classes that count as failures and add to the failure rate.                                          |\n| Circuit breaker ignored exception list  | null                       | A list of Throwable classes to explicitly ignore for failure rate calculations.                                          |                                                                                                               |\n\n### Health Check Configuration and Customization\n\nThe `MultiDbClient` includes a comprehensive health check system that continuously monitors the availability of Redis databases to enable automatic failover and failback.\n\nThe health check system serves several critical purposes in the failover architecture:\n\n1. **Proactive Monitoring**: Continuously monitors passive databases that aren't currently receiving traffic\n2. **Failback Detection**: Determines when a previously failed database has recovered and is ready to accept traffic\n3. **Circuit Breaker Integration**: Works with the circuit breaker pattern to manage database state transitions\n4. **Customizable Strategies**: Supports pluggable health check implementations for different deployment scenarios\n\nThe health check system operates independently of your application traffic, running background checks at configurable intervals to assess database health without impacting performance.\n\n#### Available Health Check Types\n\n##### 1. PingStrategy (Default)\n\nThe `PingStrategy` is the default health check implementation that uses Redis's `PING` command to verify both connectivity and write capability.\n\n**Use Cases:**\n- General-purpose health checking for most Redis deployments\n- Verifying both read and write operations\n- Simple connectivity validation\n\n**How it works:**\n- Sends `PING` command to the Redis server\n- Expects exact response `\"PONG\"` to consider the server healthy\n- Any exception or unexpected response marks the server as unhealthy\n\n##### 2. LagAwareStrategy [PREVIEW] (Redis Enterprise)\n\nThe `LagAwareStrategy` is designed specifically for Redis Enterprise Active-Active deployments and uses the Redis Enterprise REST API to check database availability and replication lag.\n\n**Use Cases:**\n- Redis Enterprise Active-Active (CRDB) deployments\n- Scenarios where replication lag tolerance is critical\n- Enterprise environments with REST API access\n\n**How it works:**\n- Queries Redis Enterprise REST API for database availability\n- Optionally validates replication lag against configurable thresholds\n- Automatically discovers database IDs based on endpoint hostnames\n\n**Example Configuration:**\n```java\nBiFunction<HostAndPort, Supplier<RedisCredentials>, MultiDbConfig.StrategySupplier> healthCheckStrategySupplier =\n        (HostAndPort dbHostPort, Supplier<RedisCredentials> credentialsSupplier) -> {\n            LagAwareStrategy.Config lagConfig = LagAwareStrategy.Config.builder(dbHostPort, credentialsSupplier)\n                    .interval(5000)                                          // Check every 5 seconds\n                    .timeout(3000)                                           // 3 second timeout\n                    .extendedCheckEnabled(true)\n                    .build();\n\n            return (hostAndPort, jedisClientConfig) -> new LagAwareStrategy(lagConfig);\n        };\n\n// Configure REST API endpoint and credentials\nHostAndPort restEndpoint = new HostAndPort(\"redis-enterprise-db-fqdn\", 9443);\nSupplier<RedisCredentials> credentialsSupplier = () ->\n        new DefaultRedisCredentials(\"rest-api-user\", \"pwd\");\n\nMultiDbConfig.StrategySupplier lagawareStrategySupplier = healthCheckStrategySupplier.apply(\n        restEndpoint, credentialsSupplier);\n\nMultiDbConfig.DatabaseConfig dbConfig =\n        MultiDbConfig.DatabaseConfig.builder(hostAndPort, clientConfig)\n                .healthCheckStrategySupplier(lagawareStrategySupplier)\n                .build();\n```\n\n##### 3. Custom Health Check Strategies\n\nYou can implement custom health check strategies by implementing the `HealthCheckStrategy` interface.\n\n**Use Cases:**\n- Application-specific health validation logic\n- Integration with external monitoring systems\n- Custom performance or latency-based health checks\n\nUse the `healthCheckStrategySupplier()` method to provide a custom health check implementation:\n\n```java\n// Custom strategy supplier\nMultiDbConfig.StrategySupplier customStrategy =\n        (hostAndPort, jedisClientConfig) -> {\n            // Return your custom HealthCheckStrategy implementation\n            return new MyCustomHealthCheckStrategy(hostAndPort, jedisClientConfig);\n        };\n\nMultiDbConfig.DatabaseConfig dbConfig =\n        MultiDbConfig.DatabaseConfig.builder(hostAndPort, clientConfig)\n                .healthCheckStrategySupplier(customStrategy)\n                .weight(1.0f)\n                .build();\n```\n\nYou can implement custom health check strategies by implementing the `HealthCheckStrategy` interface:\n\n```java\nMultiDbConfig.StrategySupplier pingStrategy = (hostAndPort, jedisClientConfig) -> {\n    return new HealthCheckStrategy() {\n        @Override\n        public int getInterval() {\n            return 1000; // Check every second\n        }\n\n        @Override\n        public int getTimeout() {\n            return 500; // 500ms timeout\n        }\n\n\n        @Override\n        public int getNumProbes() {\n            return 1;\n        }\n\n        @Override\n        public ProbingPolicy getPolicy() {\n            return ProbingPolicy.BuiltIn.ANY_SUCCESS;\n        }\n\n        @Override\n        public int getDelayInBetweenProbes() {\n            return 100;\n        }\n        @Override\n        public HealthStatus doHealthCheck(Endpoint endpoint) {\n            try (UnifiedJedis jedis = new UnifiedJedis(hostAndPort, jedisClientConfig)) {\n                String result = jedis.ping();\n                return \"PONG\".equals(result) ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;\n            } catch (Exception e) {\n                return HealthStatus.UNHEALTHY;\n            }\n        }\n\n        @Override\n        public void close() {\n            // Cleanup resources if needed\n        }\n    };\n};\n\nMultiDbConfig.DatabaseConfig dbConfig =\n        MultiDbConfig.DatabaseConfig.builder(hostAndPort, clientConfig)\n                .healthCheckStrategySupplier(pingStrategy)\n                .build();\n```\n\n#### Disabling Health Checks\n\nUse the `healthCheckEnabled(false)` method to completely disable health checks:\n\n```java\nMultiDbConfig.DatabaseConfig dbConfig = MultiDbConfig.DatabaseConfig.builder(east, config)\n    .healthCheckEnabled(false) // Disable health checks entirely\n    .build();\n```\n\n### Fallback configuration\n\nJedis uses the following fallback settings:\n\n| Setting                 | Default value                                         | Description                                        |\n|-------------------------|-------------------------------------------------------|----------------------------------------------------|\n| Fallback exception list | [CallNotPermittedException, JedisConnectionException] | A list of Throwable classes that trigger fallback. |\n\n### Failover callbacks\n\nIn the event that Jedis fails over, you may wish to take some action. This might include logging a warning, recording\na metric, or externally persisting the database connection state, to name just a few examples. For this reason,\n`MultiDbClient` lets you register a custom callback that will be called whenever Jedis\nfails over to a new database.\n\nTo use this feature, you'll need to design a class that implements `java.util.function.Consumer`.\nThis class must implement the `accept` method, as you can see below.\n\n```java\npublic class FailoverReporter implements Consumer<DatabaseSwitchEvent> {\n    \n    @Override\n    public void accept(DatabaseSwitchEvent e) {\n        System.out.println(\"Jedis failover to database: \" + e.getDatabaseName() + \" due to \" + e.getReason());\n    }\n}\n```\n\nDatabaseSwitchEvent consumer can be registered as follows:\n\n```java\nFailoverReporter reporter = new FailoverReporter();\nMultiDbClient client = MultiDbClient.builder()\n        .databaseSwitchListener(reporter)\n        .build();\n```\nThe provider will call your `accept` whenever a failover occurs.\nor directly using lambda expression:\n```java\nMultiDbClient client = MultiDbClient.builder()\n        .databaseSwitchListener(event -> System.out.println(\"Switched to: \" + event.getEndpoint()))\n        .build();\n```\n\n\n## Failing back\n\nJedis supports automatic failback based on health checks or manual failback using the database selection API.\n\n## Failback scenario\n\nWhen a failover is triggered, Jedis will attempt to connect to the next Redis server based on the weights of server configurations\nyou provide at setup.\n\nFor example, recall the `redis-east` and `redis-west` deployments from the basic usage example above.\nJedis will attempt to connect to `redis-east` first.\nIf `redis-east` becomes unavailable (and the circuit breaker transitions), then Jedis will attempt to use `redis-west`.\n\nNow suppose that `redis-east` eventually comes back online.\nYou will likely want to fail your application back to `redis-east`.\n\n### Automatic failback based on health checks\n\nWhen health checks are enabled, Jedis automatically monitors the health of all configured databases, including those that are currently inactive due to previous failures. \nThe automatic failback process works as follows:\n\n1. **Continuous Monitoring**: Health checks run continuously for all databases, regardless of their current active status\n2. **Recovery Detection**: When a previously failed database passes the required number of consecutive health checks, it's marked as healthy\n3. **Weight-Based Failback**: If automatic failback is enabled and a recovered database has a higher weight than the currently active database, Jedis will automatically switch to the recovered database\n4. **Grace Period Respect**: Failback only occurs after the configured grace period has elapsed since the database was marked as unhealthy\n\n## Manual Failback using the database selection API\n\nOnce you've determined that it's safe to fail back to a previously-unavailable database,\nyou need to decide how to trigger the failback. There are two ways to accomplish this:\n\n`MultiDbClient` exposes a method that you can use to manually select which database Jedis should use.\nTo select a different database to use, pass the database's `HostAndPort` to `setActiveDatabase()`:\n```\n        Endpoint endpoint =  new HostAndPort(\"redis-east.example.com\", 14000);\n        client.setActiveDatabase(endpoint);\n```\n\nThis method is thread-safe.\n\nIf you decide to implement manual failback, you will need a way for external systems to trigger this method in your\napplication. For example, if your application exposes a REST API, you might consider creating a REST endpoint\nto call `setActiveDatabase` and fail back the application.\n\n## Dynamic Weight Management\n\n> Introduced in version 7.4.0\n\nJedis allows you to dynamically adjust database weights at runtime without recreating the `MultiDbClient`.\n\n**Important**: Weight determines the **priority for selecting which database becomes the active database**. At any given time, only ONE database is active and receives all traffic. Weight does not distribute load across databases - it determines which single database Jedis will prefer to use as the active connection.\n\nThis is useful for scenarios where you need to change the active database selection priority based on operational conditions, such as:\n\n- Changing which database should be preferred during planned maintenance\n- Adjusting selection priority based on database performance or regional preferences\n- Implementing controlled switchover between databases\n- Responding to changing infrastructure conditions\n\n### Getting and Setting Weights\n\nThe `MultiDbClient` provides methods to query and modify database weights at runtime:\n\n```java\n// Get the current weight of a database\nHostAndPort east = new HostAndPort(\"redis-east.example.com\", 14000);\nfloat currentWeight = client.getWeight(east);\nSystem.out.println(\"Current weight: \" + currentWeight);\n\n// Set a new weight for a database\nclient.setWeight(east, 2.0f);\n```\n\n### Weight Constraints\n\nWhen setting weights dynamically, the following constraints apply:\n\n- **Weight must be greater than 0**: Attempting to set a weight of 0 or negative values will throw an `IllegalArgumentException`\n- **Endpoint must exist**: The endpoint must be part of the configured databases, otherwise a `JedisValidationException` is thrown\n\n### Runtime Behavior\n\nWhen you change a database's weight at runtime:\n\n1. **Immediate Effect on Selection**: The weight change takes effect immediately for future active database selection decisions during failover or failback\n2. **Automatic Failback Trigger**: If automatic failback is enabled and you increase a database's weight above the currently active database, Jedis will automatically switch to the higher-weight database during the next periodic failback check (if the database is healthy and the grace period has elapsed)\n3. **No Disruption**: Changing weights does not interrupt ongoing operations or force an immediate switch\n4. **Single Active Database**: Remember that only one database is active at any time - all traffic goes to that single database\n\n### How Weight Affects Database Selection\n\nDuring failover or failback, Jedis selects the active database by:\n\n1. Filtering for healthy databases (passing health checks, not in grace period, circuit breaker not open)\n2. Sorting the healthy databases by weight in descending order (highest weight first)\n3. Selecting the first database from this sorted list as the active database\n\n### Example: Changing Active Database Priority\n\nHere's a practical example of dynamically adjusting weights to control which database should be active:\n\n```java\n// Initial configuration - primary has higher weight, so it will be selected as active\nHostAndPort primary = new HostAndPort(\"redis-primary.example.com\", 6379);\nHostAndPort secondary = new HostAndPort(\"redis-secondary.example.com\", 6379);\n\nMultiDbConfig config = MultiDbConfig.builder()\n        .database(DatabaseConfig.builder(primary, clientConfig).weight(2.0f).build())\n        .database(DatabaseConfig.builder(secondary, clientConfig).weight(1.0f).build())\n        .failbackSupported(true)\n        .failbackCheckInterval(1000)\n        .build();\n\nMultiDbClient client = MultiDbClient.builder()\n        .multiDbConfig(config)\n        .build();\n\n// At this point, 'primary' is the active database (weight 2.0 > 1.0)\n\n// Before planned maintenance on primary, make secondary the preferred database\nclient.setWeight(secondary, 3.0f);  // Now secondary has highest weight\n// During the next failback check, Jedis will switch to secondary as the active database\n\n// After maintenance, restore primary as the preferred database\nclient.setWeight(primary, 4.0f);  // Now primary has highest weight again\n// During the next failback check, Jedis will switch back to primary as the active database\n```\n\n### Monitoring Database Switches\n\nYou can combine weight changes with failover callbacks to monitor when the active database switches due to weight adjustments:\n\n```java\nMultiDbClient client = MultiDbClient.builder()\n        .multiDbConfig(config)\n        .databaseSwitchListener(event -> {\n            System.out.println(\"Active database switched to: \" + event.getEndpoint() +\n                             \" due to: \" + event.getReason());\n        })\n        .build();\n\n// Change weight - may trigger automatic failback to switch active database\nclient.setWeight(secondary, 5.0f);\n```\n\n## Dynamic Database Management\n\nJedis allows you to dynamically add and remove database endpoints at runtime without recreating the `MultiDbClient`. This provides flexibility for scenarios such as:\n\n- Adding new database replicas or regions as they become available\n- Removing databases during planned maintenance or decommissioning\n- Scaling your Redis infrastructure dynamically\n- Responding to infrastructure changes without application restarts\n\n### Adding Databases at Runtime\n\nThe `MultiDbClient` provides two overloaded methods for adding databases dynamically:\n\n#### Method 1: Using DatabaseConfig\n\nThis method provides maximum flexibility for advanced configurations including custom health check strategies, connection pool settings, and other database-specific options.\n\n```java\n// Create a fully configured DatabaseConfig\nHostAndPort newEndpoint = new HostAndPort(\"redis-new.example.com\", 6379);\nJedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .user(\"cache\").password(\"secret\").build();\n\nDatabaseConfig databaseConfig = DatabaseConfig.builder(newEndpoint, clientConfig)\n        .weight(1.5f).connectionPoolConfig(poolConfig).healthCheckEnabled(true).build();\n\n// Add the database to the client\nclient.addDatabase(databaseConfig);\n```\n\n#### Method 2: Using Endpoint, Weight, and ClientConfig\n\nThis is a convenience method for simpler configurations when you don't need advanced customization.\n\n```java\nHostAndPort newEndpoint = new HostAndPort(\"redis-new.example.com\", 6379);\nJedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .user(\"cache\").password(\"secret\").build();\n\n// Add the database with basic configuration\nclient.addDatabase(newEndpoint, 1.5f, clientConfig);\n```\n\n### Removing Databases at Runtime\n\nYou can remove database endpoints dynamically using the `removeDatabase()` method:\n\n```java\nHostAndPort endpointToRemove = new HostAndPort(\"redis-old.example.com\", 6379);\n\n// Remove the database from the client\nclient.removeDatabase(endpointToRemove);\n```\n\n### Behavior and Constraints\n\nWhen adding or removing databases, the following behavior applies:\n\n#### Adding Databases\n\n- **Immediate Availability**: The new endpoint becomes available for failover operations immediately after being added\n- **Health Check Integration**: If health checks are configured, the new database will be monitored according to the configured health check strategy\n- **Duplicate Prevention**: Attempting to add an endpoint that already exists will throw a `JedisValidationException`\n- **Weight-Based Selection**: The new database participates in weight-based active database selection according to its configured weight\n\n#### Removing Databases\n\n- **Automatic Failover**: If the removed endpoint is currently the active database, Jedis will automatically failover to the next available healthy endpoint based on weight priority\n- **Last Database Protection**: You cannot remove the last remaining endpoint - attempting to do so will throw a `JedisValidationException`\n- **Non-Existent Endpoint**: Attempting to remove an endpoint that doesn't exist will throw a `JedisValidationException`\n- **Resource Cleanup**: The removed database's connections and resources are properly closed and cleaned up\n- **Health Check Cleanup**: Health checks for the removed database are automatically stopped and unregistered\n\n### Querying Configured Databases\n\nYou can retrieve the set of all currently configured database endpoints:\n\n```java\nSet<Endpoint> endpoints = client.getDatabaseEndpoints();\nSystem.out.println(\"Configured databases: \" + endpoints);\n```\n\n### Complete Example: Dynamic Database Management\n\nHere's a practical example demonstrating dynamic database management:\n\n```java\n// Initial setup with two databases, primary and secondary.\nHostAndPort primary = new HostAndPort(\"redis-primary.example.com\", 6379);\nHostAndPort secondary = new HostAndPort(\"redis-secondary.example.com\", 6379);\n\nJedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .user(\"cache\").password(\"secret\").build();\n\nMultiDbConfig config = MultiDbConfig.builder()\n        .database(DatabaseConfig.builder(primary, clientConfig).weight(2.0f).build())\n        .database(DatabaseConfig.builder(secondary, clientConfig).weight(1.0f).build())\n        .failbackSupported(true)\n        .build();\n\nMultiDbClient client = MultiDbClient.builder()\n        .multiDbConfig(config)\n        .databaseSwitchListener(event -> {\n            System.out.println(\"Switched to: \" + event.getEndpoint() +\n                             \" due to: \" + event.getReason());\n        })\n        .build();\n\n// Add a new database in a different region\nHostAndPort newRegion = new HostAndPort(\"redis-eu.example.com\", 6379);\nclient.addDatabase(newRegion, 1.0f, clientConfig);\nSystem.out.println(\"Added new database: \" + newRegion);\n\n// Verify the database was added\nSet<Endpoint> endpoints = client.getDatabaseEndpoints();\nSystem.out.println(\"Current databases: \" + endpoints);\n// Output: [redis-primary.example.com:6379, redis-secondary.example.com:6379, redis-eu.example.com:6379]\n\n// Later, remove the secondary database for maintenance\nclient.removeDatabase(secondary);\nSystem.out.println(\"Removed database: \" + secondary);\n// If secondary was active, automatic failover occurs to primary or newRegion\n\n// Verify the database was removed\nendpoints = client.getDatabaseEndpoints();\nSystem.out.println(\"Current databases: \" + endpoints);\n// Output: [redis-primary.example.com:6379, redis-eu.example.com:6379]\n\n```\n\n### Thread Safety\n\nBoth `addDatabase()` and `removeDatabase()` methods are thread-safe and can be called concurrently from multiple threads. The client ensures that database additions and removals are properly synchronized with ongoing operations.\n\n## Troubleshooting Failover and Failback Issues\n\n#### Health Checks Always Report Unhealthy\n\n**Common causes:**\n- Timeout too aggressive for network conditions\n- Authentication issues with Redis server\n- Network connectivity problems\n\n**Solutions:**\n```java\n// Increase timeout values\nHealthCheckStrategy.Config config = HealthCheckStrategy.Config.builder()\n    .timeout(3000)  // Increase from default 1000ms\n    .build();\n```\n\n#### Intermittent Health Check Failures\n\n**Solutions:**\n```java\n// Require more consecutive successes for stability\nHealthCheckStrategy.Config config = HealthCheckStrategy.Config.builder()\n    .interval(5000)                 // Less frequent checks\n    .timeout(2000)                  // More generous timeout\n    .build();\n```\n\n#### Slow Failback After Recovery\n\n**Solutions:**\n```java\n// Faster recovery configuration\nHealthCheckStrategy.Config config = HealthCheckStrategy.Config.builder()\n    .interval(1000)                    // More frequent checks\n    .build();\n\n// Adjust failback timing\nMultiDbConfig multiConfig = MultiDbConfig.builder()\n        .gracePeriod(5000)                 // Shorter grace period\n        .build();\n```\n\n## Need help or have questions?\nFor assistance with this automatic failover and failback feature,\n[start a discussion](https://github.com/redis/jedis/discussions/new?category=q-a).\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# Frequently Asked Questions\n\n## If you get `java.net.SocketTimeoutException: Read timed out` exception\n\nTry setting own `timeout` value when constructing `JedisPool` using the following constructor:\n```java\nJedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout)\n```\nwhere `timeout` is given as milliseconds.\n\nDefault `timeout` value is **2 seconds**.\n\n## JedisPool blocks after getting 8 connections\n\nJedisPool defaults to 8 connections, you can change this in the PoolConfig:\n\n```java\nJedisPoolConfig poolConfig = new JedisPoolConfig();\npoolConfig.setMaxTotal(maxTotal); // maximum active connections\npoolConfig.setMaxIdle(maxIdle);  // maximum idle connections\n```\n\nTake into account that `JedisPool` inherits commons-pool [BaseObjectPoolConfig](https://commons.apache.org/proper/commons-pool/api-2.3/org/apache/commons/pool2/impl/BaseObjectPoolConfig.html) which has a lot of configuration parameters. \nWe've set some defined ones which suit most of the cases. In case, you experience [issues](https://github.com/xetorthio/jedis/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+JedisPool) tuning these parameters may help.\n\n## How to configure the buffer size of socket(s)\n\nThe buffer size of all Jedis sockets in an application can be configured through system property.\n\nBuffer size of input stream can be configured by setting `jedis.bufferSize.input` or `jedis.bufferSize` system property.  \nBuffer size of output stream can be configured by setting `jedis.bufferSize.output` or `jedis.bufferSize` system property.  \nIf you want to set the buffer size of both input and output stream to same value, you can just set `jedis.bufferSize`.\n\nNote: This feature is available since Jedis 4.2.0.\n\n## How to avoid cluster initialization error\n\nAs of Jedis 4.0.0, a `JedisClusterOperationException` is raised with the message `Could not initialize cluster slots cache.` when the cluster initialization process fails. \n\nShould you would want to avoid this error (for example, creating `JedisConnectionFactory` to an unavailable cluster for a spring-data-redis `Bean`), set the system property `jedis.cluster.initNoError` to any value.  \nIn the console, add the option `-Djedis.cluster.initNoError`.\nIn an application, `System.setProperty(\"jedis.cluster.initNoError\", \"\");` can be set before creating any cluster object.\n\nNote: This feature is available since Jedis 4.4.2."
  },
  {
    "path": "docs/index.md",
    "content": "{% include 'README.md' %}"
  },
  {
    "path": "docs/jedis-maven.md",
    "content": "## Use Jedis as a maven dependency:\n\n### Official Releases\n\n```xml\n<dependency>\n    <groupId>redis.clients</groupId>\n    <artifactId>jedis</artifactId>\n    <version>6.2.0</version>\n</dependency>\n```\n\n### Snapshots\n\n```xml\n<repositories>\n    <repository>\n        <id>sonatype-snapshots</id>\n        <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n        <releases>\n            <enabled>false</enabled>\n        </releases>\n        <snapshots>\n            <enabled>true</enabled>\n        </snapshots>\n    </repository>\n</repositories>\n```\n\nand\n\n```xml\n<dependencies>\n    <dependency>\n        <groupId>redis.clients</groupId>\n        <artifactId>jedis</artifactId>\n        <version>7.0.0-SNAPSHOT</version>\n    </dependency>\n</dependencies>\n```\n"
  },
  {
    "path": "docs/migration-guides/v3-to-v4-primitives.md",
    "content": "## The following methods now return primitive values:\n\\>> `long`/`boolean`/`double` instead of `Long`/`Boolean`/`Double`:\n\n- dbSize()\n- lastsave()\n- slowlogLen()\n- clientId()\n- clientUnblock(long clientId, UnblockType unblockType)\n- clientKill(ClientKillParams params)\n- aclDelUser(String name)\n- aclDelUser(byte[] name)\n- move(String key, int dbIndex)\n- move(byte[] key, int dbIndex)\n- waitReplicas(int replicas, long timeout)\n- waitReplicas(String key, int replicas, long timeout)\n- exists(String key)\n- exists(byte[] key)\n- exists(String... keys)\n- exists(byte[]... keys)\n- persist(String key)\n- expire(String key, long seconds)\n- expire(byte[] key, long seconds)\n- pexpire(String key, long milliseconds)\n- pexpire(byte[] key, long milliseconds)\n- expireAt(String key, long unixTime)\n- expireAt(byte[] key, long unixTime)\n- pexpireAt(String key, long millisecondsTimestamp)\n- pexpireAt(byte[] key, long millisecondsTimestamp)\n- ttl(String key)\n- ttl(byte[] key)\n- pttl(String key)\n- touch(String key)\n- touch(byte[] key)\n- touch(String... keys)\n- touch(byte[]... keys)\n- setbit(String key, long offset, boolean value)\n- setbit(byte[] key, long offset, boolean value)\n- getbit(String key, long offset)\n- getbit(byte[] key, long offset)\n- setrange(String key, long offset, String value)\n- setrange(byte[] key, long offset, byte[] value)\n- setnx(String key, String value)\n- setnx(byte[] key, byte[] value)\n- incr(String key)\n- incr(byte[] key)\n- decr(String key)\n- decr(byte[] key)\n- incrBy(String key, long increment)\n- incrBy(byte[] key, long increment)\n- decrBy(String key, long decrement)\n- decrBy(byte[] key, long decrement)\n- incrByFloat(String key, double increment)\n- incrByFloat(byte[] key, double increment)\n- append(String key, String value)\n- append(byte[] key, byte[] value)\n- hset(String key, String field, String value)\n- hset(byte[] key, byte[] field, byte[] value)\n- hset(String key, Map<String, String> hash)\n- hset(byte[] key, Map<byte[], byte[]> hash)\n- hsetnx(String key, String field, String value)\n- hsetnx(byte[] key, byte[] field, byte[] value)\n- hincrBy(String key, String field, long value)\n- hincrBy(byte[] key, byte[] field, long value)\n- hincrByFloat(String key, String field, double value)\n- hincrByFloat(byte[] key, byte[] field, double value)\n- hexists(String key, String field)\n- hexists(byte[] key, byte[] field)\n- hdel(String key, String... field)\n- hdel(byte[] key, byte[]... field)\n- hlen(String key)\n- hlen(byte[] key)\n- rpush(String key, String... string)\n- rpush(byte[] key, byte[]... args)\n- lpush(String key, String... string)\n- lpush(byte[] key, byte[]... args)\n- llen(String key)\n- llen(byte[] key)\n- lrem(String key, long count, String value)\n- lrem(byte[] key, long count, byte[] value)\n- sadd(String key, String... member)\n- sadd(byte[] key, byte[]... member)\n- scard(String key)\n- scard(byte[] key)\n- sismember(String key, String member)\n- sismember(byte[] key, byte[] member)\n- strlen(String key)\n- strlen(byte[] key)\n- zadd(String key, double score, String member)\n- zadd(byte[] key, double score, byte[] member)\n- zadd(String key, double score, String member, ZAddParams params)\n- zadd(byte[] key, double score, byte[] member, ZAddParams params)\n- zadd(String key, Map<String, Double> scoreMembers)\n- zadd(byte[] key, Map<byte[], Double> scoreMembers)\n- zadd(String key, Map<String, Double> scoreMembers, ZAddParams params)\n- zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params)\n- zrem(String key, String... members)\n- zrem(byte[] key, byte[]... members)\n- zincrby(String key, double increment, String member)\n- zincrby(byte[] key, double increment, byte[] member)\n- zcard(String key)\n- zcard(byte[] key)\n- zcount(String key, double min, double max)\n- zcount(byte[] key, double min, double max)\n- zcount(String key, String min, String max)\n- zcount(byte[] key, byte[] min, byte[] max)\n- zremrangeByRank(String key, long start, long stop)\n- zremrangeByRank(byte[] key, long start, long stop)\n- zremrangeByScore(String key, double min, double max)\n- zremrangeByScore(byte[] key, double min, double max)\n- zremrangeByScore(String key, String min, String max)\n- zremrangeByScore(byte[] key, byte[] min, byte[] max)\n- zlexcount(String key, String min, String max)\n- zlexcount(byte[] key, byte[] min, byte[] max)\n- zremrangeByLex(String key, String min, String max)\n- zremrangeByLex(byte[] key, byte[] min, byte[] max)\n- linsert(String key, ListPosition where, String pivot, String value)\n- linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value)\n- lpushx(String key, String... string)\n- lpushx(byte[] key, byte[]... arg)\n- rpushx(String key, String... string)\n- rpushx(byte[] key, byte[]... arg)\n- del(String key)\n- del(byte[] key)\n- del(String... keys)\n- unlink(String key)\n- unlink(byte[] key)\n- unlink(String... keys)\n- bitcount(String key)\n- bitcount(byte[] key)\n- bitcount(String key, long start, long end)\n- bitcount(byte[] key, long start, long end)\n- bitpos(String key, boolean value)\n- bitpos(String key, boolean value, BitPosParams params)\n- pfadd(String key, String... elements)\n- pfadd(byte[] key, byte[]... elements)\n- pfcount(byte[]... keys)\n- geoadd(String key, double longitude, double latitude, String member)\n- geoadd(byte[] key, double longitude, double latitude, byte[] member)\n- geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap)\n- geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap)\n- geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap)\n- geoadd(byte[] key, GeoAddParams params, Map<byte[], GeoCoordinate> memberCoordinateMap)\n- hstrlen(String key, String field)\n- hstrlen(byte[] key, byte[] field)\n- xlen(String key)\n- xlen(byte[] key)\n- xack(String key, String group, StreamEntryID... ids)\n- xack(byte[] key, byte[] group, byte[]... ids)\n- xgroupDestroy(String key, String groupname)\n- xgroupDestroy(byte[] key, byte[] consumer)\n- xgroupDelConsumer( String key, String groupname, String consumername)\n- xgroupDelConsumer(byte[] key, byte[] consumer, byte[] consumerName)\n- xdel(String key, StreamEntryID... ids)\n- xdel(byte[] key, byte[]... ids)\n- xtrim(String key, long maxLen, boolean approximate)\n- xtrim(byte[] key, long maxLen, boolean approximateLength)\n- xtrim(String key, XTrimParams params)\n- xtrim(byte[] key, XTrimParams params)\n- clusterKeySlot(String key)\n- clusterCountKeysInSlot(int slot)\n- msetnx(String... keysvalues)\n- msetnx(byte[]... keysvalues)\n- renamenx(String oldkey, String newkey)\n- renamenx(byte[] oldkey, byte[] newkey)\n- sdiffstore(String dstkey, String... keys)\n- sdiffstore(byte[] dstkey, byte[]... keys)\n- sinterstore(String dstkey, String... keys)\n- sinterstore(byte[] dstkey, byte[]... keys)\n- smove(String srckey, String dstkey, String member)\n- smove(byte[] srckey, byte[] dstkey, byte[] member)\n- sort(String key, String dstkey)\n- sort(byte[] key, byte[] dstkey)\n- sort(String key, SortingParams sortingParameters, String dstkey)\n- sort(byte[] key, SortingParams sortingParameters, byte[] dstkey)\n- sunionstore(String dstkey, String... keys)\n- sunionstore(byte[] dstkey, byte[]... keys)\n- zdiffStore(String dstkey, String... keys)\n- zdiffStore(byte[] dstkey, byte[]... keys)\n- zinterstore(String dstkey, String... sets)\n- zinterstore(byte[] dstkey, byte[]... sets)\n- zinterstore(String dstkey, ZParams params, String... sets)\n- zinterstore(byte[] dstkey, ZParams params, byte[]... sets)\n- zunionstore(String dstkey, String... sets)\n- zunionstore(byte[] dstkey, byte[]... sets)\n- zunionstore(String dstkey, ZParams params, String... sets)\n- zunionstore(byte[] dstkey, ZParams params, byte[]... sets)\n- bitop(BitOP op, String destKey, String... srcKeys)\n- bitop(BitOP op, byte[] destKey, byte[]... srcKeys)\n- georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)\n- georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)\n- georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)\n- georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam)\n- copy(String srcKey, String dstKey, int db, boolean replace)\n- copy(byte[] srcKey, byte[] dstKey, int db, boolean replace)\n- copy(String srcKey, String dstKey, boolean replace)\n- copy(byte[] srcKey, byte[] dstKey, boolean replace)\n"
  },
  {
    "path": "docs/migration-guides/v3-to-v4-zset-list.md",
    "content": "## Each of the following sorted set methods now return a Java `List` instead of a `Set`:\n\n- zrange(byte[] key, long start, long stop)\n- zrange(String key, long start, long stop)\n- zrevrange(byte[] key, long start, long stop)\n- zrevrange(String key, long start, long stop)\n- zrangeWithScores(byte[] key, long start, long stop)\n- zrangeWithScores(String key, long start, long stop)\n- zrevrangeWithScores(byte[] key, long start, long stop)\n- zrevrangeWithScores(String key, long start, long stop)\n- zrandmember(byte[] key, long count)\n- zrandmember(String key, long count)\n- zrandmemberWithScores(byte[] key, long count)\n- zrandmemberWithScores(String key, long count)\n- zpopmax(byte[] key, int count)\n- zpopmax(String key, int count)\n- zpopmin(byte[] key, int count)\n- zpopmin(String key, int count)\n- zrangeByScore(byte[] key, double min, double max)\n- zrangeByScore(String key, double min, double max)\n- zrangeByScore(byte[] key, byte[] min, byte[] max)\n- zrangeByScore(String key, String min, String max)\n- zrevrangeByScore(byte[] key, double max, double min)\n- zrevrangeByScore(String key, double max, double min)\n- zrangeByScore(byte[] key, double min, double max, int offset, int count)\n- zrangeByScore(String key, double min, double max, int offset, int count)\n- zrevrangeByScore(byte[] key, byte[] max, byte[] min)\n- zrevrangeByScore(String key, String max, String min)\n- zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count)\n- zrangeByScore(String key, String min, String max, int offset, int count)\n- zrevrangeByScore(byte[] key, double max, double min, int offset, int count)\n- zrevrangeByScore(String key, double max, double min, int offset, int count)\n- zrangeByScoreWithScores(byte[] key, double min, double max)\n- zrangeByScoreWithScores(String key, double min, double max)\n- zrevrangeByScoreWithScores(byte[] key, double max, double min)\n- zrevrangeByScoreWithScores(String key, double max, double min)\n- zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count)\n- zrangeByScoreWithScores(String key, double min, double max, int offset, int count)\n- zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count)\n- zrevrangeByScore(String key, String max, String min, int offset, int count)\n- zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max)\n- zrangeByScoreWithScores(String key, String min, String max)\n- zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min)\n- zrevrangeByScoreWithScores(String key, String max, String min)\n- zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count)\n- zrangeByScoreWithScores(String key, String min, String max, int offset, int count)\n- zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count)\n- zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count)\n- zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count)\n- zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count)\n- zrangeByLex(byte[] key, byte[] min, byte[] max)\n- zrangeByLex(String key, String min, String max)\n- zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count)\n- zrangeByLex(String key, String min, String max, int offset, int count)\n- zrevrangeByLex(byte[] key, byte[] max, byte[] min)\n- zrevrangeByLex(String key, String max, String min)\n- zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count)\n- zrevrangeByLex(String key, String max, String min, int offset, int count)\n"
  },
  {
    "path": "docs/migration-guides/v3-to-v4.md",
    "content": "# Jedis 4 Breaking Changes\n\n- The `BinaryJedis` and `BinaryJedisCluster` classes have been removed.\n\n  The methods from these classes are available in the `Jedis` and `JedisCluster` classes\n  respectively.\n\n- The following cases now throws an `IllegalStateException` instead of a\n`JedisDataException`.\n  - `Cannot use Jedis when in Multi. Please use Transaction or reset Jedis state.`\n  - `Cannot use Jedis when in Pipeline. Please use Pipeline or reset Jedis state.`\n\n- The Redis transaction methods `multi()`, `exec()` and `discard()` have\n  been removed from `Pipeline`.\n\n- The `execGetResponse()` method has been removed from `Transaction`.\n\n- The `watch()` and `unwatch()` methods from the `Transaction` class are unsupported\nwithin MULTI (i.e., after the `multi()` method). However, `watch()` and `unwatch\n()` can still be used before calling MULTI.\n\n- The `JedisCluster` constructors with `GenericObjectPoolConfig<Jedis>` now accept\n  `GenericObjectPoolConfig<Connection>`.\n\n- All `JedisCluster` constructors now throw a `JedisClusterOperationException` if\nunable to connect to any of the provided `HostAndPort(s)`. Previously, the\nconnection would go into an unusable state.\n\n- `JedisCluster.getClusterNodes()` returns `Map<String, ConnectionPool>` instead of\n  `Map<String, JedisPool>`.\n\n- `JedisCluster.getConnectionFromSlot(int)` returns `Connection` instead of `Jedis`.\n\n- `JedisNoReachableClusterNodeException` has been removed.\n`JedisClusterOperationException`, with a similar message, is thrown instead.\n\n- `JedisClusterMaxAttemptsException` has been removed.\n`JedisClusterOperationException`, with a similar message, is thrown instead.\n\n- `JedisExhaustedPoolException` has been removed. A `JedisException` with a similar message is thrown\n  instead.\n\n- [Many sorted set methods](v3-to-v4-zset-list.md) return a Java `List` instead of a\n`Set`. [See the complete list](v3-to-v4-zset-list.md).\n\n- [Many methods return primitive values](v3-to-v4-primitives.md)) (`long`/`boolean`/`double` instead of\n`Long`/`Boolean`/\n  `Double`). [See the complete list](v3-to-v4-primitives.md).\n\n- `scriptExists(byte[])` method now returns `Boolean` instead of `Long`.\n\n- `scriptExists(byte[]...)` method now returns `List<Boolean>` instead of `List<Long>`.\n\n- In the [`xadd`](https://redis.io/commands/XADD) method with StreamEntryID\nparameter, sending untyped `null` raises an exception.\n\n  Casting the `null` to StreamEntryID (`(StreamEntryID) null`) resolves this issue.\n\n- In the [`xrange`](https://redis.io/commands/XRANGE) and\n  [`xrevrange`](https://redis.io/commands/xrevrange) methods with StreamEntryID parameters, sending\n  untyped `null`s for both start and end parameters raises an exception.\n\n  Casting the `null`s to StreamEntryID (`(StreamEntryID) null`) resolves this issue.\n\n- The return type of `Jedis.shutdown()` is now `void`. Previously, it would return null.\n\n- The `eval` and `evalsha` methods are now non-blocking. These methods were\nblocking in Jedis 3.x.\n\n- The `HostAndPort.localhost` constant has been removed.\n\n- The following methods have been removed from HostAndPort class:\n  - `extractParts()`\n  - `parseString()`\n  - `convertHost()`\n  - `setLocalhost()`\n  - `getLocalhost()`\n  - `getLocalHostQuietly()`\n\n- The following classes have been moved to the `redis.clients.jedis.args` package.\n  - `BitOP`\n  - `GeoUnit`\n  - `ListPosition`\n\n- The following classes have been moved to the `redis.clients.jedis.params` package.\n  - `BitPosParams`\n  - `ScanParams`\n  - `SortingParams`\n  - `ZParams`\n\n- The following classes have been moved to the `redis.clients.jedis.resps` package.\n  - `AccessControlLogEntry`\n  - `AccessControlUser`\n  - `GeoRadiusResponse`\n  - `ScanResult`\n  - `Slowlog`\n  - `StreamConsumersInfo`\n  - `StreamEntry`\n  - `StreamGroupInfo`\n  - `StreamInfo`\n  - `StreamPendingEntry`\n  - `StreamPendingSummary`\n  - `Tuple`\n\n- Jedis and JedisPool constructors with a `String` parameter, and no `int`\nparameter, only support a URL or URI string.\n  - Jedis(String)\n  - JedisPool(String)\n  - JedisPool(String, SSLSocketFactory, SSLParameters, HostnameVerifier)\n  - JedisPool(GenericObjectPoolConfig<Jedis>, String)\n\n- The `Client` and `BinaryClient` classes have been removed.\n\n- `redis.clients.jedis.commands` package has been reimplemented, meaning that the\n`Commands` interfaces have been restructured.\n\n- The `ShardedJedisPool`, `Sharded`, `ShardedJedis`, `BinaryShardedJedis`, `ShardInfo`,\n`JedisShardInfo` classes have been removed.\n  - Introduced `JedisSharding` class to replace `ShardedJedisPool`.\n\n    Earlier code without the use of \"name\" and \"weight\" (in ShardInfo/JedisShardInfo) are\n    transferable to the new class.\n\n- `ShardedJedisPipeline` class has been removed.\n  - Introduced `ShardedPipeline` class to replace `ShardedJedisPipeline`.\n\n- The type of `Protocol.CHARSET` has been changed to `java.nio.charset.Charset`.\n\n- `Jedis.debug(DebugParams)` method has been removed.\n\n- The `DebugParams` class has been removed.\n\n- The `Jedis.sync()` method has been removed.\n\n- The `Jedis.pubsubNumSub(String...)` method now returns `Map<String, Long>`\ninstead of `Map<String, String>`.\n\n- `setDataSource` method in Jedis class now has `protected` access.\n\n- `JedisPoolAbstract` class has been removed. Use `Pool<Jedis>`.\n\n- The `Pool.initPool()` method has been removed.\n\n- The `Pool.getNumActive()` method now returns `0` (via GenericObjectPool) when the\npool is closed.\n\n- The `Connection.getRawObjectMultiBulkReply()` method has been removed. Use\n  `Connection.getUnflushedObjectMultiBulkReply()` method.\n\n- The `Queable.getResponse(Builder<T> builder)` method has been renamed to\n  `Queable.enqueResponse(Builder<T> builder)`.\n\n- All methods in Queable are now `final`:\n  - `clean()`\n  - `generateResponse(Object data)`\n  - `enqueResponse(Builder<T> builder)`\n  - `getPipelinedResponseLength()`\n\n- These BuilderFactory implementations have been removed:\n  - `OBJECT` (use `RAW_OBJECT`)\n  - `BYTE_ARRAY_ZSET` (use `BINARY_LIST` or `BINARY_SET`)\n  - `BYTE_ARRAY_MAP` (use `BINARY_MAP`)\n  - `STRING_ZSET` (use `STRING_LIST` or `STRING_SET`)\n  - `EVAL_RESULT` (use `ENCODED_OBJECT`)\n  - `EVAL_BINARY_RESULT` (use `RAW_OBJECT`)\n\n- All String variables representing Cluster, Sentinel and PubSub subcommands in Protocol class\n  have been removed.\n\n- `ClientKillParams.Type` has been removed. Use `ClientType`.\n\n- `ClusterReset` has been removed. Use `ClusterResetType`.\n\n- The `JedisClusterHostAndPortMap` interface has been removed. Use the\n`HostAndPortMapper` interface.\n\n- `JedisClusterHashTagUtil` class has been renamed to `JedisClusterHashTag`.\n\n- The `KeyMergeUtil` class has been removed.\n"
  },
  {
    "path": "docs/migration-guides/v4-to-v5.md",
    "content": "# Jedis 5 Breaking Changes\n\n- All variants of `blmpop` and `bzmpop` methods now take `double timeout` parameter instead of `long timeout` parameter.\n  This is breaking ONLY IF you are using `Long` for timeout.\n\n- `Reducer` abstract class is refactored:\n  - **`Reducer(String field)` constructor is removed; `Reducer(String name, String field)` constructor is added.**\n  - **`Reducer(String name)` constructor is added; it will cause runtime error with older `Reducer(String field)` constructor.**\n  - `getName` method is removed.\n  - `getAlias` method is removed.\n  - `setAlias` method is removed; use `as` method.\n  - `setAliasAsField` method is removed.\n  - `getOwnArgs` method is now abstract.\n  - `getArgs` method is removed.\n\n- `quit()` method has been removed from `Connection` and `ServerCommands` interface and implementations.\n\n- `updatePassword(String password)` method has been removed from `JedisClientConfig` and implementations.\n\n- `setPassword(String password)` method has been removed from both `JedisFactory` and `ConnectionFactory` classes.\n\n- Both `bzpopmax(double timeout, String... keys)` and `bzpopmin(double timeout, String... keys)` now return `KeyValue<String, Tuple>` (instead of `KeyedZSetElement`).\n\n- Both `bzpopmax(double timeout, byte[]... keys)` and `bzpopmin(double timeout, byte[]... keys)` now return `KeyValue<byte[], Tuple>` (instead of `List<byte[]>`).\n\n- Following methods now return `KeyValue<String, String>` instead of `KeyedListElement`:\n  - `blpop(double timeout, String key)`\n  - `blpop(double timeout, String... keys)`\n  - `brpop(double timeout, String key)`\n  - `brpop(double timeout, String... keys)`\n\n- Following methods now return `KeyValue<byte[], byte[]>` instead of `List<byte[]>`:\n  - `blpop(double timeout, byte[]... keys)`\n  - `brpop(double timeout, byte[]... keys)`\n\n- `zdiff(String... keys)` method now returns `List<String>` (instead of `Set<String>`).\n- `zdiff(byte[]... keys)` method now returns `List<byte[]>` (instead of `Set<byte[]>`).\n- Both `zdiffWithScores(String... keys)` and `zdiffWithScores(byte[]... keys)` methods now return `List<Tuple>` (instead of `Set<Tuple>`).\n\n- `zinter(ZParams params, String... keys)` method now returns `List<String>` (instead of `Set<String>`).\n- `zinter(ZParams params, byte[]... keys)` method now returns `List<byte[]>` (instead of `Set<byte[]>`).\n- Both `zinterWithScores(ZParams params, String... keys)` and `zinterWithScores(ZParams params, byte[]... keys)` methods now return `List<Tuple>` (instead of `Set<Tuple>`).\n\n- `zunion(ZParams params, String... keys)` method now returns `List<String>` (instead of `Set<String>`).\n- `zunion(ZParams params, byte[]... keys)` method now returns `List<byte[]>` (instead of `Set<byte[]>`).\n- Both `zunionWithScores(ZParams params, String... keys)` and `zunionWithScores(ZParams params, byte[]... keys)` methods now return `List<Tuple>` (instead of `Set<Tuple>`).\n\n- Both `configGet(String pattern)` and `configGet(String... patterns)` methods now return `Map<String, String>` instead of `List<String>`.\n- Both `configGet(byte[] pattern)` and `configGet(byte[]... patterns)` methods now return `Map<byte[], byte[]>` instead of `List<byte[]>`.\n\n- New `aclDelUser(String... names)` method replaces `aclDelUser(String name)` and `aclDelUser(String name, String... names)` methods.\n- New `aclDelUser(byte[]... names)` method replaces `aclDelUser(byte[] name)` and `aclDelUser(byte[] name, byte[]... names)` methods.\n\n- `tsMGet(TSMGetParams multiGetParams, String... filters)` method now returns `Map<String, TSMGetElement>` instead of `List<TSKeyValue<TSElement>>`.\n\n- Following methods now return `Map<String, TSMRangeElements>` instead of `List<TSKeyedElements>`:\n  - `tsMRange(long fromTimestamp, long toTimestamp, String... filters)`\n  - `tsMRange(TSMRangeParams multiRangeParams)`\n  - `tsMRevRange(long fromTimestamp, long toTimestamp, String... filters)`\n  - `tsMRevRange(TSMRangeParams multiRangeParams)`\n\n- `jsonNumIncrBy(String key, Path2 path, double value)` method now returns `Object` instead of `JSONArray`.\n  - The returning object would still be JSONArray for all previous cases. So simple type casting is enough to handle this change.\n  - The returning object will be `List<Double>` when running under RESP3 protocol.\n\n- `getAgeSeconds()` in `AccessControlLogEntry` now returns `Double` instead of `String`.\n\n- Both `ftConfigGet(String option)` and `ftConfigGet(String indexName, String option)` methods now return `Map<String, Object>` instead of `Map<String, String>`.\n\n- `ftList()` method now returns `Set<String>` instead of `List<String>`.\n\n- `graphSlowlog(String graphName)` now returns `List<List<Object>>` (instead of `List<List<String>>`).\n\n- `CommandListFilterByParams` now throws `IllegalArgumentException` (instead of `JedisDataException`) in case of unfulfilling filter.\n\n- `FailoverParams` now throws `IllegalArgumentException` (instead of `IllegalStateException`) in case of unfulfilling optional arguments.\n\n- `XPendingParams` now throws `IllegalArgumentException` (instead of `IllegalStateException`) in case of unfulfilling optional arguments.\n\n- `get()` option has been removed from `SetParams`.  Following methods have been added in Jedis/UnifiedJedis for convenience:\n  - `setGet(String key, String value)` method has been added in `StringCommands` interface.\n  - `setGet(byte[] key, byte[] value)` method has been added in `StringBinaryCommands` interface.\n\n- `xpending(String key, String groupName, StreamEntryID start, StreamEntryID end, int count, String consumerName)` method has been removed from everywhere.\n  - Use `xpending(java.lang.String, java.lang.String, redis.clients.jedis.params.XPendingParams)` instead.\n\n- `xpending(byte[] key, byte[] groupName, byte[] start, byte[] end, int count, byte[] consumerName)` method has been removed from everywhere.\n  - Use `xpending(byte[], byte[], redis.clients.jedis.params.XPendingParams)` instead.\n\n- `retentionTime(long retentionTime)` method in `TSAlterParams` has been removed. Use `retention(long)` method instead.\n\n- Following classes have been removed:\n  - `KeyedZSetElement`\n  - `KeyedListElement`\n  - `TSKeyValue`\n  - `TSKeyedElements`\n  - `Limit`\n\n- Following BuilderFactory implementations have been removed:\n  - `BYTE_ARRAY` (use `BINARY`)\n  - `BYTE_ARRAY_LIST` (use `BINARY_LIST`)\n  - `BINARY_MAP_FROM_PAIRS`\n  - `STRING_ORDERED_SET`\n\n- All _payload_ related parameters are removed from _search_ related classes; namely `Document`, `IndexDefinition`, `Query`.\n\n- `topkCount(String key, String... items)` method has been removed from everywhere.\n\n- Following methods supporting JSON.RESP command have been removed:\n  - `jsonResp(String key)`\n  - `jsonResp(String key, Path path)`\n  - `jsonResp(String key, Path2 path)`\n\n- `RedisJsonCommands` and `RedisJsonPipelineCommands` interfaces have been moved into `redis.clients.jedis.json.commands` package.\n\n- `AbortedTransactionException` is removed.\n\n- `Queable` class is removed.\n\n- `Params` abstract class is removed.\n  - `toString()` support used by its sub-classes is now unavailable.\n\n- `getParams()` method is removed from `SortingParams` class.\n\n- Both `SEARCH_AGGREGATION_RESULT` and `SEARCH_AGGREGATION_RESULT_WITH_CURSOR` implementations from `SearchBuilderFactory` class have been moved to `AggregationResult` class.\n\n- All `AggregationResult` constructors have been made `private`.\n\n- `getArgs()`, `getArgsString()` and `serializeRedisArgs(List<byte[]> redisArgs)` methods have been removed from `AggregationBuilder`.\n\n- `totalResults` variable in `AggregationResult` has been made private. Use `getTotalResults()` method instead.\n\n- `getArgs()` and `limit(Limit limit)` methods have been removed from `Group` class.\n\n- `addCommandEncodedArguments` and `addCommandBinaryArguments` methods have been removed from `FieldName` class.\n\n- `addObjects(int[] ints)` method has been removed from `CommandArguments`.\n\n- Following methods have been removed:\n  - `strAlgoLCSStrings(String strA, String strB, StrAlgoLCSParams params)`\n  - `strAlgoLCSStrings(byte[] strA, byte[] strB, StrAlgoLCSParams params)`\n  - `strAlgoLCSKeys(String keyA, String keyB, StrAlgoLCSParams params)`\n  - `strAlgoLCSKeys(byte[] keyA, byte[] keyB, StrAlgoLCSParams params)`\n\n- `StrAlgoLCSParams` class has been removed.\n\n- Following methods have been removed from all Pipeline classes:\n  - `ftCursorRead(String indexName, long cursorId, int count)`\n  - `ftCursorDel(String indexName, long cursorId)`\n  - `ftDropIndex(String indexName)`\n  - `ftDropIndexDD(String indexName)`\n  - `ftAliasAdd(String aliasName, String indexName)`\n  - `ftAliasUpdate(String aliasName, String indexName)`\n  - `ftAliasDel(String aliasName)`\n\n- `JedisSentineled(String masterName, Set<HostAndPort> sentinels, JedisClientConfig masterClientConfig, JedisClientConfig sentinelClientConfig)` and\n`JedisSentineled(String masterName, Set<HostAndPort> sentinels, GenericObjectPoolConfig<Connection> poolConfig, JedisClientConfig masterClientConfig, JedisClientConfig sentinelClientConfig)`\nconstructors have been removed.\n\n- `JedisClusterInfoCache(JedisClientConfig clientConfig)` and `JedisClusterInfoCache(JedisClientConfig clientConfig, GenericObjectPoolConfig<Connection> poolConfig)`\nconstructors have been removed.\n"
  },
  {
    "path": "docs/migration-guides/v5-to-v6.md",
    "content": "# Jedis 6.0.0 Migration Guide\n\nThis guide helps you migrate from Jedis 5.x to Jedis 6.0.0. Version 6.0.0 includes breaking changes focused on Redis 8.0 support, removal of deprecated modules, and improvements to the Search API.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Breaking Changes](#breaking-changes)\n  - [Removed RedisGraph Support](#removed-redisgraph-support)\n  - [Removed Triggers and Functions (RedisGears v2)](#removed-triggers-and-functions-redisgears-v2)\n  - [Search Dialect Default Change](#search-dialect-default-change)\n  - [FT.PROFILE Return Type Change](#ftprofile-return-type-change)\n  - [COMMAND INFO Response Changes](#command-info-response-changes)\n- [New Features](#new-features)\n  - [Redis 8.0 Support](#redis-80-support)\n  - [SslOptions for Advanced SSL Configuration](#ssloptions-for-advanced-ssl-configuration)\n  - [Token-Based Authentication](#token-based-authentication)\n  - [New Hash Commands](#new-hash-commands)\n  - [Search Warning Messages](#search-warning-messages)\n- [Additional Resources](#additional-resources)\n\n## Overview\n\nJedis 6.0.0 is a major release that adds Redis 8.0 support and removes deprecated features. The main focus areas are:\n\n1. **Redis 8.0 compatibility** - Full support for Redis 8.0 features including built-in JSON, Search, and TimeSeries\n2. **Module deprecations** - Removal of RedisGraph and Triggers & Functions (RedisGears v2) support\n3. **Search API improvements** - Default dialect change and enhanced profiling responses\n4. **Security enhancements** - New SSL options and token-based authentication support\n\n## Breaking Changes\n\n### Removed RedisGraph Support\n\nRedisGraph module support has been completely removed from Jedis 6.0.0 as the module has been deprecated by Redis.\n\n#### Removed Classes and Interfaces\n\nAll classes in the `redis.clients.jedis.graph` package have been removed:\n\n- `RedisGraphCommands` interface\n- `RedisGraphPipelineCommands` interface\n- `GraphCommandObjects` class\n- `GraphCache`, `GraphProtocol`, `GraphQueryParams` classes\n- `ResultSet`, `ResultSetBuilder`, `Record`, `Header`, `Statistics` classes\n- All entity classes: `Edge`, `Node`, `Path`, `Point`, `Property`, `GraphEntity`\n\n### Removed Triggers and Functions (RedisGears v2)\n\nSupport for Triggers and Functions (RedisGears v2) has been removed from Jedis 6.0.0.\n\n#### Removed Classes and Interfaces\n\nAll classes in the `redis.clients.jedis.gears` package have been removed:\n\n- `RedisGearsCommands` interface\n- `RedisGearsProtocol` class\n- `TFunctionListParams`, `TFunctionLoadParams` classes\n- Response classes: `FunctionInfo`, `FunctionStreamInfo`, `GearsLibraryInfo`, `StreamTriggerInfo`, `TriggerInfo`\n\n### Search Dialect Default Change\n\n**BREAKING:** The default search dialect has changed from server-side default to **DIALECT 2** (client-side override).\n\n#### Impact\n\nStarting with Jedis 6.0.0, all `FT.SEARCH` and `FT.AGGREGATE` commands automatically append `DIALECT 2` unless explicitly configured otherwise. This may affect query results if you were relying on DIALECT 1 behavior.\n\n#### Migration Path\n\n**Option 1: Accept DIALECT 2 (Recommended)**\n\nReview your search queries to ensure they work correctly with DIALECT 2. Most queries should work without changes.\n\n**Option 2: Revert to DIALECT 1**\n\nIf you need to maintain DIALECT 1 behavior:\n\n```java\nJedisPooled jedis = new JedisPooled(\"redis://localhost:6379\");\n\n// Set default dialect to 1\njedis.setDefaultSearchDialect(1);\n\n// Now all search commands will use DIALECT 1\nSearchResult result = jedis.ftSearch(\"idx:products\", \"@category:electronics\");\n```\n\n### FT.PROFILE Return Type Change\n\nThe return type of `FT.PROFILE` commands has changed from `Map<String, Object>` to a structured `ProfilingInfo` object.\n\n#### Changed Methods\n\n**Before (v5.x):**\n```java\nMap.Entry<SearchResult, Map<String, Object>> ftProfileSearch(\n    String indexName, FTProfileParams profileParams, Query query);\n\nMap.Entry<AggregationResult, Map<String, Object>> ftProfileAggregate(\n    String indexName, FTProfileParams profileParams, AggregationBuilder aggr);\n```\n\n**After (v6.0.0):**\n```java\nMap.Entry<SearchResult, ProfilingInfo> ftProfileSearch(\n    String indexName, FTProfileParams profileParams, Query query);\n\nMap.Entry<AggregationResult, ProfilingInfo> ftProfileAggregate(\n    String indexName, FTProfileParams profileParams, AggregationBuilder aggr);\n```\n\n### COMMAND INFO Response Changes\n\nThe response format for `COMMAND INFO` has been updated to include subcommand details, making it compatible with Redis 7.0+ and Redis 8.0.\n\n#### Impact\n\nIf you were parsing the `COMMAND INFO` response, you may need to update your code to handle the new structure that includes subcommand information.\n\n**Before (v5.x):**\n```java\nList<Object> commandInfo = jedis.commandInfo(\"SET\");\n// Returns basic command information\n```\n\n**After (v6.0.0):**\n```java\nList<Object> commandInfo = jedis.commandInfo(\"SET\");\n// Returns command information including subcommand details\n// Compatible with Redis 7.0+ format\n```\n\n## New Features\n\n### Redis 8.0 Support\n\nJedis 6.0.0 adds full support for Redis 8.0, which includes built-in support for:\n\n- **JSON** - Native JSON data type (previously RedisJSON module)\n- **Search and Query** - Full-text search capabilities (previously RediSearch module)\n- **TimeSeries** - Time-series data support (previously RedisTimeSeries module)\n\n**Example:**\n```java\nJedisPooled jedis = new JedisPooled(\"redis://localhost:6379\");\n\n// JSON operations (built-in in Redis 8.0)\njedis.jsonSet(\"user:1\", Path2.of(\"$\"), \"{\\\"name\\\":\\\"John\\\",\\\"age\\\":30}\");\nString json = jedis.jsonGet(\"user:1\");\n\n// Search operations (built-in in Redis 8.0)\njedis.ftCreate(\"idx:users\",\n    FTCreateParams.createParams()\n        .on(IndexDataType.JSON)\n        .addPrefix(\"user:\"),\n    TextField.of(\"$.name\").as(\"name\"),\n    NumericField.of(\"$.age\").as(\"age\"));\n\nSearchResult result = jedis.ftSearch(\"idx:users\", \"@name:John\");\n```\n\n### SslOptions for Advanced SSL Configuration\n\nA new `SslOptions` class provides advanced SSL/TLS configuration options for secure connections.\n\n**Features:**\n- Custom keystore and truststore configuration\n- SSL protocol selection\n- SSL verification mode control\n- SSL parameters customization\n\n**Example:**\n```java\nSslOptions sslOptions = SslOptions.builder()\n    .keystore(new File(\"/path/to/keystore.jks\"))\n    .keystorePassword(\"keystorePassword\".toCharArray())\n    .truststore(new File(\"/path/to/truststore.jks\"))\n    .truststorePassword(\"truststorePassword\".toCharArray())\n    .sslVerifyMode(SslVerifyMode.FULL)\n    .build();\n\nJedisClientConfig config = DefaultJedisClientConfig.builder()\n    .ssl(true)\n    .sslOptions(sslOptions)\n    .build();\n\nJedisPooled jedis = new JedisPooled(\"localhost\", 6379, config);\n```\n\n### Token-Based Authentication\n\nJedis 6.0.0 introduces support for token-based authentication, useful for cloud environments and managed Redis services.\n\n**Example:**\n```java\n// Token-based authentication with automatic token refresh\nTokenCredentials tokenCredentials = new TokenCredentials(\"initial-token\");\n\nJedisClientConfig config = DefaultJedisClientConfig.builder()\n    .credentials(tokenCredentials)\n    .build();\n\nJedisPooled jedis = new JedisPooled(\"localhost\", 6379, config);\n\n// Token can be updated dynamically\ntokenCredentials.updateToken(\"new-token\");\n```\n\n### New Hash Commands\n\nSupport for new hash field expiration commands introduced in Redis 7.4:\n\n- `HGETDEL` - Get and delete a hash field\n- `HGETEX` - Get a hash field with expiration options\n- `HSETEX` - Set a hash field with expiration\n\n**Example:**\n```java\n// HSETEX - Set field with expiration\njedis.hsetex(\"user:1\", 3600, \"session\", \"abc123\");\n\n// HGETEX - Get field and update expiration\nString session = jedis.hgetex(\"user:1\", \"session\", \n    HGetExParams.hgetExParams().ex(7200));\n\n// HGETDEL - Get and delete field\nString oldSession = jedis.hgetdel(\"user:1\", \"session\");\n```\n\n### Search Warning Messages\n\nSearch and aggregation queries now support warning messages in results, helping identify potential issues with queries.\n\n**Example:**\n```java\nSearchResult result = jedis.ftSearch(\"idx:products\", \"@name:laptop\");\n\n// Check for warnings\nif (result.hasWarnings()) {\n    List<String> warnings = result.getWarnings();\n    warnings.forEach(warning -> \n        System.out.println(\"Search warning: \" + warning));\n}\n```\n\n## Additional Resources\n\n- [Redis 8.0 Release Notes](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES)\n- [Redis Search Documentation](https://redis.io/docs/interact/search-and-query/)\n- [Redis Query Dialects](https://redis.io/docs/interact/search-and-query/advanced-concepts/dialects/)\n\n## Getting Help\n\nIf you encounter issues during migration:\n\n1. Check the [Jedis GitHub Issues](https://github.com/redis/jedis/issues)\n2. Join the [Redis Discord](https://discord.gg/redis)\n3. Review the [Jedis Javadocs](https://www.javadoc.io/doc/redis.clients/jedis/latest/)\n4. Start a [Discussion](https://github.com/redis/jedis/discussions)\n\n"
  },
  {
    "path": "docs/migration-guides/v6-to-v7.md",
    "content": "# Jedis 7.0.0 Migration Guide\n\nThis guide helps you migrate from Jedis 6.2.0 to Jedis 7.0.0. Version 7.0.0 includes several breaking changes focused on removing deprecated features and improving the API design.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Breaking Changes](#breaking-changes)\n  - [Removed Deprecated Sharding/Sharded Features](#removed-deprecated-shardingsharded-features)\n  - [Base Class Changes](#base-class-changes)\n  - [UnifiedJedis Constructor Changes](#unifiedjedis-constructor-changes) \n  - [Return Type Changes](#return-type-changes)\n- [New Features](#new-features)\n  - [Automatic Failover and Failback](#automatic-failover-and-failback)\n  - [Builder Pattern for Client Creation](#builder-pattern-for-client-creation)\n\n## Overview\n\nJedis 7.0.0 is a major release that removes previously deprecated features and modernizes the API. The main focus areas are:\n\n1. **Removal of deprecated sharding features** - JedisSharding and related classes have been removed\n2. **Base class consolidation** - Pipeline and Transaction base classes have been renamed\n3. **Builder pattern introduction** - New fluent builders for JedisPooled, JedisCluster, and JedisSentineled\n4. **API cleanup** - Removal of deprecated constructors and methods\n\n## Breaking Changes\n\n### Removed Deprecated Sharding Features\n\nThe following classes and features have been **completely removed** in Jedis 7.0.0:\n\n#### Removed Classes\n\n- `JedisSharding` - Use `JedisCluster` for distributed Redis deployments instead\n- `ShardedPipeline` - Use `Pipeline` with `JedisCluster` instead\n- `ShardedConnectionProvider` - No replacement needed\n- `ShardedCommandArguments` - Internal class, no replacement needed\n- `ShardedCommandObjects` - Internal class, no replacement needed\n\n#### Migration Path\n\nIf you were using `JedisSharding`:\n\n**Before (v6.2.0):**\n```java\nList<HostAndPort> shards = Arrays.asList(\n    new HostAndPort(\"localhost\", 6379),\n    new HostAndPort(\"localhost\", 6380)\n);\nJedisSharding jedisSharding = new JedisSharding(shards);\njedisSharding.set(\"key\", \"value\");\n```\n\n**After (v7.0.0):**\n```java\n// Option 1: Use JedisCluster for distributed deployments\nSet<HostAndPort> nodes = new HashSet<>(Arrays.asList(\n    new HostAndPort(\"localhost\", 6379),\n    new HostAndPort(\"localhost\", 6380)\n));\nJedisCluster jedisCluster = new JedisCluster(nodes);\njedisCluster.set(\"key\", \"value\");\n\n// Option 2: Use JedisPooled for single-node deployments\nJedisPooled jedis = new JedisPooled(\"localhost\", 6379);\njedis.set(\"key\", \"value\");\n```\n\n### Base Class Changes\n\nSeveral base classes have been renamed to better reflect their purpose:\n\n#### Pipeline Base Class\n\n- `PipelineBase` has been **removed**\n- `Pipeline` now extends `AbstractPipeline` instead\n\n**Impact:** If you were using `PipelineBase` as a type reference, change it to `AbstractPipeline`.\n\n**Before (v6.2.0):**\n```java\nPipelineBase pipeline = jedis.pipelined();\n```\n\n**After (v7.0.0):**\n```java\nAbstractPipeline pipeline = jedis.pipelined();\n// Or use the concrete type:\nPipeline pipeline = (Pipeline) jedis.pipelined();\n```\n\n#### Transaction Base Class\n\n- `TransactionBase` has been **removed**\n- `Transaction` now extends `AbstractTransaction` instead\n\n**Impact:** If you were using `TransactionBase` as a type reference, change it to `AbstractTransaction`.\n\n**Before (v6.2.0):**\n```java\nTransactionBase transaction = jedis.multi();\n```\n\n**After (v7.0.0):**\n```java\nAbstractTransaction transaction = jedis.multi();\n// Or use the concrete type:\nTransaction transaction = (Transaction) jedis.multi();\n```\n\n### UnifiedJedis Constructor Changes\n\nSeveral deprecated constructors have been removed from `UnifiedJedis`:\n\n#### Removed Constructors\n\n1. **Cluster constructors with maxAttempts:**\n   ```java\n   // REMOVED in v7.0.0\n   UnifiedJedis(Set<HostAndPort> nodes, JedisClientConfig config, int maxAttempts)\n   UnifiedJedis(Set<HostAndPort> nodes, JedisClientConfig config, int maxAttempts, Duration maxTotalRetriesDuration)\n   UnifiedJedis(Set<HostAndPort> nodes, JedisClientConfig config, GenericObjectPoolConfig<Connection> poolConfig, int maxAttempts, Duration maxTotalRetriesDuration)\n   ```\n\n2. **Sharding constructors:**\n   ```java\n   // REMOVED in v7.0.0\n   UnifiedJedis(ShardedConnectionProvider provider)\n   UnifiedJedis(ShardedConnectionProvider provider, Pattern tagPattern)\n   ```\n\n#### Migration Path\n\n**Before (v6.2.0):**\n```java\nSet<HostAndPort> nodes = new HashSet<>();\nnodes.add(new HostAndPort(\"localhost\", 6379));\nJedisClientConfig config = DefaultJedisClientConfig.builder().build();\n\nUnifiedJedis jedis = new UnifiedJedis(nodes, config, 3);\n```\n\n**After (v7.0.0):**\n```java\nSet<HostAndPort> nodes = new HashSet<>();\nnodes.add(new HostAndPort(\"localhost\", 6379));\nJedisClientConfig config = DefaultJedisClientConfig.builder().build();\n\n// Use JedisCluster instead\nJedisCluster jedis = new JedisCluster(nodes, config);\n\n// Or use the new builder pattern\nJedisCluster jedis = JedisCluster.builder()\n    .nodes(nodes)\n    .clientConfig(config)\n    .build();\n```\n\n### Return Type Changes\n\n#### UnifiedJedis.pipelined()\n\nThe return type has been changed to be more generic:\n\n**Before (v6.2.0):**\n```java\nPipelineBase pipelined()\n```\n\n**After (v7.0.0):**\n```java\nAbstractPipeline pipelined()\n```\n\n**Impact:** Minimal - `AbstractPipeline` is the parent class, so existing code should continue to work.\n\n## New Features\n\n### Automatic Failover and Failback\n\nJedis 7.0.0 significantly refactors the automatic failover and failback API. If you were using the failover features in v6.2.0 with `MultiClusterClientConfig` and `MultiClusterPooledConnectionProvider`, these have been renamed and improved in v7.0.0.\n\n**For detailed migration guidance on automatic failover and failback** please refer to the **[Automatic Failover and Failback Migration Guide](https://redis.github.io/jedis/failover/#migration-from-6x-to-7x)**.\n\n### Builder Pattern for Client Creation\n\nJedis 7.0.0 introduces a fluent builder pattern for creating client instances, making configuration more intuitive and discoverable.\n\n#### JedisPooled Builder\n\n**New in v7.0.0:**\n```java\nJedisPooled jedis = JedisPooled.builder()\n    .hostAndPort(\"localhost\", 6379)\n    .clientConfig(DefaultJedisClientConfig.builder()\n        .user(\"myuser\")\n        .password(\"mypassword\")\n        .database(0)\n        .build())\n    .poolConfig(new GenericObjectPoolConfig<>())\n    .build();\n```\n\n#### JedisCluster Builder\n\n**New in v7.0.0:**\n```java\nSet<HostAndPort> nodes = new HashSet<>();\nnodes.add(new HostAndPort(\"localhost\", 7000));\nnodes.add(new HostAndPort(\"localhost\", 7001));\n\nJedisCluster cluster = JedisCluster.builder()\n    .nodes(nodes)\n    .clientConfig(DefaultJedisClientConfig.builder()\n        .password(\"mypassword\")\n        .build())\n    .maxAttempts(5)\n    .maxTotalRetriesDuration(Duration.ofSeconds(10))\n    .build();\n```\n\n#### JedisSentineled Builder\n\n**New in v7.0.0:**\n```java\nSet<HostAndPort> sentinels = new HashSet<>();\nsentinels.add(new HostAndPort(\"localhost\", 26379));\nsentinels.add(new HostAndPort(\"localhost\", 26380));\n\nJedisSentineled jedis = JedisSentineled.builder()\n    .masterName(\"mymaster\")\n    .sentinels(sentinels)\n    .clientConfig(DefaultJedisClientConfig.builder()\n        .password(\"mypassword\")\n        .build())\n    .build();\n```\n\n## Getting Help\n\nIf you encounter issues during migration create an issue or [start a discussion](https://github.com/redis/jedis/discussions/new?category=q-a).\n\n"
  },
  {
    "path": "docs/redisearch.md",
    "content": "# RediSearch Jedis Quick Start\n\nTo use RediSearch features with Jedis, you'll need to use an implementation of RediSearchCommands.\n\n## Creating the RediSearch client\n\nInitializing the client with RedisClient:\n\n```java\nRedisClient client = RedisClient.builder().hostAndPort(\"localhost\", 6379).build();\n```\n\nInitializing the client with RedisClusterClient:\n\n```java\nSet<HostAndPort> nodes = new HashSet<>();\nnodes.add(new HostAndPort(\"127.0.0.1\", 7379));\nnodes.add(new HostAndPort(\"127.0.0.1\", 7380));\n\nRedisClusterClient client = RedisClusterClient.builder().nodes(nodes).build();\n```\n\n## Indexing and querying\n\n### Indexing\n\nDefining a schema for an index and creating it:\n\n```java\nSchema sc = new Schema()\n        .addTextField(\"title\", 5.0)\n        .addTextField(\"body\", 1.0)\n        .addNumericField(\"price\");\n\nIndexDefinition def = new IndexDefinition()\n        .setPrefixes(new String[]{\"item:\", \"product:\"})\n        .setFilter(\"@price>100\");\n\nclient.ftCreate(\"item-index\", IndexOptions.defaultOptions().setDefinition(def), sc);\n```\n\nAlternatively, we can create the same index using FTCreateParams:\n\n```java\nclient.ftCreate(\"item-index\",\n\n        FTCreateParams.createParams()\n                .prefix(\"item:\", \"product:\")\n                .filter(\"@price>100\"),\n\n        TextField.of(\"title\").weight(5.0),\n        TextField.of(\"body\"),\n        NumericField.of(\"price\")\n);\n```\n\n### Inserting\n\nAdding documents to the index:\n\n```java\nMap<String, Object> fields = new HashMap<>();\nfields.put(\"title\", \"hello world\");\nfields.put(\"state\", \"NY\");\nfields.put(\"body\", \"lorem ipsum\");\nfields.put(\"price\", 1337);\n\nclient.hset(\"item:hw\", RediSearchUtil.toStringMap(fields));\n```\n\nAnother way to insert documents:\n\n```java\nclient.hsetObject(\"item:hw\", fields);\n```\n\n### Querying\n\nSearching the index:\n\n```java\nQuery q = new Query(\"hello world\")\n        .addFilter(new Query.NumericFilter(\"price\", 0, 1000))\n        .limit(0, 5);\n\nSearchResult sr = client.ftSearch(\"item-index\", q);\n```\n\nAlternative searching using FTSearchParams:\n\n```java\nSearchResult sr = client.ftSearch(\"item-index\",\n        \"hello world\",\n        FTSearchParams.searchParams()\n                .filter(\"price\", 0, 1000)\n                .limit(0, 5));\n```\n\nAggregation query:\n\n```java\nAggregationBuilder ab = new AggregationBuilder(\"hello\")\n        .apply(\"@price/1000\", \"k\")\n        .groupBy(\"@state\", Reducers.avg(\"@k\").as(\"avgprice\"))\n        .filter(\"@avgprice>=2\")\n        .sortBy(10, SortedField.asc(\"@state\"));\n\nAggregationResult ar = client.ftAggregate(\"item-index\", ab);\n```\n"
  },
  {
    "path": "docs/redisjson.md",
    "content": "# RedisJSON Jedis Quick Start\n\nJedis supports [RedisJSON](https://redis.io/docs/latest/develop/data-types/json/) and [RediSearch](https://redis.io/docs/latest/develop/interact/search-and-query/).\n\nThe latest versions of RedisJSON let you store, manipulate, index, and query JSON.\nTo use these features with Jedis, you'll need to use the `UnifiedJedis` interface or a sub-class of it.\n\nLet's see how this works.\n\n## Creating with RedisJSON client\n\nFirst, let's create a `RedisClient` client instance:\n\n```java\nRedisClient client = RedisClient.builder().hostAndPort(\"localhost\", 6479).build();\n```\n\nOr, a `RedisClusterClient` client instance:\n\n```java\nSet<HostAndPort> nodes = new HashSet<>();\nnodes.add(new HostAndPort(\"127.0.0.1\", 7379));\nnodes.add(new HostAndPort(\"127.0.0.1\", 7380));\n\nRedisClusterClient client = RedisClusterClient.builder().nodes(nodes).build();\n```\n\nNow we can start working with JSON. For these examples, we'll be using [GSON](https://github.com/google/gson)\nto handle the serialization of POJOs to JSON.\n\n## Creating JSON documents\n\nSuppose we're building an online learning platform, and we want to represent students.\nLet's create a POJO to represent our students:\n\n```java\nprivate class Student {\n    private String firstName;\n    private String lastName;\n\n    public Student(String firstName, String lastName) {\n        this.firstName = firstName;\n        this.lastName = lastName;\n    }\n\n    public String getFirstName() {\n        return firstName;\n    }\n\n    public String getLastName() {\n        return lastName;\n    }\n}\n```\n\nNow we can create some students and store them in Redis as JSON:\n\n```java\nfinal Gson gson = new Gson();\n\nStudent maya = new Student(\"Maya\", \"Jayavant\");\nclient.jsonSet(\"student:111\", gson.toJson(maya));\n\nStudent oliwia = new Student(\"Oliwia\", \"Jagoda\");\nclient.jsonSet(\"student:112\", gson.toJson(oliwia));\n```\n\nSome of other ways to store POJOs as JSON:\n\n```\nclient.jsonSetLegacy(\"student:111\", maya);\nclient.jsonSetWithEscape(\"student:112\", oliwia);\n```\n\n## Querying and indexing JSON\n\nIf we want to be able to query this JSON, we'll need to create an index. Let's create an index on the \"firstName\" and \"lastName\" fields.\n\n1. We define which fields to index (\"firstName\" and \"lastName\").\n2. We set up the index definition to recognize JSON and include only those documents whose key starts with \"student:\".\n3. Then we actually create the index, called \"student-index\", by calling `ftCreate()`.\n\n```java\nSchema schema = new Schema().addTextField(\"$.firstName\", 1.0).addTextField(\"$.lastName\", 1.0);\n\nIndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON)\n        .setPrefixes(new String[]{\"student:\"});\n\nclient.ftCreate(\"student-index\", IndexOptions.defaultOptions().setDefinition(rule), schema);\n```\n\nAlternatively creating the same index using FTCreateParams: \n\n```java\nclient.ftCreate(\"student-index\",\n        FTCreateParams.createParams().on(IndexDataType.JSON).prefix(\"student:\"),\n        TextField.of(\"$.firstName\"), TextField.of(\"$.lastName\"));\n```\n\nWith an index now defined, we can query our JSON. Let's find all students whose name begins with \"maya\":\n\n```java\nQuery q = new Query(\"@\\\\$\\\\.firstName:maya*\");\nSearchResult mayaSearch = client.ftSearch(\"student-index\", q);\n```\n\nSame query can be done using FTSearchParams:\n\n```java\nSearchResult mayaSearch = client.ftSearch(\"student-index\",\n        \"@\\\\$\\\\.firstName:maya*\",\n        FTSearchParams.searchParams());\n```\n\nWe can then iterate over our search results:\n\n```java\nList<Document> docs = mayaSearch.getDocuments();\nfor (Document doc : docs) {\n   System.out.println(doc);\n}\n```\n\nThis example just scratches the surface. You can atomically manipulate JSON documents and query them in a variety of ways.\nSee the [RedisJSON docs](https://oss.redis.com/redisjson/), the [RediSearch](https://oss.redis.com/redisearch/) docs,\nand our course, [\"Querying, Indexing, and Full-text Search in Redis\"](https://university.redis.com/courses/ru203/),\nfor a lot more examples.\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "mkdocs~=1.6\nmkdocs-material~=9.5\npymdown-extensions~=10.8\nmkdocs-macros-plugin~=1.0\nmkdocs-glightbox"
  },
  {
    "path": "docs/transactions-multi.md",
    "content": "# Transactions/Multi\n\n## Overview\n\nTransactions guarantee that all the included commands will execute to completion without being interrupted by commands from other clients. See the [Transactions](https://redis.io/docs/latest/develop/interact/transactions/) page for more information.\n\nTo execute commands in a transaction, create a transaction object with the `multi()` command, call command methods on that object, and then call the transaction object's `exec()` method to execute it. You can access the results from commands in the transaction using `Response` objects. The `exec()` method also returns a `List<Object>` value that contains all the result values in the order the commands were executed.\n\n\n## Immediate Transaction Start\n\nThe simplest way to use transactions is to call `multi()` on your Jedis client, which immediately starts a transaction by sending the `MULTI` command to Redis. All subsequent commands are queued until `exec()` is called.\n\n```java\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.AbstractTransaction;\nimport redis.clients.jedis.Response;\nimport java.util.List;\n\nRedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n// Create a transaction that immediately sends MULTI\ntry (AbstractTransaction tx = jedis.multi()) {\n    // Commands are queued\n    Response<String> set1 = tx.set(\"counter:1\", \"0\");\n    Response<Long> incr1 = tx.incrBy(\"counter:1\", 1);\n    Response<Long> incr2 = tx.incrBy(\"counter:1\", 2);\n    \n    // Execute the transaction\n    List<Object> results = tx.exec();\n    \n    // Access results via Response objects\n    System.out.println(incr1.get()); // 1\n    System.out.println(incr2.get()); // 3\n    \n    // Or via the results list\n    System.out.println(results.get(0)); // OK\n    System.out.println(results.get(1)); // 1\n    System.out.println(results.get(2)); // 3\n}\n\njedis.close();\n```\n\n### Response Handling\n\nCommands invoked within a transaction return `Response<T>` objects. These responses become available only after `exec()` is called:\n\n- Before `exec()`: Calling `response.get()` will throw `IllegalStateException` with the message \"Please close pipeline or multi block before calling this method.\"\n- After `exec()`: Response objects contain the actual results from Redis\n\nThe `exec()` method returns a `List<Object>` containing all command results in the order they were queued.\n\n## Manual Transaction Start\n\nFor more control, you can create a transaction without immediately sending `MULTI`. This is useful when you need to:\n\n- Execute commands before starting the transaction\n- Use `WATCH` to implement optimistic locking\n- Conditionally start the transaction\n\nCreate a manual transaction by passing `false` to the `transaction()` method:\n\n```java\nRedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n// Create transaction without sending MULTI\ntry (AbstractTransaction tx = jedis.transaction(false)) {\n    \n    // Commands before multi() are executed immediately\n    Response<String> setBeforeMulti = tx.set(\"mykey\", \"initial_value\");\n    Response<String> getBeforeMulti = tx.get(\"mykey\");\n    \n    // These responses are available immediately\n    System.out.println(setBeforeMulti.get()); // OK\n    System.out.println(getBeforeMulti.get()); // initial_value\n    \n    // Now start the transaction\n    tx.multi();\n    \n    // Commands after multi() are queued\n    Response<String> set = tx.set(\"mykey\", \"new_value\");\n    Response<String> get = tx.get(\"mykey\");\n    \n    // Execute the transaction\n    List<Object> results = tx.exec();\n    \n    // Results from queued commands\n    System.out.println(set.get()); // OK\n    System.out.println(get.get()); // new_value\n}\n\njedis.close();\n```\n\n### Using WATCH for Optimistic Locking\n\nThe `WATCH` command monitors keys for changes. If any watched key is modified before `EXEC`, the transaction is aborted and `exec()` returns `null`.\n\n**Important:** `WATCH` must be executed through the transaction object to ensure it uses the transaction's dedicated connection.\n\n```java\ntry (AbstractTransaction tx = jedis.transaction(false)) {\n    // WATCH must be on the transaction's dedicated connection\n    tx.watch(\"counter\");\n\n    // Read current value - can use the client directly\n    String current = jedis.get(\"counter\");\n    int newValue = Integer.parseInt(current) + 1;\n\n    // Start transaction and queue update\n    tx.multi();\n    tx.set(\"counter\", String.valueOf(newValue));\n\n    // Returns null if key was modified by another client\n    List<Object> results = tx.exec();\n\n    if (results == null) {\n        System.out.println(\"Transaction aborted - key was modified\");\n    }\n}\n```\n\n## Connection Lifecycle\n\nA transaction acquires a dedicated connection that is held until `close()` is called. Ensure the transaction is closed on error:\n\n```java\ntry (AbstractTransaction tx = jedis.multi()) {\n    tx.set(\"key\", \"value\");\n    tx.exec();\n}\n```\n\n**Important:** `WATCH` must be executed through the transaction object to ensure it uses the transaction's dedicated connection.\n\n\n\n## Transaction Completion\n\nComplete a transaction by calling either:\n\n- **`exec()`** - Executes all queued commands atomically and returns a `List<Object>` with the results\n- **`discard()`** - Discards all queued commands without executing them\n\n### Automatic Cleanup\n\nWhen using try-with-resources, `close()` automatically sends `DISCARD` (if in `MULTI` state) or `UNWATCH` (if in `WATCH` state) to ensure the connection is returned to the pool in a clean state.\n\n\n\n"
  },
  {
    "path": "docs/tutorials_examples.md",
    "content": "# Tutorials and Examples\n\n## General\n\n* Redis for Java Developers: <https://university.redis.io/learningpath/kllayn0wtd847i>\n* Jedis Guide: <https://redis.io/docs/latest/develop/clients/jedis/>\n* Connecting to a Redis server: <https://redis.io/docs/latest/develop/clients/jedis/connect/>\n* Using Jedis in production: <https://redis.io/docs/latest/develop/clients/jedis/produsage/>\n\n## Client-side Caching\n\n* Client-side Caching: <https://github.com/Redislabs-Solution-Architects/redis-client-side-caching-csc-jedis-demo>\n\n## JSON\n\n* Store, Read and Search JSON: <https://redis.io/kb/doc/1cd7hi2721/learn-to-store-read-and-search-data-in-json-documents-using-jedis>\n\n## Search\n\n* Vector Search: <https://redis.io/kb/doc/13qsrk8xpx/how-to-perform-vector-search-in-java-with-the-jedis-client-library>\n* Spring Boot Search: <https://github.com/Redislabs-Solution-Architects/Spring-Boot-RediSearch-Example>\n"
  },
  {
    "path": "docs/verifying-artifacts.md",
    "content": "## Verifying contents\nJedis artifacts published on Maven central are signed. For each artifact, there is an associated signature file with the `.asc` suffix.\n\n## Keys used for signing\n```\npub   rsa3072 2023-12-17 [SC]\n165E63A75659104C72B49129C2B44BE148BDCEC8\nuid           [ unknown] Redis OSS <oss@redis.com>\nsig 3        C2B44BE148BDCEC8 2023-12-17  [self-signature]\n```\nA copy of this key is stored on the  keyserver [Ubuntu keyserver](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0xC2B44BE148BDCEC8)\n\n### Before 2023-12-17 \n```\npub   rsa3072 2023-09-03 [SC]\n6BF6C1905C9642D1181D157443543D884CC71C96\nuid           [ unknown] Redis OSS <oss@redis.com>\nsig 3        43543D884CC71C96 2023-09-03  [self-signature]\n```\nA copy of this key is stored on the  keyserver [Ubuntu keyserver](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0x43543D884CC71C96)\n\n### Before 2023-09-03\n```\npub   rsa3072 2021-06-27 [SC] [expired: 2023-06-27]\n      1C913D6D36DAD0A679863F5CB68CD0A11D28B97D\nuid           [ expired] RedisLabs OSS <oss@redislabs.com>\nsig 3        B68CD0A11D28B97D 2021-06-27  [self-signature]\n```\nA copy of this key is stored on the keyserver [Ubuntu keyserver](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0xB68CD0A11D28B97D)\n### Before 2021-06-27\n```\npub   rsa3072 2018-08-12 [SC] [expired: 2020-08-11]\n      4EC78BD38D30A2E85C02C39C2D62B50EF8D3297A\nuid           [ expired] Guy Korlad <guy.korland@redislabs.com>\nsig 3        2D62B50EF8D3297A 2018-08-12  [self-signature]\n```\nA copy of this key is stored on the keyserver [Ubuntu keyserver](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0x2D62B50EF8D3297A)"
  },
  {
    "path": "formatter-pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n\t<parent>\n\t\t<groupId>org.sonatype.oss</groupId>\n\t\t<artifactId>oss-parent</artifactId>\n\t\t<version>7</version>\n\t</parent>\n\n\t<modelVersion>4.0.0</modelVersion>\n\t<packaging>jar</packaging>\n\t<groupId>redis.clients</groupId>\n\t<artifactId>jedis</artifactId>\n\t<name>Jedis</name>\n\t<version>5.3.0</version>\n\t<description>Jedis is a blazingly small and sane Redis java client.</description>\n\t<url>https://github.com/redis/jedis</url>\n\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>net.revelc.code.formatter</groupId>\n\t\t\t\t<artifactId>formatter-maven-plugin</artifactId>\n\t\t\t\t<version>2.16.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<configFile>${project.basedir}/hbase-formatter.xml</configFile>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>validate</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "hbase-formatter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<profiles version=\"12\">\n<profile kind=\"CodeFormatterProfile\" name=\"'HBase Apache Current'\" version=\"12\">\n<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.disabling_tag\" value=\"@formatter:off\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_field\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.use_on_off_tags\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_ellipsis\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_multiple_fields\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_conditional_expression\" value=\"80\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_binary_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_array_initializer\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_package\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.continuation_indentation\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation\" value=\"20\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_binary_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_package\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.source\" value=\"1.7\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_line_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.join_wrapped_lines\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_member_type\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.align_type_members_on_columns\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_unary_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_parameter_description\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.lineSplit\" value=\"100\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indentation.size\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.enabling_tag\" value=\"@formatter:on\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_assignment\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.problem.assertIdentifier\" value=\"error\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.tabulation.char\" value=\"space\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_statements_compare_to_body\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_method\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_method_declaration\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_switch\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.problem.enumIdentifier\" value=\"error\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_ellipsis\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_block\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_method_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.compact_else_if\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_enum_constant\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_root_tags\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.tabulation.size\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_empty_lines\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_block_in_case\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.compliance\" value=\"1.7\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_unary_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_binary_expression\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode\" value=\"enabled\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_label\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_javadoc_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.line_length\" value=\"100\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_between_import_groups\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_binary_operator\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_statements_compare_to_block\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.join_lines_in_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_compact_if\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_imports\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_html\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_source_code\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.codegen.targetPlatform\" value=\"1.7\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_resources_in_try\" value=\"80\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_block_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_enum_constants\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_type_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_imports\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line\" value=\"true\"/>\n</profile>\n</profiles>\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Jedis\nrepo_name: Jedis\nsite_author: Redis, Inc.\nsite_description: Jedis is a Redis client for the JVM.\nrepo_url: https://github.com/redis/jedis\nremote_branch: gh-pages\n\ntheme:\n  name: material\n  logo: assets/images/logo.png\n  favicon: assets/images/favicon-16x16.png\nextra_css:\n  - css/extra.css\n\nplugins:\n  - search\n  - macros:\n      include_dir: .\n\nmarkdown_extensions:\n  - pymdownx.highlight:\n      anchor_linenums: true\n      line_spans: __span\n      pygments_lang_class: true\n  - pymdownx.inlinehilite\n  - pymdownx.snippets\n  - pymdownx.superfences\n  - admonition\n  - pymdownx.details\n\nnav:\n  - Home: index.md\n  - Jedis Maven: jedis-maven.md\n  - User Guide:\n    - Transactions/Multi: transactions-multi.md\n  - Migrating to newer versions:\n      - Jedis 7: migration-guides/v6-to-v7.md\n      - Jedis 6: migration-guides/v5-to-v6.md\n      - Jedis 5: migration-guides/v4-to-v5.md\n      - Jedis 4:\n        - Breaking changes: migration-guides/v3-to-v4.md\n        - Primitives: migration-guides/v3-to-v4-primitives.md\n        - ZSET: migration-guides/v3-to-v4-zset-list.md\n  - Using Jedis with ...:\n    - Search: redisearch.md\n    - JSON: redisjson.md\n  - Failover: failover.md\n  - Verifying artifacts: verifying-artifacts.md\n  - FAQ: faq.md\n  - API Reference: https://www.javadoc.io/doc/redis.clients/jedis/latest/index.html\n  - Tutorials and Examples:  tutorials_examples.md\n  - Jedis Guide: https://redis.io/docs/latest/develop/connect/clients/java/jedis/\n  - Redis Command Reference: https://redis.io/docs/latest/commands/\n  - Advanced Usage: advanced-usage.md\n\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n\t<parent>\n\t\t<groupId>org.sonatype.oss</groupId>\n\t\t<artifactId>oss-parent</artifactId>\n\t\t<version>7</version>\n\t</parent>\n\n\t<modelVersion>4.0.0</modelVersion>\n\t<packaging>jar</packaging>\n\t<groupId>redis.clients</groupId>\n\t<artifactId>jedis</artifactId>\n\t<version>8.0.0-SNAPSHOT</version>\n\t<name>Jedis</name>\n\t<description>Jedis is a blazingly small and sane Redis java client.</description>\n\t<url>https://github.com/redis/jedis</url>\n\n\t<mailingLists>\n\t\t<mailingList>\n\t\t\t<name>Jedis Mailing List</name>\n\t\t\t<post>jedis_redis@googlegroups.com</post>\n\t\t\t<archive>\n\t\t\t\thttps://groups.google.com/group/jedis_redis\n\t\t\t</archive>\n\t\t</mailingList>\n\t</mailingLists>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>MIT</name>\n\t\t\t<url>https://github.com/redis/jedis/blob/master/LICENSE</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<issueManagement>\n\t\t<system>github</system>\n\t\t<url>https://github.com/redis/jedis/issues</url>\n\t</issueManagement>\n\n\t<scm>\n\t\t<connection>scm:git:git@github.com:redis/jedis.git</connection>\n\t\t<url>scm:git:git@github.com:redis/jedis.git</url>\n\t\t<developerConnection>https://github.com/redis/jedis/tree/master</developerConnection>\n\t</scm>\n\n\t<developers>\n\t\t<developer>\n\t\t\t<id>redis</id>\n\t\t\t<name>Redis Ltd.</name>\n\t\t\t<organization>Redis</organization>\n\t\t\t<organizationUrl>https://redis.io</organizationUrl>\n\t\t</developer>\n\t</developers>\n\n\t<properties>\n\t\t<github.global.server>github</github.global.server>\n\t\t<jedis.module.name>redis.clients.jedis</jedis.module.name>\n\t\t<slf4j.version>1.7.36</slf4j.version>\n\t\t<resilience4j.version>1.7.1</resilience4j.version>\n\t\t<jackson.version>2.21.1</jackson.version>\n\t\t<maven.surefire.version>3.5.5</maven.surefire.version>\n\t\t<junit.version>5.14.3</junit.version>\n\t\t<!-- Default JVM options for tests -->\n\t\t<JVM_OPTS></JVM_OPTS>\n\t\t<!-- Default excluded groups for tests - can be overridden from command line -->\n\t\t<excludedGroupsForUnitTests>integration,scenario</excludedGroupsForUnitTests>\n\t\t<skipUnitTests>false</skipUnitTests>\n\t\t<skipIntegrationTests>false</skipIntegrationTests>\n\t\t<skipScenarioTests>true</skipScenarioTests>\n\t</properties>\n\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.junit</groupId>\n\t\t\t\t<artifactId>junit-bom</artifactId>\n\t\t\t\t<version>${junit.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t\t<version>${slf4j.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t<artifactId>commons-pool2</artifactId>\n\t\t\t<version>2.12.1</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.json</groupId>\n\t\t\t<artifactId>json</artifactId>\n\t\t\t<version>20251224</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.google.code.gson</groupId>\n\t\t\t<artifactId>gson</artifactId>\n\t\t\t<version>2.13.2</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>redis.clients.authentication</groupId>\n\t\t\t<artifactId>redis-authx-core</artifactId>\n\t\t\t<version>0.1.1-beta2</version>\n\t\t</dependency>\n\n\t\t<!-- Optional dependencies -->\n\n\t\t<!-- UNIX socket connection support -->\n\t\t<dependency>\n\t\t\t<groupId>com.kohlschutter.junixsocket</groupId>\n\t\t\t<artifactId>junixsocket-core</artifactId>\n\t\t\t<version>2.10.1</version>\n\t\t\t<type>pom</type>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<!-- Well-known text representation of geometry in RediSearch support -->\n\t\t<dependency>\n\t\t\t<groupId>org.locationtech.jts</groupId>\n\t\t\t<artifactId>jts-core</artifactId>\n\t\t\t<version>1.20.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<!-- test -->\n\t\t<dependency>\n\t\t\t<groupId>org.junit.jupiter</groupId>\n\t\t\t<artifactId>junit-jupiter-api</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.junit.jupiter</groupId>\n\t\t\t<artifactId>junit-jupiter-params</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.mockito</groupId>\n\t\t\t<artifactId>mockito-junit-jupiter</artifactId>\n\t\t\t<version>4.11.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.junit.jupiter</groupId>\n\t\t\t<artifactId>junit-jupiter-engine</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.hamcrest</groupId>\n\t\t\t<artifactId>hamcrest</artifactId>\n\t\t\t<version>3.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>ch.qos.logback</groupId>\n\t\t\t<artifactId>logback-classic</artifactId>\n\t\t\t<version>1.2.13</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.mockito</groupId>\n\t\t\t<artifactId>mockito-inline</artifactId>\n\t\t\t<version>4.11.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.fasterxml.jackson.core</groupId>\n\t\t\t<artifactId>jackson-databind</artifactId>\n\t\t\t<version>${jackson.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.fasterxml.jackson.datatype</groupId>\n\t\t\t<artifactId>jackson-datatype-jsr310</artifactId>\n\t\t\t<version>${jackson.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>net.javacrumbs.json-unit</groupId>\n\t\t\t<artifactId>json-unit</artifactId>\n\t\t\t<version>2.40.1</version> <!-- 3.x requires Java 17 -->\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.httpcomponents.client5</groupId>\n\t\t\t<artifactId>httpclient5-fluent</artifactId>\n\t\t\t<version>5.6</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.awaitility</groupId>\n\t\t\t<artifactId>awaitility</artifactId>\n\t\t\t<version>4.3.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>redis.clients.authentication</groupId>\n\t\t\t<artifactId>redis-authx-entraid</artifactId>\n\t\t\t<version>0.1.1-beta2</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>eu.rekawek.toxiproxy</groupId>\n\t\t\t<artifactId>toxiproxy-java</artifactId>\n\t\t\t<version>2.1.11</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<!-- circuit breaker / failover -->\n\t\t<dependency>\n\t\t\t<groupId>io.github.resilience4j</groupId>\n\t\t\t<artifactId>resilience4j-all</artifactId>\n\t\t\t<version>${resilience4j.version}</version>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.github.resilience4j</groupId>\n\t\t\t<artifactId>resilience4j-circuitbreaker</artifactId>\n\t\t\t<version>${resilience4j.version}</version>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.github.resilience4j</groupId>\n\t\t\t<artifactId>resilience4j-retry</artifactId>\n\t\t\t<version>${resilience4j.version}</version>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t</dependencies>\n\n\t<distributionManagement>\n\t\t<repository>\n\t\t\t<id>central</id>\n\t\t\t<url>https://central.sonatype.com/api/v1/publisher/deployments/upload/</url>\n\t\t</repository>\n\t\t<snapshotRepository>\n\t\t\t<id>central</id>\n\t\t\t<url>https://central.sonatype.com/repository/maven-snapshots/</url>\n\t\t</snapshotRepository>\n\t</distributionManagement>\n\n\t<build>\n\t\t<resources>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<filtering>true</filtering>\n\t\t\t</resource>\n\t\t</resources>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t<version>0.8.14</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<!-- Collect coverage for unit tests -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>prepare-agent-ut</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<propertyName>argLine</propertyName>\n\t\t\t\t\t\t\t<destFile>${project.build.directory}/jacoco-ut.exec</destFile>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Collect coverage for integration tests tagged with @Tag(\"integration\") -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>prepare-agent-it-tagged</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<propertyName>failsafeTaggedArgLine</propertyName>\n\t\t\t\t\t\t\t<destFile>${project.build.directory}/jacoco-it-tagged.exec</destFile>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Collect coverage for integration tests with filename suffix IntegrationTest(s) -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>prepare-agent-it-suffix</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<propertyName>failsafeSuffixArgLine</propertyName>\n\t\t\t\t\t\t\t<destFile>${project.build.directory}/jacoco-it-suffix.exec</destFile>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Collect coverage for scenario tests -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>prepare-agent-scenario</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<propertyName>failsafeScenarioArgLine</propertyName>\n\t\t\t\t\t\t\t<destFile>${project.build.directory}/jacoco-scenario.exec</destFile>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Merge all coverage data after all integration tests -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>merge-coverage</id>\n\t\t\t\t\t\t<phase>post-integration-test</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>merge</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<fileSets>\n\t\t\t\t\t\t\t\t<fileSet>\n\t\t\t\t\t\t\t\t\t<directory>${project.build.directory}</directory>\n\t\t\t\t\t\t\t\t\t<includes>\n\t\t\t\t\t\t\t\t\t\t<include>jacoco-ut.exec</include>\n\t\t\t\t\t\t\t\t\t\t<include>jacoco-it-tagged.exec</include>\n\t\t\t\t\t\t\t\t\t\t<include>jacoco-it-suffix.exec</include>\n\t\t\t\t\t\t\t\t\t\t<include>jacoco-scenario.exec</include>\n\t\t\t\t\t\t\t\t\t</includes>\n\t\t\t\t\t\t\t\t</fileSet>\n\t\t\t\t\t\t\t</fileSets>\n\t\t\t\t\t\t\t<destFile>${project.build.directory}/jacoco-merged.exec</destFile>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Generate the final report on the merged data -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>report</id>\n\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>report</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<dataFile>${project.build.directory}/jacoco-merged.exec</dataFile>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t<version>3.15.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<source>1.8</source>\n\t\t\t\t\t<target>1.8</target>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-surefire-plugin</artifactId>\n\t\t\t\t<version>${maven.surefire.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<skipTests>${skipUnitTests}</skipTests>\n\t\t\t\t\t<argLine>@{argLine} ${JVM_OPTS}</argLine>\n\t\t\t\t\t<systemPropertyVariables>\n\t\t\t\t\t\t<redis-hosts>${redis-hosts}</redis-hosts>\n\t\t\t\t\t</systemPropertyVariables>\n\t\t\t\t\t<excludedGroups>${excludedGroupsForUnitTests}</excludedGroups>\n\t\t\t\t\t<excludes>\n\t\t\t\t\t\t<exclude>**/examples/*.java</exclude>\n\t\t\t\t\t\t<exclude>**/scenario/*Test.java</exclude>\n\t\t\t\t\t\t<!-- Exclude integration tests from unit-test phase -->\n\t\t\t\t\t\t<exclude>**/*IntegrationTest.java</exclude>\n\t\t\t\t\t\t<exclude>**/*IntegrationTests.java</exclude>\n\t\t\t\t\t\t<!-- Exclude code generators - these are tools, not tests -->\n\t\t\t\t\t\t<exclude>**/codegen/**/*.java</exclude>\n\t\t\t\t\t</excludes>\n\t\t\t\t\t<!--<trimStackTrace>false</trimStackTrace>-->\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-failsafe-plugin</artifactId>\n\t\t\t\t<version>${maven.surefire.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<argLine>@{failsafeSuffixArgLine} ${JVM_OPTS}</argLine>\n\t\t\t\t\t<systemPropertyVariables>\n\t\t\t\t\t\t<redis-hosts>${redis-hosts}</redis-hosts>\n\t\t\t\t\t</systemPropertyVariables>\n\t\t\t\t\t<summaryFile>${project.build.directory}/failsafe-summary.xml</summaryFile>\n\t\t\t\t\t<!-- Default includes used when invoking failsafe goals directly (e.g., mvn failsafe:integration-test) -->\n\t\t\t\t\t<excludedGroups>scenario</excludedGroups>\n\t\t\t\t\t<includes>\n\t\t\t\t\t\t<include>**/*IntegrationTest.java</include>\n\t\t\t\t\t\t<include>**/*IntegrationTests.java</include>\n\t\t\t\t\t</includes>\n\t\t\t\t\t<excludes>\n\t\t\t\t\t\t<exclude>**/examples/*.java</exclude>\n\t\t\t\t\t\t<!-- Exclude unit tests -->\n\t\t\t\t\t\t<exclude>**/mocked/*.java</exclude>\n\t\t\t\t\t</excludes>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<!-- Run all tests tagged with @Tag(\"integration\") regardless of file name -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>it-tagged</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>integration-test</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<skip>${skipIntegrationTests}</skip>\n\t\t\t\t\t\t\t<argLine>@{failsafeTaggedArgLine} ${JVM_OPTS}</argLine>\n\t\t\t\t\t\t\t<groups>integration</groups>\n\t\t\t\t\t\t\t<excludedGroups>scenario,unit</excludedGroups>\n\t\t\t\t\t\t\t<includes>\n\t\t\t\t\t\t\t\t<include>**/*Test.java</include>\n\t\t\t\t\t\t\t\t<include>**/*Tests.java</include>\n\t\t\t\t\t\t\t</includes>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Also run tests whose filenames end with IntegrationTest or IntegrationTests, even if not tagged -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>it-suffix</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>integration-test</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<skip>${skipIntegrationTests}</skip>\n\t\t\t\t\t\t\t<argLine>@{failsafeSuffixArgLine} ${JVM_OPTS}</argLine>\n\t\t\t\t\t\t\t<excludedGroups>scenario,unit</excludedGroups>\n\t\t\t\t\t\t\t<includes>\n\t\t\t\t\t\t\t\t<include>**/*IT.java</include>\n\t\t\t\t\t\t\t\t<include>**/*ITs.java</include>\n\t\t\t\t\t\t\t\t<include>**/*IntegrationTest.java</include>\n\t\t\t\t\t\t\t\t<include>**/*IntegrationTests.java</include>\n\t\t\t\t\t\t\t</includes>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Run all tests tagged with @Tag(\"scenario\") regardless of file name -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>scenario-tests</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>integration-test</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<skip>${skipScenarioTests}</skip>\n\t\t\t\t\t\t\t<argLine>@{failsafeScenarioArgLine} ${JVM_OPTS}</argLine>\n\t\t\t\t\t\t\t<groups>scenario</groups>\n\t\t\t\t\t\t\t<excludedGroups>integration,unit</excludedGroups>\n\t\t\t\t\t\t\t<includes>\n\t\t\t\t\t\t\t\t<include>**/*IT.java</include>\n\t\t\t\t\t\t\t\t<include>**/*ITs.java</include>\n\t\t\t\t\t\t\t</includes>\n\t\t\t\t\t\t\t<excludes>\n\t\t\t\t\t\t\t\t<!-- ensure plugin level excludes are overridden -->\n\t\t\t\t\t\t\t</excludes>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<!-- Verify phase should run once after both IT executions -->\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>it-verify</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>verify</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t<version>3.4.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<attach>true</attach>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t<version>3.12.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<source>8</source><!-- Until JDK 11+ -->\n\t\t\t\t\t<detectJavaApiLink>false</detectJavaApiLink><!-- Until JDK 11+ -->\n\t\t\t\t\t<!--<doclint>none</doclint>-->\n\t\t\t\t\t<!--<doclint>all,-missing</doclint>-->\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>attach-javadoc</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t<version>3.3.1</version>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.sonatype.central</groupId>\n\t\t\t\t<artifactId>central-publishing-maven-plugin</artifactId>\n\t\t\t\t<version>0.10.0</version>\n\t\t\t\t<extensions>true</extensions>\n\t\t\t\t<configuration>\n\t\t\t\t\t<publishingServerId>central</publishingServerId>\n\t\t\t\t\t<autoPublish>true</autoPublish>\n\t\t\t\t\t<waitUntil>published</waitUntil>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>net.revelc.code.formatter</groupId>\n\t\t\t\t<artifactId>formatter-maven-plugin</artifactId>\n\t\t\t\t<version>2.16.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<configFile>${project.basedir}/hbase-formatter.xml</configFile>\n\t\t\t\t\t<directories>\n\t\t\t\t\t\t<directory>${project.basedir}</directory>\n\t\t\t\t\t</directories>\n\t\t\t\t\t<includes>\n\t\t\t\t\t\t<!-- Specific files -->\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedis/annots/*.java</include>\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedis/params/XCfgSetParams.java</include>\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java</include>\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedisargs/StreamDeletionPolicy.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/commands/StreamsCommandsTestBase.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/params/XCfgSetParamsTest.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/commands/jedis/ClusterStreamsCommandsTest.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/commands/jedis/PooledStreamsCommandsTest.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java</include>\n                        <include>src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsStringCommandsTest.java</include>\n                        <include>src/test/java/redis/clients/jedis/commands/jedis/BinaryValuesCommandsTest.java</include>\n                        <include>src/test/java/redis/clients/jedis/commands/jedis/StringValuesCommandsTest.java</include>\n                        <include>src/test/java/redis/clients/jedis/commands/unified/BinaryValuesCommandsTestBase.java</include>\n                        <include>src/test/java/redis/clients/jedis/commands/unified/StringValuesCommandsTestBase.java</include>\n                        <include>src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStringValuesCommandsTest.java</include>\n\t\t\t\t\t\t<include>**/VectorSet*.java</include>\n\t\t\t\t\t\t<include>**/VectorTestUtils.java</include>\n\t\t\t\t\t\t<include>**/VAddParams.java</include>\n\t\t\t\t\t\t<include>**/VSimParams.java</include>\n\t\t\t\t\t\t<include>**/VSimScoreAttribs.java</include>\n\t\t\t\t\t\t<include>**/*FunctionCommandsTest*.java</include>\n\t\t\t\t\t\t<include>**/Endpoint.java</include>\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedis/mcf/*.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/failover/*.java</include>\n\t\t\t\t\t\t<include>src/test/java/redis/clients/jedis/mcf/*.java</include>\n\t\t\t\t\t\t<include>**/Health*.java</include>\n\t\t\t\t\t\t<include>**/*IT.java</include>\n\t\t\t\t\t\t<include>**/scenario/RestEndpointUtil.java</include>\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedis/MultiDbConfig.java</include>\n\t\t\t\t\t\t<include>src/main/java/redis/clients/jedis/HostAndPort.java</include>\n\t\t\t\t\t\t<include>**/builders/*.java</include>\n\t\t\t\t\t\t<include>**/MultiDb*.java</include>\n\t\t\t\t\t\t<include>**/*CommandFlags*.java</include>\n\t\t\t\t\t\t<include>**/*CompareCondition*.java</include>\n\t\t\t\t\t\t<include>**/*MSetExParams*.java</include>\n\t\t\t\t\t\t<include>**/*UnitTest.java</include>\n\t\t\t\t\t\t<include>**/DriverInfo.java</include>\n\t\t\t\t\t\t<include>**/ClientSetInfo*.java</include>\n\t\t\t\t\t\t<include>**/ClientCommandsTest*.java</include>\n\t\t\t\t\t\t<include>**/Delay*.java</include>\n\t\t\t\t\t\t<include>**/SentineledConnectionProviderReconnectionTest.java</include>\n\t\t\t\t\t\t<include>**/search/Apply.java</include>\n\t\t\t\t\t\t<include>**/search/Combiner.java</include>\n\t\t\t\t\t\t<include>**/search/Combiners.java</include>\n\t\t\t\t\t\t<include>**/search/Filter.java</include>\n\t\t\t\t\t\t<include>**/search/Limit.java</include>\n\t\t\t\t\t\t<include>**/search/Scorer.java</include>\n\t\t\t\t\t\t<include>**/search/Scorers.java</include>\n\t\t\t\t\t\t<include>**/search/hybrid/*.java</include>\n\t\t\t\t\t\t<include>**/search/FTHybrid*.java</include>\n\t\t\t\t\t\t<include>**/*Hotkeys*.java</include>\n\t\t\t\t\t\t<include>**/TestDataUtil*.java</include>\n\t\t\t\t\t\t<include>**/*JedisClientConfig*.java</include>\n\n\t\t\t\t\t\t<include>**/resps/LibraryInfoTest.java</include>\n\t\t\t\t\t\t<include>**/*Matchers.java</include>\n\t\t\t\t\t\t<include>**/*TestUtil.java</include>\n\t\t\t\t\t\t<include>**/executors/aggregators/*.java</include>\n\t\t\t\t\t\t<include>**/*MapMatcher.java</include>\n\t\t\t\t\t</includes>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>validate</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-jar-plugin</artifactId>\n\t\t\t\t<version>3.5.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<archive>\n\t\t\t\t\t\t<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>\n\t\t\t\t\t\t<manifestEntries>\n\t\t\t\t\t\t\t<Automatic-Module-Name>${jedis.module.name}</Automatic-Module-Name>\n\t\t\t\t\t\t</manifestEntries>\n\t\t\t\t\t</archive>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.felix</groupId>\n\t\t\t\t<artifactId>maven-bundle-plugin</artifactId>\n\t\t\t\t<version>5.1.9</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>bundle-manifest</id>\n\t\t\t\t\t\t<phase>process-classes</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>manifest</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<!--Sign the components - this is required by maven central for releases -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>3.2.8</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<gpgArguments>\n\t\t\t\t\t\t\t\t<arg>--pinentry-mode</arg>\n\t\t\t\t\t\t\t\t<arg>loopback</arg>\n\t\t\t\t\t\t\t</gpgArguments>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>doctests</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<artifactId>maven-surefire-plugin</artifactId>\n\t\t\t\t\t\t<version>${maven.surefire.version}</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<test>**/examples/*Example.java</test>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>tests-with-params</id>\n\t\t\t<activation>\n\t\t\t\t<property>\n\t\t\t\t\t<name>with-param-names</name>\n\t\t\t\t\t<value>true</value>\n\t\t\t\t</property>\n\t\t\t</activation>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t\t\t<version>3.15.0</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<source>1.8</source>\n\t\t\t\t\t\t\t<target>1.8</target>\n\t\t\t\t\t\t\t<parameters>true</parameters>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<artifactId>maven-surefire-plugin</artifactId>\n\t\t\t\t\t\t<version>${maven.surefire.version}</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<argLine>@{argLine} ${JVM_OPTS}</argLine>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>scenario-tests</id>\n\t\t\t<properties>\n\t\t\t\t<skipUnitTests>true</skipUnitTests>\n\t\t\t\t<skipIntegrationTests>true</skipIntegrationTests>\n\t\t\t\t<skipScenarioTests>false</skipScenarioTests>\n\t\t\t</properties>\n\t\t</profile>\n\n\t</profiles>\n</project>\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/AbstractPipeline.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.Closeable;\n\npublic abstract class AbstractPipeline extends PipeliningBase implements Closeable {\n\n  protected AbstractPipeline(CommandObjects commandObjects) {\n    super(commandObjects);\n  }\n\n  @Override\n  public abstract void close();\n\n  /**\n   * Synchronize pipeline by reading all responses.\n   */\n  public abstract void sync();\n\n  public Response<Long> publish(String channel, String message) {\n    return appendCommand(commandObjects.publish(channel, message));\n  }\n\n  public Response<Long> publish(byte[] channel, byte[] message) {\n    return appendCommand(commandObjects.publish(channel, message));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/AbstractTransaction.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.Closeable;\nimport java.util.List;\n\npublic abstract class AbstractTransaction extends PipeliningBase implements Closeable {\n\n  @Deprecated\n  protected AbstractTransaction() {\n    super(new CommandObjects());\n  }\n\n  protected AbstractTransaction(CommandObjects commandObjects) {\n    super(commandObjects);\n  }\n\n  public abstract void multi();\n\n  /**\n   * Must be called before {@link AbstractTransaction#multi() MULTI}.\n   */\n  public abstract String watch(final String... keys);\n\n  /**\n   * Must be called before {@link AbstractTransaction#multi() MULTI}.\n   */\n  public abstract String watch(final byte[]... keys);\n\n  public abstract String unwatch();\n\n  @Override public abstract void close();\n\n  public abstract List<Object> exec();\n\n  public abstract String discard();\n\n  public Response<Long> waitReplicas(int replicas, long timeout) {\n    return appendCommand(commandObjects.waitReplicas(replicas, timeout));\n  }\n\n  public Response<Long> publish(String channel, String message) {\n    return appendCommand(commandObjects.publish(channel, message));\n  }\n\n  public Response<Long> publish(byte[] channel, byte[] message) {\n    return appendCommand(commandObjects.publish(channel, message));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/BinaryJedisPubSub.java",
    "content": "package redis.clients.jedis;\n\npublic abstract class BinaryJedisPubSub extends JedisPubSubBase<byte[]> {\n\n  @Override\n  protected final byte[] encode(byte[] raw) {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/BinaryJedisShardedPubSub.java",
    "content": "package redis.clients.jedis;\n\npublic abstract class BinaryJedisShardedPubSub extends JedisShardedPubSubBase<byte[]> {\n\n  @Override\n  protected final byte[] encode(byte[] raw) {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Builder.java",
    "content": "package redis.clients.jedis;\n\npublic abstract class Builder<T> {\n\n  public abstract T build(Object data);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/BuilderFactory.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.Serializable;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.resps.LCSMatchResult.MatchedPosition;\nimport redis.clients.jedis.resps.LCSMatchResult.Position;\nimport redis.clients.jedis.util.*;\n\npublic final class BuilderFactory {\n\n  public static final Builder<Object> RAW_OBJECT = new Builder<Object>() {\n    @Override\n    public Object build(Object data) {\n      return data;\n    }\n\n    @Override\n    public String toString() {\n      return \"Object\";\n    }\n  };\n\n  public static final Builder<List<Object>> RAW_OBJECT_LIST = new Builder<List<Object>>() {\n    @Override\n    public List<Object> build(Object data) {\n      return (List<Object>) data;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Object>\";\n    }\n  };\n\n  public static final Builder<Object> ENCODED_OBJECT = new Builder<Object>() {\n    @Override\n    public Object build(Object data) {\n      return SafeEncoder.encodeObject(data);\n    }\n\n    @Override\n    public String toString() {\n      return \"Object\";\n    }\n  };\n\n  public static final Builder<List<Object>> ENCODED_OBJECT_LIST = new Builder<List<Object>>() {\n    @Override\n    public List<Object> build(Object data) {\n      return (List<Object>) SafeEncoder.encodeObject(data);\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Object>\";\n    }\n  };\n\n  public static final Builder<Long> LONG = new Builder<Long>() {\n    @Override\n    public Long build(Object data) {\n      return (Long) data;\n    }\n\n    @Override\n    public String toString() {\n      return \"Long\";\n    }\n\n  };\n\n  public static final Builder<List<Long>> LONG_LIST = new Builder<List<Long>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Long> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      return (List<Long>) data;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Long>\";\n    }\n\n  };\n\n  public static final Builder<Double> DOUBLE = new Builder<Double>() {\n    @Override\n    public Double build(Object data) {\n      if (data == null) return null;\n      else if (data instanceof Double) return (Double) data;\n      else return DoublePrecision.parseFloatingPointNumber(STRING.build(data));\n    }\n\n    @Override\n    public String toString() {\n      return \"Double\";\n    }\n  };\n\n  public static final Builder<List<Double>> DOUBLE_LIST = new Builder<List<Double>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Double> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(DOUBLE::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Double>\";\n    }\n  };\n\n  public static final Builder<Boolean> BOOLEAN = new Builder<Boolean>() {\n    @Override\n    public Boolean build(Object data) {\n      if (data == null) return null;\n      else if (data instanceof Boolean) return (Boolean) data;\n      return ((Long) data) == 1L;\n    }\n\n    @Override\n    public String toString() {\n      return \"Boolean\";\n    }\n  };\n\n  public static final Builder<List<Boolean>> BOOLEAN_LIST = new Builder<List<Boolean>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Boolean> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(BOOLEAN::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Boolean>\";\n    }\n  };\n\n  public static final Builder<List<Boolean>> BOOLEAN_WITH_ERROR_LIST = new Builder<List<Boolean>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Boolean> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream()\n          //.map((val) -> (val instanceof JedisDataException) ? val : BOOLEAN.build(val))\n          .map((val) -> (val instanceof JedisDataException) ? null : BOOLEAN.build(val))\n          .collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Boolean>\";\n    }\n  };\n\n  public static final Builder<byte[]> BINARY = new Builder<byte[]>() {\n    @Override\n    public byte[] build(Object data) {\n      return (byte[]) data;\n    }\n\n    @Override\n    public String toString() {\n      return \"byte[]\";\n    }\n  };\n\n  public static final Builder<List<byte[]>> BINARY_LIST = new Builder<List<byte[]>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<byte[]> build(Object data) {\n      return (List<byte[]>) data;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<byte[]>\";\n    }\n  };\n\n  public static final Builder<Set<byte[]>> BINARY_SET = new Builder<Set<byte[]>>() {\n    @Override\n    public Set<byte[]> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<byte[]> l = BINARY_LIST.build(data);\n      return SetFromList.of(l);\n    }\n\n    @Override\n    public String toString() {\n      return \"Set<byte[]>\";\n    }\n  };\n\n  public static final Builder<List<Map.Entry<byte[], byte[]>>> BINARY_PAIR_LIST\n      = new Builder<List<Map.Entry<byte[], byte[]>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map.Entry<byte[], byte[]>> build(Object data) {\n      final List<byte[]> flatHash = (List<byte[]>) data;\n      final List<Map.Entry<byte[], byte[]>> pairList = new ArrayList<>();\n      final Iterator<byte[]> iterator = flatHash.iterator();\n      while (iterator.hasNext()) {\n        pairList.add(new AbstractMap.SimpleEntry<>(iterator.next(), iterator.next()));\n      }\n\n      return pairList;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Map.Entry<byte[], byte[]>>\";\n    }\n  };\n\n  public static final Builder<List<Map.Entry<byte[], byte[]>>> BINARY_PAIR_LIST_FROM_PAIRS\n      = new Builder<List<Map.Entry<byte[], byte[]>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map.Entry<byte[], byte[]>> build(Object data) {\n      final List<Object> list = (List<Object>) data;\n      final List<Map.Entry<byte[], byte[]>> pairList = new ArrayList<>();\n      for (Object object : list) {\n        final List<byte[]> flat = (List<byte[]>) object;\n        pairList.add(new AbstractMap.SimpleEntry<>(flat.get(0), flat.get(1)));\n      }\n\n      return pairList;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Map.Entry<byte[], byte[]>>\";\n    }\n  };\n\n  public static final Builder<String> STRING = new Builder<String>() {\n    @Override\n    public String build(Object data) {\n      return data == null ? null : SafeEncoder.encode((byte[]) data);\n    }\n\n    @Override\n    public String toString() {\n      return \"String\";\n    }\n  };\n\n  public static final Builder<List<String>> STRING_LIST = new Builder<List<String>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<String> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(STRING::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<String>\";\n    }\n  };\n\n  public static final Builder<Set<String>> STRING_SET = new Builder<Set<String>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Set<String> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(STRING::build).collect(Collectors.toSet());\n    }\n\n    @Override\n    public String toString() {\n      return \"Set<String>\";\n    }\n  };\n\n  public static final Builder<Map<byte[], byte[]>> BINARY_MAP = new Builder<Map<byte[], byte[]>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<byte[], byte[]> build(Object data) {\n      final List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<byte[], byte[]> map = new JedisByteHashMap();\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          KeyValue kv = (KeyValue) iterator.next();\n          map.put(BINARY.build(kv.getKey()), BINARY.build(kv.getValue()));\n        }\n        return map;\n      } else {\n        final Map<byte[], byte[]> map = new JedisByteHashMap();\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(BINARY.build(iterator.next()), BINARY.build(iterator.next()));\n        }\n        return map;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<byte[], byte[]>\";\n    }\n  };\n\n  public static final Builder<Map<String, String>> STRING_MAP = new Builder<Map<String, String>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, String> build(Object data) {\n      final List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<String, String> map = new HashMap<>(list.size(), 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          KeyValue kv = (KeyValue) iterator.next();\n          map.put(STRING.build(kv.getKey()), STRING.build(kv.getValue()));\n        }\n        return map;\n      } else {\n        final Map<String, String> map = new HashMap<>(list.size() / 2, 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(STRING.build(iterator.next()), STRING.build(iterator.next()));\n        }\n        return map;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, String>\";\n    }\n  };\n\n  public static final Builder<Map<String, Object>> ENCODED_OBJECT_MAP = new Builder<Map<String, Object>>() {\n    @Override\n    public Map<String, Object> build(Object data) {\n      if (data == null) return null;\n      final List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<String, Object> map = new HashMap<>(list.size(), 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          KeyValue kv = (KeyValue) iterator.next();\n          map.put(STRING.build(kv.getKey()), ENCODED_OBJECT.build(kv.getValue()));\n        }\n        return map;\n      } else {\n        final Map<String, Object> map = new HashMap<>(list.size() / 2, 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(STRING.build(iterator.next()), ENCODED_OBJECT.build(iterator.next()));\n        }\n        return map;\n      }\n    }\n  };\n\n  public static final Builder<Object> AGGRESSIVE_ENCODED_OBJECT = new Builder<Object>() {\n    @Override\n    public Object build(Object data) {\n      if (data == null) return null;\n\n      if (data instanceof List) {\n        final List list = (List) data;\n        if (list.isEmpty()) {\n          return list == Protocol.PROTOCOL_EMPTY_MAP ? Collections.emptyMap() : Collections.emptyList();\n        }\n\n        if (list.get(0) instanceof KeyValue) {\n          return ((List<KeyValue>) data).stream()\n              .filter(kv -> kv != null && kv.getKey() != null && kv.getValue() != null)\n              .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()),\n                  kv -> this.build(kv.getValue())));\n        } else {\n          return list.stream().map(this::build).collect(Collectors.toList());\n        }\n      } else if (data instanceof byte[]) {\n        return STRING.build(data);\n      } else {\n        return data;\n      }\n    }\n  };\n\n  public static final Builder<Map<String, Object>> AGGRESSIVE_ENCODED_OBJECT_MAP = new Builder<Map<String, Object>>() {\n    @Override\n    public Map<String, Object> build(Object data) {\n      return (Map<String, Object>) AGGRESSIVE_ENCODED_OBJECT.build(data);\n    }\n  };\n\n  public static final Builder<List<Map.Entry<String, String>>> STRING_PAIR_LIST\n      = new Builder<List<Map.Entry<String, String>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map.Entry<String, String>> build(Object data) {\n      final List<byte[]> flatHash = (List<byte[]>) data;\n      final List<Map.Entry<String, String>> pairList = new ArrayList<>(flatHash.size() / 2);\n      final Iterator<byte[]> iterator = flatHash.iterator();\n      while (iterator.hasNext()) {\n        pairList.add(KeyValue.of(STRING.build(iterator.next()), STRING.build(iterator.next())));\n      }\n\n      return pairList;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Map.Entry<String, String>>\";\n    }\n  };\n\n  public static final Builder<List<Map.Entry<String, String>>> STRING_PAIR_LIST_FROM_PAIRS\n      = new Builder<List<Map.Entry<String, String>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map.Entry<String, String>> build(Object data) {\n      return ((List<Object>) data).stream().map(o -> (List<Object>) o)\n          .map(l -> KeyValue.of(STRING.build(l.get(0)), STRING.build(l.get(1))))\n          .collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Map.Entry<String, String>>\";\n    }\n  };\n\n  public static final Builder<Map<String, Long>> STRING_LONG_MAP = new Builder<Map<String, Long>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, Long> build(Object data) {\n      final List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<String, Long> map = new LinkedHashMap<>(list.size(), 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          KeyValue kv = (KeyValue) iterator.next();\n          map.put(STRING.build(kv.getKey()), LONG.build(kv.getValue()));\n        }\n        return map;\n      } else {\n        final Map<String, Long> map = new LinkedHashMap<>(list.size() / 2, 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(STRING.build(iterator.next()), LONG.build(iterator.next()));\n        }\n        return map;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, Long>\";\n    }\n  };\n\n  public static final Builder<Map<String, Double>> STRING_DOUBLE_MAP = new Builder<Map<String, Double>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, Double> build(Object data) {\n      final List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<String, Double> map = new LinkedHashMap<>(list.size(), 1f);\n        for (Object o : list) {\n          KeyValue<?, ?> kv = (KeyValue<?, ?>) o;\n          map.put(STRING.build(kv.getKey()), DOUBLE.build(kv.getValue()));\n        }\n        return map;\n      } else {\n        final Map<String, Double> map = new LinkedHashMap<>(list.size() / 2, 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(STRING.build(iterator.next()), DOUBLE.build(iterator.next()));\n        }\n        return map;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, Double>\";\n    }\n  };\n\n\n  public static final Builder<Map<byte[], Double>> BINARY_DOUBLE_MAP = new Builder<Map<byte[], Double>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<byte[], Double> build(Object data) {\n      final List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      final JedisByteMap<Double> map = new JedisByteMap<>();\n      if (list.get(0) instanceof KeyValue) {\n        for (Object o : list) {\n          KeyValue<?, ?> kv = (KeyValue<?, ?>) o;\n          map.put(BINARY.build(kv.getKey()), DOUBLE.build(kv.getValue()));\n        }\n        return map;\n      } else {\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(BINARY.build(iterator.next()), DOUBLE.build(iterator.next()));\n        }\n        return map;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, Double>\";\n    }\n  };\n  public static final Builder<KeyValue<String, String>> KEYED_ELEMENT = new Builder<KeyValue<String, String>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<String, String> build(Object data) {\n      if (data == null) return null;\n      List<Object> l = (List<Object>) data;\n      return KeyValue.of(STRING.build(l.get(0)), STRING.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<String, String>\";\n    }\n  };\n\n  public static final Builder<KeyValue<byte[], byte[]>> BINARY_KEYED_ELEMENT = new Builder<KeyValue<byte[], byte[]>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<byte[], byte[]> build(Object data) {\n      if (data == null) return null;\n      List<Object> l = (List<Object>) data;\n      return KeyValue.of(BINARY.build(l.get(0)), BINARY.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<byte[], byte[]>\";\n    }\n  };\n\n  public static final Builder<KeyValue<Long, Double>> ZRANK_WITHSCORE_PAIR = new Builder<KeyValue<Long, Double>>() {\n    @Override\n    public KeyValue<Long, Double> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n      List<Object> l = (List<Object>) data;\n      return new KeyValue<>(LONG.build(l.get(0)), DOUBLE.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<Long, Double>\";\n    }\n  };\n\n  public static final Builder<KeyValue<String, List<String>>> KEYED_STRING_LIST\n      = new Builder<KeyValue<String, List<String>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<String, List<String>> build(Object data) {\n      if (data == null) return null;\n      List<byte[]> l = (List<byte[]>) data;\n      return new KeyValue<>(STRING.build(l.get(0)), STRING_LIST.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<String, List<String>>\";\n    }\n  };\n\n  public static final Builder<KeyValue<Long, Long>> LONG_LONG_PAIR = new Builder<KeyValue<Long, Long>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<Long, Long> build(Object data) {\n      if (data == null) return null;\n      List<Object> dataList = (List<Object>) data;\n      return new KeyValue<>(LONG.build(dataList.get(0)), LONG.build(dataList.get(1)));\n    }\n  };\n\n  public static final Builder<List<KeyValue<String, List<String>>>> KEYED_STRING_LIST_LIST\n      = new Builder<List<KeyValue<String, List<String>>>>() {\n    @Override\n    public List<KeyValue<String, List<String>>> build(Object data) {\n      List<Object> list = (List<Object>) data;\n      return list.stream().map(KEYED_STRING_LIST::build).collect(Collectors.toList());\n    }\n  };\n\n  public static final Builder<KeyValue<byte[], List<byte[]>>> KEYED_BINARY_LIST\n      = new Builder<KeyValue<byte[], List<byte[]>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<byte[], List<byte[]>> build(Object data) {\n      if (data == null) return null;\n      List<byte[]> l = (List<byte[]>) data;\n      return new KeyValue<>(BINARY.build(l.get(0)), BINARY_LIST.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<byte[], List<byte[]>>\";\n    }\n  };\n\n  public static final Builder<Tuple> TUPLE = new Builder<Tuple>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Tuple build(Object data) {\n      List<byte[]> l = (List<byte[]>) data; // never null\n      if (l.isEmpty()) {\n        return null;\n      }\n      return new Tuple(l.get(0), DOUBLE.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"Tuple\";\n    }\n  };\n\n  public static final Builder<KeyValue<String, Tuple>> KEYED_TUPLE = new Builder<KeyValue<String, Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<String, Tuple> build(Object data) {\n      if (data == null) return null;\n      List<Object> l = (List<Object>) data;\n      if (l.isEmpty()) return null;\n      return KeyValue.of(STRING.build(l.get(0)), new Tuple(BINARY.build(l.get(1)), DOUBLE.build(l.get(2))));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<String, Tuple>\";\n    }\n  };\n\n  public static final Builder<KeyValue<byte[], Tuple>> BINARY_KEYED_TUPLE = new Builder<KeyValue<byte[], Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<byte[], Tuple> build(Object data) {\n      if (data == null) return null;\n      List<Object> l = (List<Object>) data;\n      if (l.isEmpty()) return null;\n      return KeyValue.of(BINARY.build(l.get(0)), new Tuple(BINARY.build(l.get(1)), DOUBLE.build(l.get(2))));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<byte[], Tuple>\";\n    }\n  };\n\n  public static final Builder<List<Tuple>> TUPLE_LIST = new Builder<List<Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Tuple> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<byte[]> l = (List<byte[]>) data;\n      final List<Tuple> result = new ArrayList<>(l.size() / 2);\n      Iterator<byte[]> iterator = l.iterator();\n      while (iterator.hasNext()) {\n        result.add(new Tuple(iterator.next(), DOUBLE.build(iterator.next())));\n      }\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Tuple>\";\n    }\n  };\n\n  public static final Builder<List<Tuple>> TUPLE_LIST_RESP3 = new Builder<List<Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Tuple> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(TUPLE::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Tuple>\";\n    }\n  };\n\n  @Deprecated\n  public static final Builder<Set<Tuple>> TUPLE_ZSET = new Builder<Set<Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Set<Tuple> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<byte[]> l = (List<byte[]>) data;\n      final Set<Tuple> result = new LinkedHashSet<>(l.size() / 2, 1);\n      Iterator<byte[]> iterator = l.iterator();\n      while (iterator.hasNext()) {\n        result.add(new Tuple(iterator.next(), DOUBLE.build(iterator.next())));\n      }\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return \"ZSet<Tuple>\";\n    }\n  };\n\n  @Deprecated\n  public static final Builder<Set<Tuple>> TUPLE_ZSET_RESP3 = new Builder<Set<Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Set<Tuple> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(TUPLE::build).collect(Collectors.toCollection(LinkedHashSet::new));\n    }\n\n    @Override\n    public String toString() {\n      return \"ZSet<Tuple>\";\n    }\n  };\n\n  private static final Builder<List<Tuple>> TUPLE_LIST_FROM_PAIRS = new Builder<List<Tuple>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Tuple> build(Object data) {\n      if (data == null) return null;\n      return ((List<List<Object>>) data).stream().map(TUPLE::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Tuple>\";\n    }\n  };\n\n  public static final Builder<KeyValue<String, List<Tuple>>> KEYED_TUPLE_LIST\n      = new Builder<KeyValue<String, List<Tuple>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<String, List<Tuple>> build(Object data) {\n      if (data == null) return null;\n      List<Object> l = (List<Object>) data;\n      return new KeyValue<>(STRING.build(l.get(0)), TUPLE_LIST_FROM_PAIRS.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<String, List<Tuple>>\";\n    }\n  };\n\n  public static final Builder<KeyValue<byte[], List<Tuple>>> BINARY_KEYED_TUPLE_LIST\n      = new Builder<KeyValue<byte[], List<Tuple>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public KeyValue<byte[], List<Tuple>> build(Object data) {\n      if (data == null) return null;\n      List<Object> l = (List<Object>) data;\n      return new KeyValue<>(BINARY.build(l.get(0)), TUPLE_LIST_FROM_PAIRS.build(l.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"KeyValue<byte[], List<Tuple>>\";\n    }\n  };\n\n  public static final Builder<ScanResult<String>> SCAN_RESPONSE = new Builder<ScanResult<String>>() {\n    @Override\n    public ScanResult<String> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      String newcursor = new String((byte[]) result.get(0));\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      List<String> results = new ArrayList<>(rawResults.size());\n      for (byte[] bs : rawResults) {\n        results.add(SafeEncoder.encode(bs));\n      }\n      return new ScanResult<>(newcursor, results);\n    }\n  };\n\n  public static final Builder<ScanResult<Map.Entry<String, String>>> HSCAN_RESPONSE\n      = new Builder<ScanResult<Map.Entry<String, String>>>() {\n    @Override\n    public ScanResult<Map.Entry<String, String>> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      String newcursor = new String((byte[]) result.get(0));\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      List<Map.Entry<String, String>> results = new ArrayList<>(rawResults.size() / 2);\n      Iterator<byte[]> iterator = rawResults.iterator();\n      while (iterator.hasNext()) {\n        results.add(new AbstractMap.SimpleEntry<>(SafeEncoder.encode(iterator.next()),\n            SafeEncoder.encode(iterator.next())));\n      }\n      return new ScanResult<>(newcursor, results);\n    }\n  };\n\n  public static final Builder<ScanResult<String>> SSCAN_RESPONSE = new Builder<ScanResult<String>>() {\n    @Override\n    public ScanResult<String> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      String newcursor = new String((byte[]) result.get(0));\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      List<String> results = new ArrayList<>(rawResults.size());\n      for (byte[] bs : rawResults) {\n        results.add(SafeEncoder.encode(bs));\n      }\n      return new ScanResult<>(newcursor, results);\n    }\n  };\n\n  public static final Builder<ScanResult<Tuple>> ZSCAN_RESPONSE = new Builder<ScanResult<Tuple>>() {\n    @Override\n    public ScanResult<Tuple> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      String newcursor = new String((byte[]) result.get(0));\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      List<Tuple> results = new ArrayList<>(rawResults.size() / 2);\n      Iterator<byte[]> iterator = rawResults.iterator();\n      while (iterator.hasNext()) {\n        results.add(new Tuple(iterator.next(), BuilderFactory.DOUBLE.build(iterator.next())));\n      }\n      return new ScanResult<>(newcursor, results);\n    }\n  };\n\n  public static final Builder<ScanResult<byte[]>> SCAN_BINARY_RESPONSE = new Builder<ScanResult<byte[]>>() {\n    @Override\n    public ScanResult<byte[]> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      byte[] newcursor = (byte[]) result.get(0);\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      return new ScanResult<>(newcursor, rawResults);\n    }\n  };\n\n  public static final Builder<ScanResult<Map.Entry<byte[], byte[]>>> HSCAN_BINARY_RESPONSE\n      = new Builder<ScanResult<Map.Entry<byte[], byte[]>>>() {\n    @Override\n    public ScanResult<Map.Entry<byte[], byte[]>> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      byte[] newcursor = (byte[]) result.get(0);\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      List<Map.Entry<byte[], byte[]>> results = new ArrayList<>(rawResults.size() / 2);\n      Iterator<byte[]> iterator = rawResults.iterator();\n      while (iterator.hasNext()) {\n        results.add(new AbstractMap.SimpleEntry<>(iterator.next(), iterator.next()));\n      }\n      return new ScanResult<>(newcursor, results);\n    }\n  };\n\n  public static final Builder<ScanResult<byte[]>> SSCAN_BINARY_RESPONSE = new Builder<ScanResult<byte[]>>() {\n    @Override\n    public ScanResult<byte[]> build(Object data) {\n      List<Object> result = (List<Object>) data;\n      byte[] newcursor = (byte[]) result.get(0);\n      List<byte[]> rawResults = (List<byte[]>) result.get(1);\n      return new ScanResult<>(newcursor, rawResults);\n    }\n  };\n\n  public static final Builder<Map<String, Long>> PUBSUB_NUMSUB_MAP = new Builder<Map<String, Long>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, Long> build(Object data) {\n      final List<Object> flatHash = (List<Object>) data;\n      final Map<String, Long> hash = new HashMap<>(flatHash.size() / 2, 1f);\n      final Iterator<Object> iterator = flatHash.iterator();\n      while (iterator.hasNext()) {\n        hash.put(SafeEncoder.encode((byte[]) iterator.next()), (Long) iterator.next());\n      }\n      return hash;\n    }\n\n    @Override\n    public String toString() {\n      return \"PUBSUB_NUMSUB_MAP<String, String>\";\n    }\n  };\n\n  public static final Builder<List<GeoCoordinate>> GEO_COORDINATE_LIST = new Builder<List<GeoCoordinate>>() {\n    @Override\n    public List<GeoCoordinate> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      return interpretGeoposResult((List<Object>) data);\n    }\n\n    @Override\n    public String toString() {\n      return \"List<GeoCoordinate>\";\n    }\n\n    private List<GeoCoordinate> interpretGeoposResult(List<Object> responses) {\n      List<GeoCoordinate> responseCoordinate = new ArrayList<>(responses.size());\n      for (Object response : responses) {\n        if (response == null) {\n          responseCoordinate.add(null);\n        } else {\n          List<Object> respList = (List<Object>) response;\n          GeoCoordinate coord = new GeoCoordinate(DOUBLE.build(respList.get(0)),\n              DOUBLE.build(respList.get(1)));\n          responseCoordinate.add(coord);\n        }\n      }\n      return responseCoordinate;\n    }\n  };\n\n  public static final Builder<List<GeoRadiusResponse>> GEORADIUS_WITH_PARAMS_RESULT = new Builder<List<GeoRadiusResponse>>() {\n    @Override\n    public List<GeoRadiusResponse> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<Object> objectList = (List<Object>) data;\n\n      List<GeoRadiusResponse> responses = new ArrayList<>(objectList.size());\n      if (objectList.isEmpty()) {\n        return responses;\n      }\n\n      if (objectList.get(0) instanceof List<?>) {\n        // list of members with additional informations\n        GeoRadiusResponse resp;\n        for (Object obj : objectList) {\n          List<Object> informations = (List<Object>) obj;\n\n          resp = new GeoRadiusResponse((byte[]) informations.get(0));\n\n          int size = informations.size();\n          for (int idx = 1; idx < size; idx++) {\n            Object info = informations.get(idx);\n            if (info instanceof List<?>) {\n              // coordinate\n              List<Object> coord = (List<Object>) info;\n\n              resp.setCoordinate(new GeoCoordinate(DOUBLE.build(coord.get(0)),\n                  DOUBLE.build(coord.get(1))));\n            } else if (info instanceof Long) {\n              // score\n              resp.setRawScore(LONG.build(info));\n            } else {\n              // distance\n              resp.setDistance(DOUBLE.build(info));\n            }\n          }\n\n          responses.add(resp);\n        }\n      } else {\n        // list of members\n        for (Object obj : objectList) {\n          responses.add(new GeoRadiusResponse((byte[]) obj));\n        }\n      }\n\n      return responses;\n    }\n\n    @Override\n    public String toString() {\n      return \"GeoRadiusWithParamsResult\";\n    }\n  };\n\n  public static final Builder<Map<String, CommandDocument>> COMMAND_DOCS_RESPONSE = new Builder<Map<String, CommandDocument>>() {\n    @Override\n    public Map<String, CommandDocument> build(Object data) {\n      if (data == null) return null;\n      List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<String, CommandDocument> map = new HashMap<>(list.size(), 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          KeyValue kv = (KeyValue) iterator.next();\n          map.put(STRING.build(kv.getKey()), new CommandDocument(ENCODED_OBJECT_MAP.build(kv.getValue())));\n        }\n        return map;\n      } else {\n        final Map<String, CommandDocument> map = new HashMap<>(list.size() / 2, 1f);\n        final Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n          map.put(STRING.build(iterator.next()), new CommandDocument(ENCODED_OBJECT_MAP.build(iterator.next())));\n        }\n        return map;\n      }\n    }\n  };\n\n  @Deprecated\n  public static final Builder<Map<String, CommandInfo>> COMMAND_INFO_RESPONSE = CommandInfo.COMMAND_INFO_RESPONSE;\n\n  public static final Builder<Map<String, LatencyLatestInfo>> LATENCY_LATEST_RESPONSE = new Builder<Map<String, LatencyLatestInfo>>() {\n    @Override\n    public Map<String, LatencyLatestInfo> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<Object> rawList = (List<Object>) data;\n      Map<String, LatencyLatestInfo> map = new HashMap<>(rawList.size());\n\n      for (Object rawLatencyLatestInfo : rawList) {\n        if (rawLatencyLatestInfo == null) {\n          continue;\n        }\n\n        LatencyLatestInfo latestInfo = LatencyLatestInfo.LATENCY_LATEST_BUILDER.build(rawLatencyLatestInfo);\n        String name = latestInfo.getCommand();\n        map.put(name, latestInfo);\n      }\n\n      return map;\n    }\n  };\n\n  public static final Builder<List<LatencyHistoryInfo>> LATENCY_HISTORY_RESPONSE = new Builder<List<LatencyHistoryInfo>>() {\n    @Override\n    public List<LatencyHistoryInfo> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<Object> rawList = (List<Object>) data;\n      List<LatencyHistoryInfo> response = new ArrayList<>(rawList.size());\n\n      for (Object rawLatencyHistoryInfo : rawList) {\n        if (rawLatencyHistoryInfo == null) {\n          continue;\n        }\n\n        LatencyHistoryInfo historyInfo = LatencyHistoryInfo.LATENCY_HISTORY_BUILDER.build(rawLatencyHistoryInfo);\n        response.add(historyInfo);\n      }\n\n      return response;\n    }\n  };\n\n  private static final Builder<List<List<Long>>> CLUSTER_SHARD_SLOTS_RANGES = new Builder<List<List<Long>>>() {\n\n    @Override\n    public List<List<Long>> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Long> rawSlots = (List<Long>) data;\n      List<List<Long>> slotsRanges = new ArrayList<>();\n      for (int i = 0; i < rawSlots.size(); i += 2) {\n        slotsRanges.add(Arrays.asList(rawSlots.get(i), rawSlots.get(i + 1)));\n      }\n      return slotsRanges;\n    }\n  };\n\n  private static final Builder<List<ClusterShardNodeInfo>> CLUSTER_SHARD_NODE_INFO_LIST\n      = new Builder<List<ClusterShardNodeInfo>>() {\n\n    final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(ClusterShardNodeInfo.ID, STRING);\n      tempMappingFunctions.put(ClusterShardNodeInfo.ENDPOINT, STRING);\n      tempMappingFunctions.put(ClusterShardNodeInfo.IP, STRING);\n      tempMappingFunctions.put(ClusterShardNodeInfo.HOSTNAME, STRING);\n      tempMappingFunctions.put(ClusterShardNodeInfo.PORT, LONG);\n      tempMappingFunctions.put(ClusterShardNodeInfo.TLS_PORT, LONG);\n      tempMappingFunctions.put(ClusterShardNodeInfo.ROLE, STRING);\n      tempMappingFunctions.put(ClusterShardNodeInfo.REPLICATION_OFFSET, LONG);\n      tempMappingFunctions.put(ClusterShardNodeInfo.HEALTH, STRING);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<ClusterShardNodeInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<ClusterShardNodeInfo> response = new ArrayList<>();\n\n      List<Object> clusterShardNodeInfos = (List<Object>) data;\n      for (Object clusterShardNodeInfoObject : clusterShardNodeInfos) {\n        List<Object> clusterShardNodeInfo = (List<Object>) clusterShardNodeInfoObject;\n        Iterator<Object> iterator = clusterShardNodeInfo.iterator();\n        response.add(new ClusterShardNodeInfo(createMapFromDecodingFunctions(iterator, mappingFunctions)));\n      }\n\n      return response;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<ClusterShardNodeInfo>\";\n    }\n  };\n\n  public static final Builder<List<ClusterShardInfo>> CLUSTER_SHARD_INFO_LIST\n          = new Builder<List<ClusterShardInfo>>() {\n\n    final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(ClusterShardInfo.SLOTS, CLUSTER_SHARD_SLOTS_RANGES);\n      tempMappingFunctions.put(ClusterShardInfo.NODES, CLUSTER_SHARD_NODE_INFO_LIST);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<ClusterShardInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<ClusterShardInfo> response = new ArrayList<>();\n\n      List<Object> clusterShardInfos = (List<Object>) data;\n      for (Object clusterShardInfoObject : clusterShardInfos) {\n        List<Object> clusterShardInfo = (List<Object>) clusterShardInfoObject;\n        Iterator<Object> iterator = clusterShardInfo.iterator();\n        response.add(new ClusterShardInfo(createMapFromDecodingFunctions(iterator, mappingFunctions)));\n      }\n\n      return response;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<ClusterShardInfo>\";\n    }\n  };\n\n  public static final Builder<List<Module>> MODULE_LIST = new Builder<List<Module>>() {\n    @Override\n    public List<Module> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<List<Object>> objectList = (List<List<Object>>) data;\n\n      List<Module> responses = new ArrayList<>(objectList.size());\n      if (objectList.isEmpty()) {\n        return responses;\n      }\n\n      for (List<Object> moduleResp : objectList) {\n        if (moduleResp.get(0) instanceof KeyValue) {\n          responses.add(new Module(STRING.build(((KeyValue) moduleResp.get(0)).getValue()),\n              LONG.build(((KeyValue) moduleResp.get(1)).getValue()).intValue()));\n          continue;\n        }\n        Module m = new Module(SafeEncoder.encode((byte[]) moduleResp.get(1)),\n            ((Long) moduleResp.get(3)).intValue());\n        responses.add(m);\n      }\n\n      return responses;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Module>\";\n    }\n  };\n\n  /**\n   * Create a AccessControlUser object from the ACL GETUSER reply.\n   */\n  public static final Builder<AccessControlUser> ACCESS_CONTROL_USER = new Builder<AccessControlUser>() {\n    @Override\n    public AccessControlUser build(Object data) {\n      Map<String, Object> map = ENCODED_OBJECT_MAP.build(data);\n      if (map == null) return null;\n      return new AccessControlUser(map);\n    }\n\n    @Override\n    public String toString() {\n      return \"AccessControlUser\";\n    }\n  };\n\n  /**\n   * Create an Access Control Log Entry Result of ACL LOG command\n   */\n  public static final Builder<List<AccessControlLogEntry>> ACCESS_CONTROL_LOG_ENTRY_LIST\n      = new Builder<List<AccessControlLogEntry>>() {\n\n    private final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(AccessControlLogEntry.COUNT, LONG);\n      tempMappingFunctions.put(AccessControlLogEntry.REASON, STRING);\n      tempMappingFunctions.put(AccessControlLogEntry.CONTEXT, STRING);\n      tempMappingFunctions.put(AccessControlLogEntry.OBJECT, STRING);\n      tempMappingFunctions.put(AccessControlLogEntry.USERNAME, STRING);\n      tempMappingFunctions.put(AccessControlLogEntry.AGE_SECONDS, DOUBLE);\n      tempMappingFunctions.put(AccessControlLogEntry.CLIENT_INFO, STRING);\n      tempMappingFunctions.put(AccessControlLogEntry.ENTRY_ID, LONG);\n      tempMappingFunctions.put(AccessControlLogEntry.TIMESTAMP_CREATED, LONG);\n      tempMappingFunctions.put(AccessControlLogEntry.TIMESTAMP_LAST_UPDATED, LONG);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    public List<AccessControlLogEntry> build(Object data) {\n\n      if (null == data) {\n        return null;\n      }\n\n      List<AccessControlLogEntry> list = new ArrayList<>();\n      List<List<Object>> logEntries = (List<List<Object>>) data;\n      for (List<Object> logEntryData : logEntries) {\n        Iterator<Object> logEntryDataIterator = logEntryData.iterator();\n        AccessControlLogEntry accessControlLogEntry = new AccessControlLogEntry(\n            createMapFromDecodingFunctions(logEntryDataIterator, mappingFunctions,\n                BACKUP_BUILDERS_FOR_DECODING_FUNCTIONS));\n        list.add(accessControlLogEntry);\n      }\n      return list;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<AccessControlLogEntry>\";\n    }\n  };\n\n  // Stream Builders -->\n\n  public static final Builder<StreamEntryID> STREAM_ENTRY_ID = new Builder<StreamEntryID>() {\n    @Override\n    public StreamEntryID build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      String id = SafeEncoder.encode((byte[]) data);\n      return new StreamEntryID(id);\n    }\n\n    @Override\n    public String toString() {\n      return \"StreamEntryID\";\n    }\n  };\n\n  public static final Builder<List<StreamEntryID>> STREAM_ENTRY_ID_LIST = new Builder<List<StreamEntryID>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamEntryID> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<Object> objectList = (List<Object>) data;\n      List<StreamEntryID> responses = new ArrayList<>(objectList.size());\n      if (!objectList.isEmpty()) {\n        for(Object object : objectList) {\n          responses.add(STREAM_ENTRY_ID.build(object));\n        }\n      }\n      return responses;\n    }\n  };\n\n  public static final Builder<StreamEntryDeletionResult> STREAM_ENTRY_DELETION_RESULT = new Builder<StreamEntryDeletionResult>() {\n    @Override\n    public StreamEntryDeletionResult build(Object data) {\n      if (data == null) {\n        return null;\n      }\n      return StreamEntryDeletionResult.fromLong((Long) data);\n    }\n\n    @Override\n    public String toString() {\n      return \"StreamEntryDeletionResult\";\n    }\n  };\n\n  public static final Builder<List<StreamEntryDeletionResult>> STREAM_ENTRY_DELETION_RESULT_LIST = new Builder<List<StreamEntryDeletionResult>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamEntryDeletionResult> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n      List<Object> objectList = (List<Object>) data;\n      List<StreamEntryDeletionResult> responses = new ArrayList<>(objectList.size());\n      for (Object object : objectList) {\n        responses.add(STREAM_ENTRY_DELETION_RESULT.build(object));\n      }\n      return responses;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamEntryDeletionResult>\";\n    }\n  };\n\n  public static final Builder<StreamEntry> STREAM_ENTRY = new Builder<StreamEntry>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public StreamEntry build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<Object> objectList = (List<Object>) data;\n\n      if (objectList.isEmpty()) {\n        return null;\n      }\n\n      String entryIdString = SafeEncoder.encode((byte[]) objectList.get(0));\n      StreamEntryID entryID = new StreamEntryID(entryIdString);\n      List<byte[]> hash = (List<byte[]>) objectList.get(1);\n\n      Iterator<byte[]> hashIterator = hash.iterator();\n      Map<String, String> map = new LinkedHashMap<>(hash.size() / 2, 1f);\n      while (hashIterator.hasNext()) {\n        map.put(SafeEncoder.encode(hashIterator.next()), SafeEncoder.encode(hashIterator.next()));\n      }\n      return new StreamEntry(entryID, map);\n    }\n\n    @Override\n    public String toString() {\n      return \"StreamEntry\";\n    }\n  };\n\n  public static final Builder<List<StreamEntry>> STREAM_ENTRY_LIST = new Builder<List<StreamEntry>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamEntry> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<ArrayList<Object>> objectList = (List<ArrayList<Object>>) data;\n\n      List<StreamEntry> responses = new ArrayList<>(objectList.size() / 2);\n      if (objectList.isEmpty()) {\n        return responses;\n      }\n\n      for (ArrayList<Object> res : objectList) {\n        if (res == null) {\n          responses.add(null);\n          continue;\n        }\n        String entryIdString = SafeEncoder.encode((byte[]) res.get(0));\n        StreamEntryID entryID = new StreamEntryID(entryIdString);\n        List<byte[]> hash = (List<byte[]>) res.get(1);\n\n        Map<String, String> fieldsMap = null;\n\n        if (hash != null) {\n          Iterator<byte[]> hashIterator = hash.iterator();\n          fieldsMap = new LinkedHashMap<>(hash.size() / 2, 1f);\n\n          while (hashIterator.hasNext()) {\n            fieldsMap.put(SafeEncoder.encode(hashIterator.next()), SafeEncoder.encode(hashIterator.next()));\n          }\n        }\n\n        if (res.size() >= 4) {\n          Long millisElapsedFromDelivery = LONG.build(res.get(2));\n          Long deliveredCount = LONG.build(res.get(3));\n          responses.add(new StreamEntry(entryID, fieldsMap, millisElapsedFromDelivery, deliveredCount));\n          continue;\n        }\n\n        responses.add(new StreamEntry(entryID, fieldsMap));\n      }\n\n      return responses;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamEntry>\";\n    }\n  };\n\n  public static final Builder<Map.Entry<StreamEntryID, List<StreamEntry>>> STREAM_AUTO_CLAIM_RESPONSE\n      = new Builder<Map.Entry<StreamEntryID, List<StreamEntry>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map.Entry<StreamEntryID, List<StreamEntry>> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> objectList = (List<Object>) data;\n      return new AbstractMap.SimpleEntry<>(STREAM_ENTRY_ID.build(objectList.get(0)),\n          STREAM_ENTRY_LIST.build(objectList.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"Map.Entry<StreamEntryID, List<StreamEntry>>\";\n    }\n  };\n\n  public static final Builder<Map.Entry<StreamEntryID, List<StreamEntryID>>> STREAM_AUTO_CLAIM_JUSTID_RESPONSE\n      = new Builder<Map.Entry<StreamEntryID, List<StreamEntryID>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map.Entry<StreamEntryID, List<StreamEntryID>> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> objectList = (List<Object>) data;\n      return new AbstractMap.SimpleEntry<>(STREAM_ENTRY_ID.build(objectList.get(0)),\n          STREAM_ENTRY_ID_LIST.build(objectList.get(1)));\n    }\n\n    @Override\n    public String toString() {\n      return \"Map.Entry<StreamEntryID, List<StreamEntryID>>\";\n    }\n  };\n\n  /**\n   * @deprecated Use {@link BuilderFactory#STREAM_AUTO_CLAIM_JUSTID_RESPONSE}.\n   */\n  @Deprecated\n  public static final Builder<Map.Entry<StreamEntryID, List<StreamEntryID>>> STREAM_AUTO_CLAIM_ID_RESPONSE\n      = STREAM_AUTO_CLAIM_JUSTID_RESPONSE;\n\n  public static final Builder<List<Map.Entry<String, List<StreamEntry>>>> STREAM_READ_RESPONSE\n      = new Builder<List<Map.Entry<String, List<StreamEntry>>>>() {\n    @Override\n    public List<Map.Entry<String, List<StreamEntry>>> build(Object data) {\n      if (data == null) return null;\n      List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyList();\n\n      if (list.get(0) instanceof KeyValue) {\n        return ((List<KeyValue>) list).stream()\n            .map(kv -> new KeyValue<>(STRING.build(kv.getKey()),\n                STREAM_ENTRY_LIST.build(kv.getValue())))\n            .collect(Collectors.toList());\n      } else {\n        List<Map.Entry<String, List<StreamEntry>>> result = new ArrayList<>(list.size());\n        for (Object anObj : list) {\n          List<Object> streamObj = (List<Object>) anObj;\n          String streamKey = STRING.build(streamObj.get(0));\n          List<StreamEntry> streamEntries = STREAM_ENTRY_LIST.build(streamObj.get(1));\n          result.add(KeyValue.of(streamKey, streamEntries));\n        }\n        return result;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Entry<String, List<StreamEntry>>>\";\n    }\n  };\n\n  public static final Builder<Map<String, List<StreamEntry>>> STREAM_READ_MAP_RESPONSE\n      = new Builder<Map<String, List<StreamEntry>>>() {\n    @Override\n    public Map<String, List<StreamEntry>> build(Object data) {\n      if (data == null) return null;\n      List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        return ((List<KeyValue>) list).stream()\n            .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()),\n                kv -> STREAM_ENTRY_LIST.build(kv.getValue()),\n                (v1, v2) -> v1, LinkedHashMap::new));\n      } else {\n        Map<String, List<StreamEntry>> result = new LinkedHashMap<>(list.size(), 1f);\n        for (Object anObj : list) {\n          List<Object> streamObj = (List<Object>) anObj;\n          String streamKey = STRING.build(streamObj.get(0));\n          List<StreamEntry> streamEntries = STREAM_ENTRY_LIST.build(streamObj.get(1));\n          result.put(streamKey, streamEntries);\n        }\n        return result;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, List<StreamEntry>>\";\n    }\n  };\n\n  public static final Builder<List<StreamPendingEntry>> STREAM_PENDING_ENTRY_LIST = new Builder<List<StreamPendingEntry>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamPendingEntry> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> streamsEntries = (List<Object>) data;\n      List<StreamPendingEntry> result = new ArrayList<>(streamsEntries.size());\n      for (Object streamObj : streamsEntries) {\n        List<Object> stream = (List<Object>) streamObj;\n        String id = SafeEncoder.encode((byte[]) stream.get(0));\n        String consumerName = SafeEncoder.encode((byte[]) stream.get(1));\n        long idleTime = BuilderFactory.LONG.build(stream.get(2));\n        long deliveredTimes = BuilderFactory.LONG.build(stream.get(3));\n        result.add(new StreamPendingEntry(new StreamEntryID(id), consumerName, idleTime,\n            deliveredTimes));\n      }\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamPendingEntry>\";\n    }\n  };\n\n  public static final Builder<StreamInfo> STREAM_INFO = new Builder<StreamInfo>() {\n\n    Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamInfo.LAST_GENERATED_ID, STREAM_ENTRY_ID);\n      tempMappingFunctions.put(StreamInfo.FIRST_ENTRY, STREAM_ENTRY);\n      tempMappingFunctions.put(StreamInfo.LENGTH, LONG);\n      tempMappingFunctions.put(StreamInfo.RADIX_TREE_KEYS, LONG);\n      tempMappingFunctions.put(StreamInfo.RADIX_TREE_NODES, LONG);\n      tempMappingFunctions.put(StreamInfo.LAST_ENTRY, STREAM_ENTRY);\n      tempMappingFunctions.put(StreamInfo.GROUPS, LONG);\n      tempMappingFunctions.put(StreamInfo.IDMP_DURATION, LONG);\n      tempMappingFunctions.put(StreamInfo.IDMP_MAXSIZE, LONG);\n      tempMappingFunctions.put(StreamInfo.PIDS_TRACKED, LONG);\n      tempMappingFunctions.put(StreamInfo.IIDS_TRACKED, LONG);\n      tempMappingFunctions.put(StreamInfo.IIDS_ADDED, LONG);\n      tempMappingFunctions.put(StreamInfo.IIDS_DUPLICATES, LONG);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public StreamInfo build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> streamsEntries = (List<Object>) data;\n      Iterator<Object> iterator = streamsEntries.iterator();\n\n      return new StreamInfo(createMapFromDecodingFunctions(iterator, mappingFunctions));\n    }\n\n    @Override\n    public String toString() {\n      return \"StreamInfo\";\n    }\n  };\n\n  public static final Builder<List<StreamGroupInfo>> STREAM_GROUP_INFO_LIST = new Builder<List<StreamGroupInfo>>() {\n\n    Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamGroupInfo.NAME, STRING);\n      tempMappingFunctions.put(StreamGroupInfo.CONSUMERS, LONG);\n      tempMappingFunctions.put(StreamGroupInfo.PENDING, LONG);\n      tempMappingFunctions.put(StreamGroupInfo.LAST_DELIVERED, STREAM_ENTRY_ID);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamGroupInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<StreamGroupInfo> list = new ArrayList<>();\n      List<Object> streamsEntries = (List<Object>) data;\n      Iterator<Object> groupsArray = streamsEntries.iterator();\n\n      while (groupsArray.hasNext()) {\n\n        List<Object> groupInfo = (List<Object>) groupsArray.next();\n\n        Iterator<Object> groupInfoIterator = groupInfo.iterator();\n\n        StreamGroupInfo streamGroupInfo = new StreamGroupInfo(createMapFromDecodingFunctions(\n          groupInfoIterator, mappingFunctions));\n        list.add(streamGroupInfo);\n\n      }\n      return list;\n\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamGroupInfo>\";\n    }\n  };\n\n  /**\n   * @deprecated Use {@link BuilderFactory#STREAM_CONSUMER_INFO_LIST}.\n   */\n  @Deprecated\n  public static final Builder<List<StreamConsumersInfo>> STREAM_CONSUMERS_INFO_LIST\n      = new Builder<List<StreamConsumersInfo>>() {\n\n    Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamConsumersInfo.NAME, STRING);\n      tempMappingFunctions.put(StreamConsumersInfo.IDLE, LONG);\n      tempMappingFunctions.put(StreamConsumersInfo.PENDING, LONG);\n      return tempMappingFunctions;\n\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamConsumersInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<StreamConsumersInfo> list = new ArrayList<>();\n      List<Object> streamsEntries = (List<Object>) data;\n      Iterator<Object> groupsArray = streamsEntries.iterator();\n\n      while (groupsArray.hasNext()) {\n\n        List<Object> groupInfo = (List<Object>) groupsArray.next();\n\n        Iterator<Object> consumerInfoIterator = groupInfo.iterator();\n\n        StreamConsumersInfo streamGroupInfo = new StreamConsumersInfo(\n            createMapFromDecodingFunctions(consumerInfoIterator, mappingFunctions));\n        list.add(streamGroupInfo);\n\n      }\n      return list;\n\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamConsumersInfo>\";\n    }\n  };\n\n  public static final Builder<List<StreamConsumerInfo>> STREAM_CONSUMER_INFO_LIST\n      = new Builder<List<StreamConsumerInfo>>() {\n\n    Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamConsumerInfo.NAME, STRING);\n      tempMappingFunctions.put(StreamConsumerInfo.IDLE, LONG);\n      tempMappingFunctions.put(StreamConsumerInfo.PENDING, LONG);\n      return tempMappingFunctions;\n\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamConsumerInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<StreamConsumerInfo> list = new ArrayList<>();\n      List<Object> streamsEntries = (List<Object>) data;\n      Iterator<Object> groupsArray = streamsEntries.iterator();\n\n      while (groupsArray.hasNext()) {\n\n        List<Object> groupInfo = (List<Object>) groupsArray.next();\n\n        Iterator<Object> consumerInfoIterator = groupInfo.iterator();\n\n        StreamConsumerInfo streamConsumerInfo = new StreamConsumerInfo(\n            createMapFromDecodingFunctions(consumerInfoIterator, mappingFunctions));\n        list.add(streamConsumerInfo);\n      }\n\n      return list;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamConsumerInfo>\";\n    }\n  };\n\n  private static final Builder<List<StreamConsumerFullInfo>> STREAM_CONSUMER_FULL_INFO_LIST\n      = new Builder<List<StreamConsumerFullInfo>>() {\n\n    final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamConsumerFullInfo.NAME, STRING);\n      tempMappingFunctions.put(StreamConsumerFullInfo.SEEN_TIME, LONG);\n      tempMappingFunctions.put(StreamConsumerFullInfo.PEL_COUNT, LONG);\n      tempMappingFunctions.put(StreamConsumerFullInfo.PENDING, ENCODED_OBJECT_LIST);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamConsumerFullInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<StreamConsumerFullInfo> list = new ArrayList<>();\n      List<Object> streamsEntries = (List<Object>) data;\n\n      for (Object streamsEntry : streamsEntries) {\n        List<Object> consumerInfoList = (List<Object>) streamsEntry;\n        Iterator<Object> consumerInfoIterator = consumerInfoList.iterator();\n        StreamConsumerFullInfo consumerInfo = new StreamConsumerFullInfo(\n            createMapFromDecodingFunctions(consumerInfoIterator, mappingFunctions));\n        list.add(consumerInfo);\n      }\n      return list;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamConsumerFullInfo>\";\n    }\n  };\n\n  private static final Builder<List<StreamGroupFullInfo>> STREAM_GROUP_FULL_INFO_LIST\n      = new Builder<List<StreamGroupFullInfo>>() {\n\n    final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamGroupFullInfo.NAME, STRING);\n      tempMappingFunctions.put(StreamGroupFullInfo.CONSUMERS, STREAM_CONSUMER_FULL_INFO_LIST);\n      tempMappingFunctions.put(StreamGroupFullInfo.PENDING, ENCODED_OBJECT_LIST);\n      tempMappingFunctions.put(StreamGroupFullInfo.LAST_DELIVERED, STREAM_ENTRY_ID);\n      tempMappingFunctions.put(StreamGroupFullInfo.PEL_COUNT, LONG);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamGroupFullInfo> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<StreamGroupFullInfo> list = new ArrayList<>();\n      List<Object> streamsEntries = (List<Object>) data;\n\n      for (Object streamsEntry : streamsEntries) {\n\n        List<Object> groupInfo = (List<Object>) streamsEntry;\n\n        Iterator<Object> groupInfoIterator = groupInfo.iterator();\n\n        StreamGroupFullInfo groupFullInfo = new StreamGroupFullInfo(\n            createMapFromDecodingFunctions(groupInfoIterator, mappingFunctions));\n        list.add(groupFullInfo);\n\n      }\n      return list;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamGroupFullInfo>\";\n    }\n  };\n\n  public static final Builder<StreamFullInfo> STREAM_FULL_INFO = new Builder<StreamFullInfo>() {\n\n    final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(StreamFullInfo.LAST_GENERATED_ID, STREAM_ENTRY_ID);\n      tempMappingFunctions.put(StreamFullInfo.LENGTH, LONG);\n      tempMappingFunctions.put(StreamFullInfo.RADIX_TREE_KEYS, LONG);\n      tempMappingFunctions.put(StreamFullInfo.RADIX_TREE_NODES, LONG);\n      tempMappingFunctions.put(StreamFullInfo.GROUPS, STREAM_GROUP_FULL_INFO_LIST);\n      tempMappingFunctions.put(StreamFullInfo.ENTRIES, STREAM_ENTRY_LIST);\n\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public StreamFullInfo build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> streamsEntries = (List<Object>) data;\n      Iterator<Object> iterator = streamsEntries.iterator();\n\n      return new StreamFullInfo(createMapFromDecodingFunctions(iterator, mappingFunctions));\n    }\n\n    @Override\n    public String toString() {\n      return \"StreamFullInfo\";\n    }\n  };\n\n  /**\n   * @deprecated Use {@link BuilderFactory#STREAM_FULL_INFO}.\n   */\n  @Deprecated\n  public static final Builder<StreamFullInfo> STREAM_INFO_FULL = STREAM_FULL_INFO;\n\n  public static final Builder<VectorInfo> VECTOR_INFO = new Builder<VectorInfo>() {\n\n    final Map<String, Builder> mappingFunctions = createDecoderMap();\n\n    private Map<String, Builder> createDecoderMap() {\n      Map<String, Builder> tempMappingFunctions = new HashMap<>();\n      tempMappingFunctions.put(VectorInfo.VECTOR_DIM, LONG);\n      tempMappingFunctions.put(VectorInfo.TYPE, STRING);\n      tempMappingFunctions.put(VectorInfo.SIZE, LONG);\n      tempMappingFunctions.put(VectorInfo.MAX_NODE_UID, LONG);\n      tempMappingFunctions.put(VectorInfo.VSET_UID, LONG);\n      tempMappingFunctions.put(VectorInfo.MAX_NODES, LONG);\n      tempMappingFunctions.put(VectorInfo.PROJECTION_INPUT_DIM, LONG);\n      tempMappingFunctions.put(VectorInfo.ATTRIBUTES_COUNT, LONG);\n      tempMappingFunctions.put(VectorInfo.MAX_LEVEL, LONG);\n      return tempMappingFunctions;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public VectorInfo build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> vectorEntries = (List<Object>) data;\n      Iterator<Object> iterator = vectorEntries.iterator();\n\n      return new VectorInfo(createMapFromDecodingFunctions(iterator, mappingFunctions));\n    }\n\n    @Override\n    public String toString() {\n      return \"VectorInfo\";\n    }\n  };\n\n  public static final Builder<StreamPendingSummary> STREAM_PENDING_SUMMARY = new Builder<StreamPendingSummary>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public StreamPendingSummary build(Object data) {\n      if (null == data) {\n        return null;\n      }\n\n      List<Object> objectList = (List<Object>) data;\n      long total = LONG.build(objectList.get(0));\n      StreamEntryID minId = STREAM_ENTRY_ID.build(objectList.get(1));\n      StreamEntryID maxId = STREAM_ENTRY_ID.build(objectList.get(2));\n      Map<String, Long> map = objectList.get(3) == null ? null\n          : ((List<List<Object>>) objectList.get(3)).stream().collect(\n              Collectors.toMap(pair -> STRING.build(pair.get(0)),\n                  pair -> Long.parseLong(STRING.build(pair.get(1)))));\n      return new StreamPendingSummary(total, minId, maxId, map);\n    }\n\n    @Override\n    public String toString() {\n      return \"StreamPendingSummary\";\n    }\n  };\n\n  public static final Builder<List<StreamEntryBinary>> STREAM_ENTRY_BINARY_LIST = new Builder<List<StreamEntryBinary>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<StreamEntryBinary> build(Object data) {\n      if (null == data) {\n        return null;\n      }\n      List<ArrayList<Object>> objectList = (List<ArrayList<Object>>) data;\n\n      List<StreamEntryBinary> responses = new ArrayList<>(objectList.size() / 2);\n      if (objectList.isEmpty()) {\n        return responses;\n      }\n\n      for (ArrayList<Object> res : objectList) {\n        if (res == null) {\n          responses.add(null);\n          continue;\n        }\n        String entryIdString = SafeEncoder.encode((byte[]) res.get(0));\n        StreamEntryID entryID = new StreamEntryID(entryIdString);\n        List<byte[]> hash = (List<byte[]>) res.get(1);\n\n        Map<byte[], byte[]> map = null;\n\n        if (hash != null) {\n          Iterator<byte[]> hashIterator = hash.iterator();\n          map = new JedisByteHashMap();\n\n          while (hashIterator.hasNext()) {\n            map.put(BINARY.build(hashIterator.next()), BINARY.build(hashIterator.next()));\n          }\n        }\n\n        if (res.size() >= 4) {\n          Long millisElapsedFromDelivery = LONG.build(res.get(2));\n          Long deliveredCount = LONG.build(res.get(3));\n          responses.add(new StreamEntryBinary(entryID, map, millisElapsedFromDelivery, deliveredCount));\n          continue;\n        }\n\n        responses.add(new StreamEntryBinary(entryID, map));\n      }\n\n      return responses;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<StreamEntryBinary>\";\n    }\n  };\n\n  public static final Builder<Map<byte[], List<StreamEntryBinary>>> STREAM_READ_BINARY_MAP_RESPONSE\n      = new Builder<Map<byte[], List<StreamEntryBinary>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<byte[], List<StreamEntryBinary>> build(Object data) {\n      if (data == null) return null;\n      List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      JedisByteMap<List<StreamEntryBinary>> result = new JedisByteMap<>();\n      if (list.get(0) instanceof KeyValue) {\n        ((List<KeyValue>) list).forEach(kv -> result.put(BINARY.build(kv.getKey()), STREAM_ENTRY_BINARY_LIST.build(kv.getValue())));\n        return result;\n      } else {\n        for (Object anObj : list) {\n          List<Object> streamObj = (List<Object>) anObj;\n          byte[] streamKey = (byte[]) streamObj.get(0);\n          List<StreamEntryBinary> streamEntries = STREAM_ENTRY_BINARY_LIST.build(streamObj.get(1));\n          result.put(streamKey, streamEntries);\n        }\n        return result;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<byte[], List<StreamEntryBinary>>\";\n    }\n  };\n\n  public static final Builder<List<Map.Entry<byte[], List<StreamEntryBinary>>>> STREAM_READ_BINARY_RESPONSE\n      = new Builder<List<Map.Entry<byte[], List<StreamEntryBinary>>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map.Entry<byte[], List<StreamEntryBinary>>> build(Object data) {\n      if (data == null) return null;\n      List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyList();\n\n      if (list.get(0) instanceof KeyValue) {\n        return ((List<KeyValue>) list).stream()\n                .map(kv -> new KeyValue<>(BINARY.build(kv.getKey()),\n                    STREAM_ENTRY_BINARY_LIST.build(kv.getValue())))\n                .collect(Collectors.toList());\n      } else {\n        List<Map.Entry<byte[], List<StreamEntryBinary>>> result = new ArrayList<>(list.size());\n        for (Object anObj : list) {\n          List<Object> streamObj = (List<Object>) anObj;\n          byte[] streamKey = BINARY.build(streamObj.get(0));\n          List<StreamEntryBinary> streamEntries = STREAM_ENTRY_BINARY_LIST.build(streamObj.get(1));\n          result.add(KeyValue.of(streamKey, streamEntries));\n        }\n        return result;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Entry<byte[], List<StreamEntryBinary>>>\";\n    }\n  };\n\n  private static final List<Builder> BACKUP_BUILDERS_FOR_DECODING_FUNCTIONS\n      = Arrays.asList(STRING, LONG, DOUBLE);\n\n  private static Map<String, Object> createMapFromDecodingFunctions(Iterator<Object> iterator,\n      Map<String, Builder> mappingFunctions) {\n    return createMapFromDecodingFunctions(iterator, mappingFunctions, null);\n  }\n\n  private static Map<String, Object> createMapFromDecodingFunctions(Iterator<Object> iterator,\n      Map<String, Builder> mappingFunctions, Collection<Builder> backupBuilders) {\n\n    if (!iterator.hasNext()) {\n      return Collections.emptyMap();\n    }\n\n    Map<String, Object> resultMap = new HashMap<>();\n    while (iterator.hasNext()) {\n      final Object tempObject = iterator.next();\n      final String mapKey;\n      final Object rawValue;\n\n      if (tempObject instanceof KeyValue) {\n        KeyValue kv = (KeyValue) tempObject;\n        mapKey = STRING.build(kv.getKey());\n        rawValue = kv.getValue();\n      } else {\n        mapKey = STRING.build(tempObject);\n        rawValue = iterator.next();\n      }\n\n      if (mappingFunctions.containsKey(mapKey)) {\n        resultMap.put(mapKey, mappingFunctions.get(mapKey).build(rawValue));\n      } else { // For future - if we don't find an element in our builder map\n        Collection<Builder> builders = backupBuilders != null ? backupBuilders : mappingFunctions.values();\n        for (Builder b : builders) {\n          try {\n            resultMap.put(mapKey, b.build(rawValue));\n            break;\n          } catch (ClassCastException e) {\n            // We continue with next builder\n          }\n        }\n      }\n    }\n    return resultMap;\n  }\n\n  // <-- Stream Builders\n\n  public static final Builder<LCSMatchResult> STR_ALGO_LCS_RESULT_BUILDER = new Builder<LCSMatchResult>() {\n    @Override\n    public LCSMatchResult build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      if (data instanceof byte[]) {\n        return new LCSMatchResult(STRING.build(data));\n      } else if (data instanceof Long) {\n        return new LCSMatchResult(LONG.build(data));\n      } else {\n        long len = 0;\n        List<MatchedPosition> matchedPositions = new ArrayList<>();\n\n        List<Object> objectList = (List<Object>) data;\n        if (objectList.get(0) instanceof KeyValue) {\n          Iterator iterator = objectList.iterator();\n          while (iterator.hasNext()) {\n            KeyValue kv = (KeyValue) iterator.next();\n            if (\"matches\".equalsIgnoreCase(STRING.build(kv.getKey()))) {\n              addMatchedPosition(matchedPositions, kv.getValue());\n            } else if (\"len\".equalsIgnoreCase(STRING.build(kv.getKey()))) {\n              len = LONG.build(kv.getValue());\n            }\n          }\n        } else {\n          for (int i = 0; i < objectList.size(); i += 2) {\n            if (\"matches\".equalsIgnoreCase(STRING.build(objectList.get(i)))) {\n              addMatchedPosition(matchedPositions, objectList.get(i + 1));\n            } else if (\"len\".equalsIgnoreCase(STRING.build(objectList.get(i)))) {\n              len = LONG.build(objectList.get(i + 1));\n            }\n          }\n        }\n\n        return new LCSMatchResult(matchedPositions, len);\n      }\n    }\n\n    private void addMatchedPosition(List<MatchedPosition> matchedPositions, Object o) {\n      List<Object> matches = (List<Object>) o;\n      for (Object obj : matches) {\n        if (obj instanceof List<?>) {\n          List<Object> positions = (List<Object>) obj;\n          Position a = new Position(\n              LONG.build(((List<Object>) positions.get(0)).get(0)),\n              LONG.build(((List<Object>) positions.get(0)).get(1))\n          );\n          Position b = new Position(\n              LONG.build(((List<Object>) positions.get(1)).get(0)),\n              LONG.build(((List<Object>) positions.get(1)).get(1))\n          );\n          long matchLen = 0;\n          if (positions.size() >= 3) {\n            matchLen = LONG.build(positions.get(2));\n          }\n          matchedPositions.add(new MatchedPosition(a, b, matchLen));\n        }\n      }\n    }\n  };\n\n  public static final Builder<Map<String, String>> STRING_MAP_FROM_PAIRS = new Builder<Map<String, String>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, String> build(Object data) {\n      final List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        return ((List<KeyValue>) list).stream()\n            .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()),\n                kv -> STRING.build(kv.getValue())));\n      }\n\n      final Map<String, String> map = new HashMap<>(list.size());\n      for (Object object : list) {\n        if (object == null) continue;\n        final List<Object> flat = (List<Object>) object;\n        if (flat.isEmpty()) continue;\n        map.put(STRING.build(flat.get(0)), STRING.build(flat.get(1)));\n      }\n      return map;\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, String>\";\n    }\n  };\n\n  public static final Builder<Map<String, Object>> ENCODED_OBJECT_MAP_FROM_PAIRS = new Builder<Map<String, Object>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, Object> build(Object data) {\n      final List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        return ((List<KeyValue>) list).stream()\n            .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()),\n                kv -> ENCODED_OBJECT.build(kv.getValue())));\n      }\n\n      final Map<String, Object> map = new HashMap<>(list.size());\n      for (Object object : list) {\n        if (object == null) continue;\n        final List<Object> flat = (List<Object>) object;\n        if (flat.isEmpty()) continue;\n        map.put(STRING.build(flat.get(0)), STRING.build(flat.get(1)));\n      }\n      return map;\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, String>\";\n    }\n  };\n\n  /**\n   * @deprecated Use {@link LibraryInfo#LIBRARY_INFO_LIST}.\n   */\n  @Deprecated\n  public static final Builder<List<LibraryInfo>> LIBRARY_LIST = LibraryInfo.LIBRARY_INFO_LIST;\n\n  public static final Builder<List<List<String>>> STRING_LIST_LIST = new Builder<List<List<String>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<List<String>> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(STRING_LIST::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<List<String>>\";\n    }\n  };\n\n  public static final Builder<List<List<Object>>> ENCODED_OBJECT_LIST_LIST = new Builder<List<List<Object>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<List<Object>> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(ENCODED_OBJECT_LIST::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<List<Object>>\";\n    }\n  };\n\n  // Vector Set builders\n  public static final Builder<Map<String, VSimScoreAttribs>> VSIM_SCORE_ATTRIBS_MAP = new Builder<Map<String, VSimScoreAttribs>>() {\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, VSimScoreAttribs> build(Object data) {\n      if (data == null) return null;\n      List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        final Map<String, VSimScoreAttribs> result = new LinkedHashMap<>(list.size(), 1f);\n        for (Object o : list) {\n          KeyValue<?, ?> kv = (KeyValue<?, ?>) o;\n          List<Object> scoreAndAttribs = (List<Object>) kv.getValue();\n          result.put(STRING.build(kv.getKey()),\n              new VSimScoreAttribs(DOUBLE.build(scoreAndAttribs.get(0)),\n                  STRING.build(scoreAndAttribs.get(1))));\n        }\n        return result;\n      } else {\n        final Map<String, VSimScoreAttribs> result = new LinkedHashMap<>(list.size() / 3, 1f);\n        for (int i = 0; i < list.size(); i += 3) {\n          result.put(STRING.build(list.get(i)),\n              new VSimScoreAttribs(DOUBLE.build(list.get(i + 1)), STRING.build(list.get(i + 2))));\n        }\n        return result;\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<String, VSimScoreAttribs>\";\n    }\n  };\n\n  public static final Builder<Map<byte[], VSimScoreAttribs>> VSIM_SCORE_ATTRIBS_BINARY_MAP = new Builder<Map<byte[], VSimScoreAttribs>>() {\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Map<byte[], VSimScoreAttribs> build(Object data) {\n      if (data == null) return null;\n      List<Object> list = (List<Object>) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      JedisByteMap<VSimScoreAttribs> result = new JedisByteMap<>();\n      if (list.get(0) instanceof KeyValue) {\n        for (Object o : list) {\n          KeyValue<?, ?> kv = (KeyValue<?, ?>) o;\n          List<Object> scoreAndAttribs = (List<Object>) kv.getValue();\n          result.put(BINARY.build(kv.getKey()),\n              new VSimScoreAttribs(DOUBLE.build(scoreAndAttribs.get(0)),\n                  STRING.build(scoreAndAttribs.get(1))));\n        }\n      } else {\n        for (int i = 0; i < list.size(); i += 3) {\n          result.put(BINARY.build(list.get(i)),\n              new VSimScoreAttribs(DOUBLE.build(list.get(i + 1)), STRING.build(list.get(i + 2))));\n        }\n      }\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return \"Map<byte[], VSimScoreAttribs>\";\n    }\n  };\n\n  public static final Builder<RawVector> VEMB_RAW_RESULT = new Builder<RawVector>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public RawVector build(Object data) {\n      if (data == null) return null;\n      List<Object> list = (List<Object>) data;\n\n      String quantizationType = STRING.build(list.get(0));\n      byte[] rawData = (byte[]) list.get(1);\n      Double norm = DOUBLE.build(list.get(2));\n      Double quantizationRange = list.size() > 3 ? DOUBLE.build(list.get(3)) : null;\n\n      return new RawVector(quantizationType, rawData, norm, quantizationRange);\n    }\n\n    @Override\n    public String toString() {\n      return \"RawVector\";\n    }\n  };\n\n  // VLINKS builders\n  public static final Builder<List<Map<String, Double>>> VLINKS_WITH_SCORES_RESULT = new Builder<List<Map<String, Double>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map<String, Double>> build(Object data) {\n      if (data == null) return null;\n      List<Object> list = (List<Object>) data;\n\n      List<Map<String, Double>> result = new ArrayList<>();\n      for (Object scoresRaw : list) {\n        if (scoresRaw == null) continue;\n        Map<String, Double> scores  = STRING_DOUBLE_MAP.build(scoresRaw);\n        result.add(scores);\n      }\n\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Map<String, Double>>\";\n    }\n  };\n\n  public static final Builder<List<List<byte[]>>> BINARY_LIST_LIST = new Builder<List<List<byte[]>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<List<byte[]>> build(Object data) {\n      if (null == data) return null;\n      return ((List<Object>) data).stream().map(BINARY_LIST::build).collect(Collectors.toList());\n    }\n\n    @Override\n    public String toString() {\n      return \"List<List<String>>\";\n    }\n  };\n\n\n  public static final Builder<List<Map<byte[], Double>>> VLINKS_WITH_SCORES_RESULT_BINARY = new Builder<List<Map<byte[], Double>>>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<Map<byte[], Double>> build(Object data) {\n      if (data == null) return null;\n      List<Object> list = (List<Object>) data;\n\n      List<Map<byte[], Double>> result = new ArrayList<>();\n      for (Object scoresRaw : list) {\n        if (scoresRaw == null) continue;\n        Map<byte[], Double> scores  = BINARY_DOUBLE_MAP.build(scoresRaw);\n        result.add(scores);\n      }\n\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return \"List<Map<byte[], Double>>\";\n    }\n  };\n\n  /**\n   * A decorator to implement Set from List. Assume that given List do not contains duplicated\n   * values. The resulting set displays the same ordering, concurrency, and performance\n   * characteristics as the backing list. This class should be used only for Redis commands which\n   * return Set result.\n   */\n  protected static class SetFromList<E> extends AbstractSet<E> implements Serializable {\n    private static final long serialVersionUID = -2850347066962734052L;\n    private final List<E> list;\n\n    private SetFromList(List<E> list) {\n      this.list = list;\n    }\n\n    @Override\n    public void clear() {\n      list.clear();\n    }\n\n    @Override\n    public int size() {\n      return list.size();\n    }\n\n    @Override\n    public boolean isEmpty() {\n      return list.isEmpty();\n    }\n\n    @Override\n    public boolean contains(Object o) {\n      return list.contains(o);\n    }\n\n    @Override\n    public boolean remove(Object o) {\n      return list.remove(o);\n    }\n\n    @Override\n    public boolean add(E e) {\n      return !contains(e) && list.add(e);\n    }\n\n    @Override\n    public Iterator<E> iterator() {\n      return list.iterator();\n    }\n\n    @Override\n    public Object[] toArray() {\n      return list.toArray();\n    }\n\n    @Override\n    public <T> T[] toArray(T[] a) {\n      return list.toArray(a);\n    }\n\n    @Override\n    public String toString() {\n      return list.toString();\n    }\n\n    @Override\n    public int hashCode() {\n      return list.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o == null) return false;\n      if (o == this) return true;\n      if (!(o instanceof Set)) return false;\n\n      Collection<?> c = (Collection<?>) o;\n      if (c.size() != size()) {\n        return false;\n      }\n\n      return containsAll(c);\n    }\n\n    @Override\n    public boolean containsAll(Collection<?> c) {\n      return list.containsAll(c);\n    }\n\n    @Override\n    public boolean removeAll(Collection<?> c) {\n      return list.removeAll(c);\n    }\n\n    @Override\n    public boolean retainAll(Collection<?> c) {\n      return list.retainAll(c);\n    }\n\n    protected static <E> SetFromList<E> of(List<E> list) {\n      if (list == null) {\n        return null;\n      }\n      return new SetFromList<>(list);\n    }\n  }\n\n  private BuilderFactory() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ClientSetInfoConfig.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.exceptions.JedisValidationException;\n\n/**\n * Configuration for CLIENT SETINFO command behaviour.\n * <p>\n * This class supports two modes of operation:\n * <ul>\n * <li>Advanced mode: Using {@link #withLibNameSuffix(String)} for advanced suffix customization,\n * where the provided string is already preformatted according to the rules of the `CLIENT SETINFO`\n * command</li>\n * <li>Simple mode: Using {@link #ClientSetInfoConfig(DriverInfo)} used when the command parameter\n * will be built by the driver based on the {@link DriverInfo} provided</li>\n * </ul>\n * <p>\n * For backward compatibility, {@link #getUpstreamDrivers()} returns the upstream drivers string\n * when using driver info mode.\n * @see DriverInfo\n * @see <a href=\"https://redis.io/docs/latest/commands/client-setinfo/\">CLIENT SETINFO</a>\n */\npublic final class ClientSetInfoConfig {\n\n  private final boolean disabled;\n\n  private final DriverInfo driverInfo;\n\n  /**\n   * Creates a new ClientSetInfoConfig with default settings.\n   * <p>\n   * The default configuration uses the \"jedis\" library name without any upstream drivers.\n   */\n  public ClientSetInfoConfig() {\n    this(false);\n  }\n\n  /**\n   * Creates a new ClientSetInfoConfig with the specified disabled state.\n   * <p>\n   * When disabled, the CLIENT SETINFO command will not be sent to Redis.\n   * @param disabled {@code true} to disable CLIENT SETINFO, {@code false} otherwise\n   */\n  public ClientSetInfoConfig(boolean disabled) {\n    this.disabled = disabled;\n    this.driverInfo = DriverInfo.builder().build();\n  }\n\n  /**\n   * Creates a new ClientSetInfoConfig with a library name suffix.\n   * <p>\n   * This constructor is for legacy compatibility. The suffix will be appended to \"jedis\" in\n   * parentheses, resulting in a format like: {@code jedis(suffix)}.\n   * <p>\n   * For adding upstream driver information, use {@link #ClientSetInfoConfig(DriverInfo)} with a\n   * {@link DriverInfo} that has upstream drivers.\n   * @param libNameSuffix the suffix to append to \"jedis\" (will be placed in parentheses)\n   * @throws JedisValidationException if libNameSuffix contains braces\n   */\n  public ClientSetInfoConfig(String libNameSuffix) {\n    this.disabled = false;\n    this.driverInfo = DriverInfo.builder().addUpstreamDriver(libNameSuffix).build();\n  }\n\n  /**\n   * Creates a new ClientSetInfoConfig with the specified driver information.\n   * <p>\n   * This is the recommended constructor for setting up driver information with upstream drivers.\n   * The driver information can optionally override the library name completely.\n   * @param driverInfo the driver information, must not be {@code null}\n   * @throws JedisValidationException if driverInfo is {@code null}\n   */\n  public ClientSetInfoConfig(DriverInfo driverInfo) {\n    if (driverInfo == null) {\n      throw new JedisValidationException(\"DriverInfo must not be null\");\n    }\n    this.disabled = false;\n    this.driverInfo = driverInfo;\n  }\n\n  /**\n   * @return {@code true} if CLIENT SETINFO is disabled, {@code false} otherwise\n   */\n  public boolean isDisabled() {\n    return disabled;\n  }\n\n  /**\n   * @return the driver information\n   */\n  public DriverInfo getDriverInfo() {\n    return driverInfo;\n  }\n\n  /**\n   * Returns the formatted upstream drivers string.\n   * <p>\n   * Multiple drivers are separated by semicolons, with the most recently added driver appearing\n   * first.\n   * <p>\n   * Examples:\n   * <ul>\n   * <li>{@code \"spring-data-redis_v3.2.0\"} - single upstream driver</li>\n   * <li>{@code \"lettuce-core_v6.4.1;spring-data-redis_v3.2.0\"} - multiple upstream drivers</li>\n   * </ul>\n   * @return the formatted upstream drivers string, or {@code null} if no upstream drivers are set\n   */\n  public String getUpstreamDrivers() {\n    return driverInfo.getUpstreamDrivers();\n  }\n\n  /**\n   * Default configuration that uses the Jedis library name without any upstream drivers.\n   */\n  public static final ClientSetInfoConfig DEFAULT = new ClientSetInfoConfig();\n\n  /**\n   * Configuration that disables CLIENT SETINFO command.\n   */\n  public static final ClientSetInfoConfig DISABLED = new ClientSetInfoConfig(true);\n\n  /**\n   * Creates a new ClientSetInfoConfig with a library name suffix.\n   * <p>\n   * This is the legacy method for simple name customization. The provided suffix will be appended\n   * to \"jedis\" in parentheses, resulting in a format like: {@code jedis(suffix)}. For adding\n   * upstream driver information, use {@link #ClientSetInfoConfig(DriverInfo)} with a *\n   * {@link DriverInfo} that has upstream drivers.\n   * @param suffix the suffix to append to \"jedis\" (will be placed in parentheses)\n   * @return a new ClientSetInfoConfig with the library name suffix\n   */\n  public static ClientSetInfoConfig withLibNameSuffix(String suffix) {\n    return new ClientSetInfoConfig(suffix);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ClusterCommandObjects.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.params.MSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.HotkeysInfo;\nimport redis.clients.jedis.params.HotkeysParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport redis.clients.jedis.util.JedisClusterHashTag;\nimport redis.clients.jedis.util.KeyValue;\n\nimport java.util.*;\nimport java.util.function.BiConsumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport static redis.clients.jedis.Protocol.Command.*;\nimport static redis.clients.jedis.Protocol.Keyword.TYPE;\n\npublic class ClusterCommandObjects extends CommandObjects {\n\n  private static final String CLUSTER_UNSUPPORTED_MESSAGE = \"Not supported in cluster mode.\";\n\n  private static final String SCAN_PATTERN_MESSAGE = \"Cluster mode only supports SCAN command\"\n      + \" with MATCH pattern containing hash-tag ( curly-brackets enclosed string )\";\n\n  @Override\n  public final CommandObject<ScanResult<String>> scan(String cursor) {\n    throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);\n  }\n\n  @Override\n  public final CommandObject<ScanResult<String>> scan(String cursor, ScanParams params) {\n    String match = params.match();\n    if (match == null || !JedisClusterHashTag.isClusterCompliantMatchPattern(match)) {\n      throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);\n    }\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).addHashSlotKey(match), BuilderFactory.SCAN_RESPONSE);\n  }\n\n  @Override\n  public final CommandObject<ScanResult<String>> scan(String cursor, ScanParams params, String type) {\n    String match = params.match();\n    if (match == null || !JedisClusterHashTag.isClusterCompliantMatchPattern(match)) {\n      throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);\n    }\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).addHashSlotKey(match).add(TYPE).add(type), BuilderFactory.SCAN_RESPONSE);\n  }\n\n  @Override\n  public final CommandObject<ScanResult<byte[]>> scan(byte[] cursor) {\n    throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);\n  }\n\n  @Override\n  public final CommandObject<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params) {\n    byte[] match = params.binaryMatch();\n    if (match == null || !JedisClusterHashTag.isClusterCompliantMatchPattern(match)) {\n      throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);\n    }\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).addHashSlotKey(match), BuilderFactory.SCAN_BINARY_RESPONSE);\n  }\n\n  @Override\n  public final CommandObject<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params, byte[] type) {\n    byte[] match = params.binaryMatch();\n    if (match == null || !JedisClusterHashTag.isClusterCompliantMatchPattern(match)) {\n      throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);\n    }\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).addHashSlotKey(match).add(TYPE).add(type), BuilderFactory.SCAN_BINARY_RESPONSE);\n  }\n\n  @Override\n  public final CommandObject<Long> waitReplicas(int replicas, long timeout) {\n    throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE);\n  }\n\n  @Override\n  public CommandObject<KeyValue<Long, Long>> waitAOF(long numLocal, long numReplicas, long timeout) {\n    throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE);\n  }\n\n  @Override\n  public CommandObject<String> hotkeysStart(HotkeysParams params) {\n    throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE);\n  }\n\n  @Override\n  public CommandObject<String> hotkeysStop() {\n    throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE);\n  }\n\n  @Override\n  public CommandObject<String> hotkeysReset() {\n    throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE);\n  }\n\n  @Override\n  public CommandObject<HotkeysInfo> hotkeysGet() {\n    throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE);\n  }\n\n  /**\n   * Groups key-value pairs by their hash slot and creates separate CommandArguments for each slot.\n   * This enables commands with multiple keys to be properly distributed across Redis cluster nodes\n   * by ensuring that each resulting command only operates on keys that hash to the same slot.\n   *\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keysValues variable number of key-value pairs to be grouped (must be even length)\n   * @param params additional parameters for the command (may be null)\n   * @return a list of CommandArguments objects, each containing only keys/values that belong to the same hash slot\n   * @throws IllegalArgumentException if keysValues has odd length\n   */\n  protected List<CommandArguments> groupArgumentsByKeyValueHashSlot(CommandArguments args, String[] keysValues, IParams params) {\n    return groupArgumentsByKeyValueHashSlotImpl(\n        args,\n        keysValues,\n        params,\n        JedisClusterCRC16::getSlot,\n        CommandArguments::key,\n        CommandArguments::add,\n        false\n    );\n  }\n\n  /**\n   * Groups key-value pairs by their hash slot and creates separate CommandArguments for each slot.\n   * This enables commands with multiple keys to be properly distributed across Redis cluster nodes\n   * by ensuring that each resulting command only operates on keys that hash to the same slot.\n   *\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keysValues variable number of key-value pairs to be grouped (must be even length)\n   * @param params additional parameters for the command (may be null)\n   * @return a list of CommandArguments objects, each containing only keys/values that belong to the same hash slot\n   * @throws IllegalArgumentException if keysValues has odd length\n   */\n  protected List<CommandArguments> groupArgumentsByKeyValueHashSlot(CommandArguments args, byte[][] keysValues, IParams params) {\n    return groupArgumentsByKeyValueHashSlotImpl(\n        args,\n        keysValues,\n        params,\n        JedisClusterCRC16::getSlot,\n        CommandArguments::key,\n        CommandArguments::add,\n        false\n    );\n  }\n\n  /**\n   * Groups keys by their hash slot and creates separate CommandArguments for each slot.\n   * This enables commands with multiple keys (like DEL, EXISTS, MGET) to be properly distributed\n   * across Redis cluster nodes by ensuring that each resulting command only operates on keys\n   * that hash to the same slot.\n   *\n   * <p><b>Order Preservation:</b> This method preserves the order of keys as they appear in the input array.\n   * Consecutive keys that hash to the same slot are grouped together into a single CommandArguments,\n   * but non-consecutive keys with the same slot are kept in separate CommandArguments to maintain order.\n   * For example, if input keys map to slots [A, B, A], the result will be 3 separate CommandArguments\n   * (not 2), ensuring that when results are concatenated, they match the original input key order.</p>\n   *\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keys variable number of keys to be grouped\n   * @param params additional parameters for the command (may be null)\n   * @return a list of CommandArguments objects, each containing only keys that belong to the same hash slot,\n   *         preserving the original key order\n   */\n  protected List<CommandArguments> groupArgumentsByKeyHashSlot(CommandArguments args, String[] keys, IParams params) {\n    return groupArgumentsByKeyValueHashSlotImpl(\n        args,\n        keys,\n        params,\n        JedisClusterCRC16::getSlot,\n        CommandArguments::key,\n        null,\n        false\n    );\n  }\n\n  /**\n   * Groups keys by their hash slot and creates separate CommandArguments for each slot.\n   * This enables commands with multiple keys (like DEL, EXISTS, MGET) to be properly distributed\n   * across Redis cluster nodes by ensuring that each resulting command only operates on keys\n   * that hash to the same slot.\n   *\n   * <p><b>Order Preservation:</b> This method preserves the order of keys as they appear in the input array.\n   * Consecutive keys that hash to the same slot are grouped together into a single CommandArguments,\n   * but non-consecutive keys with the same slot are kept in separate CommandArguments to maintain order.\n   * For example, if input keys map to slots [A, B, A], the result will be 3 separate CommandArguments\n   * (not 2), ensuring that when results are concatenated, they match the original input key order.</p>\n   *\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keys variable number of keys to be grouped\n   * @param params additional parameters for the command (may be null)\n   * @return a list of CommandArguments objects, each containing only keys that belong to the same hash slot,\n   *         preserving the original key order\n   */\n  protected List<CommandArguments> groupArgumentsByKeyHashSlot(CommandArguments args, byte[][] keys, IParams params) {\n    return groupArgumentsByKeyValueHashSlotImpl(\n        args,\n        keys,\n        params,\n        JedisClusterCRC16::getSlot,\n        CommandArguments::key,\n        null,\n        false\n    );\n  }\n\n  /**\n   * Groups key-value pairs by their hash slot and creates separate CommandArguments for each slot.\n   * Inserts the key count after the command but before the keys (for commands like MSETEX).\n   *\n   * <p><b>Order Preservation:</b> This method preserves the order of key-value pairs as they appear\n   * in the input array. Consecutive pairs that hash to the same slot are grouped together, but\n   * non-consecutive pairs with the same slot are kept in separate CommandArguments to maintain order.</p>\n   *\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keysValues variable number of key-value pairs to be grouped (must be even length)\n   * @param params additional parameters for the command (may be null)\n   * @return a list of CommandArguments objects, each containing only keys/values that belong to the same hash slot\n   * @throws IllegalArgumentException if keysValues has odd length\n   */\n  protected List<CommandArguments> groupArgumentsByKeyValueHashSlotWithKeyCount(CommandArguments args, String[] keysValues, IParams params) {\n    return groupArgumentsByKeyValueHashSlotImpl(\n        args,\n        keysValues,\n        params,\n        JedisClusterCRC16::getSlot,\n        CommandArguments::key,\n        CommandArguments::add,\n        true\n    );\n  }\n\n  /**\n   * Groups key-value pairs by their hash slot and creates separate CommandArguments for each slot.\n   * Inserts the key count after the command but before the keys (for commands like MSETEX).\n   *\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keysValues variable number of key-value pairs to be grouped (must be even length)\n   * @param params additional parameters for the command (may be null)\n   * @return a list of CommandArguments objects, each containing only keys/values that belong to the same hash slot\n   * @throws IllegalArgumentException if keysValues has odd length\n   */\n  protected List<CommandArguments> groupArgumentsByKeyValueHashSlotWithKeyCount(CommandArguments args, byte[][] keysValues, IParams params) {\n    return groupArgumentsByKeyValueHashSlotImpl(\n        args,\n        keysValues,\n        params,\n        JedisClusterCRC16::getSlot,\n        CommandArguments::key,\n        CommandArguments::add,\n        true\n    );\n  }\n\n  /**\n   * Internal helper method that implements the common logic for grouping keys (and optionally values) by hash slot.\n   * When valueAdder is null, this method processes keys only (for commands like DEL, EXISTS, MGET).\n   * When valueAdder is provided, this method processes key-value pairs (for commands like MSET).\n   *\n   * <p><b>Order Preservation:</b> This method preserves the order of keys as they appear in the input array.\n   * Consecutive keys that hash to the same slot are grouped together into a single CommandArguments,\n   * but non-consecutive keys with the same slot are kept in separate CommandArguments to maintain order.\n   * For example, if input keys map to slots [A, B, A], the result will be 3 separate CommandArguments\n   * (not 2), preserving the original key order in the concatenated results.</p>\n   *\n   * @param <T> the type of key/value elements (String or byte[])\n   * @param args the original command arguments to copy (the command itself will be preserved)\n   * @param keysOrKeysValues array of keys (when valueAdder is null) or key-value pairs (when valueAdder is provided)\n   * @param params additional parameters for the command (may be null)\n   * @param slotCalculator function to calculate the hash slot for a key\n   * @param keyAdder function to add a key to CommandArguments\n   * @param valueAdder function to add a value to CommandArguments (may be null for key-only operations)\n   * @param insertKeyCount if true, inserts the number of keys after the command but before the keys (for commands like MSETEX)\n   * @return a list of CommandArguments objects, each containing only keys/values that belong to the same hash slot,\n   *         preserving the original key order\n   * @throws IllegalArgumentException if valueAdder is provided and keysOrKeysValues has odd length\n   */\n  private <T> List<CommandArguments> groupArgumentsByKeyValueHashSlotImpl(\n      CommandArguments args,\n      T[] keysOrKeysValues,\n      IParams params,\n      Function<T, Integer> slotCalculator,\n      BiConsumer<CommandArguments, T> keyAdder,\n      BiConsumer<CommandArguments, T> valueAdder,\n      boolean insertKeyCount) {\n\n    boolean keyValueMode = valueAdder != null;\n    int step = keyValueMode ? 2 : 1;\n\n    if (keyValueMode && keysOrKeysValues.length % 2 != 0) {\n      throw new IllegalArgumentException(\"keysValues must contain an even number of elements (key-value pairs)\");\n    }\n\n    if (keysOrKeysValues.length == 0) {\n      return new ArrayList<>();\n    }\n\n    // Wrap slotCalculator to apply keyPreProcessor transformation before slot calculation.\n    // This ensures keys are grouped by their actual slot (after preprocessing), not the original key's slot.\n    Function<T, Integer> effectiveSlotCalculator = keyPreProcessor != null\n        ? key -> calculateSlotFromPreprocessedKey(keyPreProcessor.actualKey(key))\n        : slotCalculator;\n\n    // Group consecutive keys with the same slot together, preserving input order\n    // Non-consecutive keys with the same slot will be in separate commands\n    List<CommandArguments> result = new ArrayList<>();\n\n    int currentSlot = -1;\n    List<T> currentGroup = new ArrayList<>();\n\n    for (int i = 0; i < keysOrKeysValues.length; i += step) {\n      T key = keysOrKeysValues[i];\n      int slot = effectiveSlotCalculator.apply(key);\n\n      if (slot != currentSlot && !currentGroup.isEmpty()) {\n        // Slot changed - finalize the current group\n        result.add(createCommandArgsForGroup(args, currentGroup, params, keyAdder, valueAdder, insertKeyCount, step));\n        currentGroup = new ArrayList<>();\n      }\n\n      currentSlot = slot;\n      currentGroup.add(key);\n      if (keyValueMode) {\n        currentGroup.add(keysOrKeysValues[i + 1]);\n      }\n    }\n\n    // Finalize the last group\n    if (!currentGroup.isEmpty()) {\n      result.add(createCommandArgsForGroup(args, currentGroup, params, keyAdder, valueAdder, insertKeyCount, step));\n    }\n\n    return result;\n  }\n\n  /**\n   * Calculates the hash slot for a preprocessed key.\n   *\n   * @param preprocessedKey the key after preprocessing (may be String, byte[], or Rawable)\n   * @return the hash slot for the key\n   */\n  private int calculateSlotFromPreprocessedKey(Object preprocessedKey) {\n    if (preprocessedKey instanceof byte[]) {\n      return JedisClusterCRC16.getSlot((byte[]) preprocessedKey);\n    } else if (preprocessedKey instanceof String) {\n      return JedisClusterCRC16.getSlot((String) preprocessedKey);\n    } else if (preprocessedKey instanceof redis.clients.jedis.args.Rawable) {\n      return JedisClusterCRC16.getSlot(((redis.clients.jedis.args.Rawable) preprocessedKey).getRaw());\n    }\n    throw new IllegalArgumentException(\"Unsupported key type: \" + preprocessedKey.getClass().getName());\n  }\n\n  /**\n   * Helper method to create a CommandArguments for a group of keys/values.\n   */\n  private <T> CommandArguments createCommandArgsForGroup(\n      CommandArguments args,\n      List<T> groupedElements,\n      IParams params,\n      BiConsumer<CommandArguments, T> keyAdder,\n      BiConsumer<CommandArguments, T> valueAdder,\n      boolean insertKeyCount,\n      int step) {\n\n    boolean keyValueMode = valueAdder != null;\n    CommandArguments slotArgs = commandArguments(args.getCommand());\n\n    // Insert key count after command but before keys (e.g., numkeys for MSETEX)\n    if (insertKeyCount) {\n      int keyCount = groupedElements.size() / step;\n      slotArgs.add(keyCount);\n    }\n\n    // Add keys (and optionally values) for this slot\n    for (int i = 0; i < groupedElements.size(); i += step) {\n      keyAdder.accept(slotArgs, groupedElements.get(i));\n      if (keyValueMode) {\n        valueAdder.accept(slotArgs, groupedElements.get(i + 1));\n      }\n    }\n\n    // Add params if provided\n    if (params != null) {\n      slotArgs.addParams(params);\n    }\n\n    return slotArgs;\n  }\n\n  // ==================== Multi-Shard Command Methods ====================\n  // These methods split commands across multiple Redis cluster shards based on key hash slots.\n  // They return List<CommandObject<T>> where each CommandObject targets keys in the same hash slot.\n\n  /**\n   * Creates multiple DEL command objects, one for each hash slot group.\n   * This enables the DEL command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to delete\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> delMultiShard(String... keys) {\n    CommandArguments args = commandArguments(DEL);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple DEL command objects, one for each hash slot group.\n   * This enables the DEL command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to delete\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> delMultiShard(byte[]... keys) {\n    CommandArguments args = commandArguments(DEL);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple EXISTS command objects, one for each hash slot group.\n   * This enables the EXISTS command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to check for existence\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> existsMultiShard(String... keys) {\n    CommandArguments args = commandArguments(EXISTS);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple EXISTS command objects, one for each hash slot group.\n   * This enables the EXISTS command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to check for existence\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> existsMultiShard(byte[]... keys) {\n    CommandArguments args = commandArguments(EXISTS);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple MGET command objects, one for each hash slot group.\n   * This enables the MGET command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * <p><b>Order Preservation:</b> The returned commands preserve the order of keys as they appear\n   * in the input array. When the results from all commands are concatenated in order, the values\n   * will correspond positionally to the input keys.</p>\n   *\n   * @param keys the keys to retrieve values for\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<List<String>>> mgetMultiShard(String... keys) {\n    CommandArguments args = commandArguments(MGET);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.STRING_LIST))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple MGET command objects, one for each hash slot group.\n   * This enables the MGET command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * <p><b>Order Preservation:</b> The returned commands preserve the order of keys as they appear\n   * in the input array. When the results from all commands are concatenated in order, the values\n   * will correspond positionally to the input keys.</p>\n   *\n   * @param keys the keys to retrieve values for\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<List<byte[]>>> mgetMultiShard(byte[]... keys) {\n    CommandArguments args = commandArguments(MGET);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.BINARY_LIST))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple MSET command objects, one for each hash slot group.\n   * This enables the MSET command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keysvalues alternating keys and values (key1, value1, key2, value2, ...)\n   * @return a list of CommandObject instances, each containing key-value pairs that belong to the same hash slot\n   */\n  public List<CommandObject<String>> msetMultiShard(String... keysvalues) {\n    CommandArguments args = commandArguments(MSET);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyValueHashSlot(args, keysvalues, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.STRING))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple MSET command objects, one for each hash slot group.\n   * This enables the MSET command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keysvalues alternating keys and values (key1, value1, key2, value2, ...)\n   * @return a list of CommandObject instances, each containing key-value pairs that belong to the same hash slot\n   */\n  public List<CommandObject<String>> msetMultiShard(byte[]... keysvalues) {\n    CommandArguments args = commandArguments(MSET);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyValueHashSlot(args, keysvalues, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.STRING))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple TOUCH command objects, one for each hash slot group.\n   * This enables the TOUCH command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to touch\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> touchMultiShard(String... keys) {\n    CommandArguments args = commandArguments(TOUCH);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple TOUCH command objects, one for each hash slot group.\n   * This enables the TOUCH command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to touch\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> touchMultiShard(byte[]... keys) {\n    CommandArguments args = commandArguments(TOUCH);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple UNLINK command objects, one for each hash slot group.\n   * This enables the UNLINK command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to unlink\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> unlinkMultiShard(String... keys) {\n    CommandArguments args = commandArguments(UNLINK);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple UNLINK command objects, one for each hash slot group.\n   * This enables the UNLINK command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots.\n   *\n   * @param keys the keys to unlink\n   * @return a list of CommandObject instances, each containing keys that belong to the same hash slot\n   */\n  public List<CommandObject<Long>> unlinkMultiShard(byte[]... keys) {\n    CommandArguments args = commandArguments(UNLINK);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyHashSlot(args, keys, null);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.LONG))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple MSETEX command objects, one for each hash slot group.\n   * This enables the MSETEX command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots. Each command preserves the provided parameters\n   * (expiration, NX/XX conditions).\n   *\n   * @param params the MSETEX parameters (expiration, NX/XX conditions)\n   * @param keysvalues alternating keys and values (key1, value1, key2, value2, ...)\n   * @return a list of CommandObject instances, each containing key-value pairs that belong to the same hash slot\n   */\n  public List<CommandObject<Boolean>> msetexMultiShard(MSetExParams params, String... keysvalues) {\n    CommandArguments args = commandArguments(MSETEX);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyValueHashSlotWithKeyCount(args, keysvalues, params);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.BOOLEAN))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Creates multiple MSETEX command objects, one for each hash slot group.\n   * This enables the MSETEX command to be executed across multiple Redis cluster shards\n   * when keys hash to different slots. Each command preserves the provided parameters\n   * (expiration, NX/XX conditions).\n   *\n   * @param params the MSETEX parameters (expiration, NX/XX conditions)\n   * @param keysvalues alternating keys and values (key1, value1, key2, value2, ...)\n   * @return a list of CommandObject instances, each containing key-value pairs that belong to the same hash slot\n   */\n  public List<CommandObject<Boolean>> msetexMultiShard(MSetExParams params, byte[]... keysvalues) {\n    CommandArguments args = commandArguments(MSETEX);\n    List<CommandArguments> groupedArgs = groupArgumentsByKeyValueHashSlotWithKeyCount(args, keysvalues, params);\n    return groupedArgs.stream()\n        .map(cmdArgs -> new CommandObject<>(cmdArgs, BuilderFactory.BOOLEAN))\n        .collect(Collectors.toList());\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ClusterPipeline.java",
    "content": "package redis.clients.jedis;\n\nimport java.time.Duration;\nimport java.util.Set;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.util.IOUtils;\n\n/**\n * Pipeline implementation for Redis Cluster mode.\n * <p>\n * ClusterPipeline allows batching multiple commands for efficient execution in a Redis Cluster\n * environment. Commands are automatically routed to the appropriate cluster nodes based on\n * key hash slots.\n * </p>\n * <p>\n * <strong>Important Limitations:</strong>\n * </p>\n * <ul>\n * <li><strong>Single-node commands only:</strong> Only commands that can be routed to a single\n * node are supported. Commands requiring execution on multiple nodes (ALL_SHARDS, MULTI_SHARD,\n * ALL_NODES, or SPECIAL request policies) will throw {@link UnsupportedOperationException}.</li>\n * <li><strong>Examples of unsupported commands:</strong>\n *   <ul>\n *     <li>{@code KEYS} - requires execution on all master shards</li>\n *     <li>{@code MGET} with keys in different slots - requires execution on multiple shards</li>\n *     <li>{@code SCRIPT LOAD} - requires execution on all nodes</li>\n *   </ul>\n * </li>\n * <li>For multi-node commands, use the non-pipelined mode\n * of {@link RedisClusterClient} instead.</li>\n * </ul>\n * <p>\n * <strong> Usage Pattern:</strong>\n * </p>\n * <pre>{@code\n * try (RedisCluster cluster = new RedisCluster(nodes, config)) {\n *   // For single-node commands, use pipelined mode\n *   try (ClusterPipeline pipeline = cluster.pipelined()) {\n *     Response<String> r1 = pipeline.set(\"key1\", \"value1\");\n *     Response<String> r2 = pipeline.get(\"key1\");\n *     pipeline.sync();\n *\n *     System.out.println(r1.get()); // \"OK\"\n *     System.out.println(r2.get()); // \"value1\"\n *   }\n *\n *   // For multi-node commands, use non-pipelined mode\n *   Set<String> allKeys = cluster.keys(\"*\"); // Executes on all master shards\n *   List<String> values = cluster.mget(\"key1\", \"key2\", \"key3\"); // Cross-slot keys\n * }\n * }</pre>\n *\n * @see MultiNodePipelineBase\n * @see redis.clients.jedis.RedisClusterClient\n */\npublic class ClusterPipeline extends MultiNodePipelineBase {\n\n  private final ClusterConnectionProvider provider;\n  private AutoCloseable closeable = null;\n\n  public ClusterPipeline(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig),\n        createClusterCommandObjects(clientConfig.getRedisProtocol()));\n    this.closeable = this.provider;\n  }\n\n  public ClusterPipeline(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig),\n        createClusterCommandObjects(clientConfig.getRedisProtocol()));\n    this.closeable = this.provider;\n  }\n\n  public ClusterPipeline(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig, Duration topologyRefreshPeriod) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig, topologyRefreshPeriod),\n        createClusterCommandObjects(clientConfig.getRedisProtocol()));\n    this.closeable = this.provider;\n  }\n\n  public ClusterPipeline(ClusterConnectionProvider provider) {\n    this(provider, new ClusterCommandObjects());\n  }\n\n  public ClusterPipeline(ClusterConnectionProvider provider, ClusterCommandObjects commandObjects) {\n    super(commandObjects);\n    this.provider = provider;\n  }\n\n  ClusterPipeline(ClusterConnectionProvider provider, ClusterCommandObjects commandObjects,\n      CommandFlagsRegistry commandFlagsRegistry) {\n    super(commandObjects, commandFlagsRegistry);\n    this.provider = provider;\n  }\n\n  private static ClusterCommandObjects createClusterCommandObjects(RedisProtocol protocol) {\n    ClusterCommandObjects cco = new ClusterCommandObjects();\n    if (protocol == RedisProtocol.RESP3) cco.setProtocol(protocol);\n    return cco;\n  }\n\n  @Override\n  public void close() {\n    try {\n      super.close();\n    } finally {\n      IOUtils.closeQuietly(closeable);\n    }\n  }\n\n  @Override\n  protected HostAndPort getNodeKey(CommandArguments args) {\n    Set<Integer> slots = args.getKeyHashSlots();\n\n    if (slots.size() > 1) {\n      throw new JedisClusterOperationException(\"Cannot get NodeKey for command with multiple hash slots\");\n    }\n\n    if (slots.isEmpty()) {\n      return null; // Let getConnection(null) handle it by using a random node\n    }\n\n    return provider.getNode(slots.iterator().next());\n  }\n\n  @Override\n  protected Connection getConnection(HostAndPort nodeKey) {\n    return provider.getConnection(nodeKey);\n  }\n\n  public Response<Long> spublish(String channel, String message) {\n    return appendCommand(commandObjects.spublish(channel, message));\n  }\n\n  public Response<Long> spublish(byte[] channel, byte[] message) {\n    return appendCommand(commandObjects.spublish(channel, message));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/CommandArguments.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.*;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.annots.Internal;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.args.RawableFactory;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.RediSearchUtil;\nimport redis.clients.jedis.util.JedisClusterCRC16;\n\npublic class CommandArguments implements Iterable<Rawable> {\n\n  /**\n   * Default initial capacity for the keys list. Most Redis commands have 1-3 keys,\n   * so a small initial capacity avoids reallocations for common cases.\n   */\n  private static final int DEFAULT_KEYS_CAPACITY = 4;\n\n  private CommandKeyArgumentPreProcessor keyPreProc = null;\n  private final ArrayList<Rawable> args;\n\n  /**\n   * Pre-allocated list for storing keys. Using ArrayList directly avoids the\n   * memory reallocation overhead of transitioning from emptyList -> singletonList -> ArrayList.\n   */\n  private final ArrayList<Object> keys;\n\n  /**\n   * Cached hash slots computed from keys. Null indicates the cache is invalid\n   * and needs to be recomputed. The cache is invalidated when keys are added.\n   */\n  private Set<Integer> cachedHashSlots;\n\n  private boolean blocking;\n\n  private CommandArguments() {\n    throw new InstantiationError();\n  }\n\n  public CommandArguments(ProtocolCommand command) {\n    args = new ArrayList<>();\n    args.add(command);\n\n    keys = new ArrayList<>(DEFAULT_KEYS_CAPACITY);\n    cachedHashSlots = null;\n  }\n\n  public ProtocolCommand getCommand() {\n    return (ProtocolCommand) args.get(0);\n  }\n\n  @Experimental\n  void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) {\n    this.keyPreProc = keyPreProcessor;\n  }\n\n  public CommandArguments add(Rawable arg) {\n    args.add(arg);\n    return this;\n  }\n\n  public CommandArguments add(byte[] arg) {\n    return add(RawableFactory.from(arg));\n  }\n\n  public CommandArguments add(boolean arg) {\n    return add(RawableFactory.from(arg));\n  }\n\n  public CommandArguments add(int arg) {\n    return add(RawableFactory.from(arg));\n  }\n\n  public CommandArguments add(long arg) {\n    return add(RawableFactory.from(arg));\n  }\n\n  public CommandArguments add(double arg) {\n    return add(RawableFactory.from(arg));\n  }\n\n  public CommandArguments add(String arg) {\n    return add(RawableFactory.from(arg));\n  }\n\n  public CommandArguments add(Object arg) {\n    if (arg == null) {\n      throw new IllegalArgumentException(\"null is not a valid argument.\");\n    } else if (arg instanceof Rawable) {\n      args.add((Rawable) arg);\n    } else if (arg instanceof byte[]) {\n      args.add(RawableFactory.from((byte[]) arg));\n    } else if (arg instanceof Boolean) {\n      args.add(RawableFactory.from((Boolean) arg));\n    } else if (arg instanceof Integer) {\n      args.add(RawableFactory.from((Integer) arg));\n    } else if (arg instanceof Long) {\n      args.add(RawableFactory.from((Long) arg));\n    } else if (arg instanceof Double) {\n      args.add(RawableFactory.from((Double) arg));\n    } else if (arg instanceof float[]) {\n      args.add(RawableFactory.from(RediSearchUtil.toByteArray((float[]) arg)));\n    } else if (arg instanceof String) {\n      args.add(RawableFactory.from((String) arg));\n    } else if (arg instanceof GeoCoordinate) {\n      GeoCoordinate geo = (GeoCoordinate) arg;\n      args.add(RawableFactory.from(geo.getLongitude() + \",\" + geo.getLatitude()));\n    } else {\n      args.add(RawableFactory.from(String.valueOf(arg)));\n    }\n    return this;\n  }\n\n  public CommandArguments addObjects(Object... args) {\n    for (Object arg : args) {\n      add(arg);\n    }\n    return this;\n  }\n\n  public CommandArguments addObjects(Collection args) {\n    args.forEach(arg -> add(arg));\n    return this;\n  }\n\n  public CommandArguments key(Object key) {\n    if (keyPreProc != null) {\n      key = keyPreProc.actualKey(key);\n    }\n\n    if (key instanceof Rawable) {\n      Rawable raw = (Rawable) key;\n      args.add(raw);\n      // Extract raw bytes for hash slot computation to avoid ClassCastException in getKeyHashSlots()\n      addHashSlotKey(raw.getRaw());\n    } else if (key instanceof byte[]) {\n      byte[] raw = (byte[]) key;\n      args.add(RawableFactory.from(raw));\n      addHashSlotKey(raw);\n    } else if (key instanceof String) {\n      String raw = (String) key;\n      args.add(RawableFactory.from(raw));\n      addHashSlotKey(raw);\n    } else {\n      throw new IllegalArgumentException(\"\\\"\" + key.toString() + \"\\\" is not a valid argument.\");\n    }\n\n    return this;\n  }\n\n  final CommandArguments addHashSlotKey(String key) {\n    keys.add(key);\n    // Invalidate cached hash slots since keys have changed\n    cachedHashSlots = null;\n    return this;\n  }\n\n  final CommandArguments addHashSlotKey(byte[] key) {\n    keys.add(key);\n    // Invalidate cached hash slots since keys have changed\n    cachedHashSlots = null;\n    return this;\n  }\n\n  public final CommandArguments keys(Object... keys) {\n    Arrays.stream(keys).forEach(this::key);\n    return this;\n  }\n\n  public final CommandArguments keys(Collection keys) {\n    keys.forEach(this::key);\n    return this;\n  }\n\n  public final CommandArguments addParams(IParams params) {\n    params.addParams(this);\n    return this;\n  }\n\n  protected final CommandArguments addHashSlotKeys(byte[]... keys) {\n    for (byte[] key : keys) {\n      addHashSlotKey(key);\n    }\n    return this;\n  }\n\n  protected final CommandArguments addHashSlotKeys(String... keys) {\n    for (String key : keys) {\n      addHashSlotKey(key);\n    }\n    return this;\n  }\n\n  public int size() {\n    return args.size();\n  }\n\n  /**\n   * Get the argument at the specified index.\n   * @param index the index of the argument to retrieve (0-based, where 0 is the command itself)\n   * @return the Rawable argument at the specified index\n   * @throws IndexOutOfBoundsException if the index is out of range\n   */\n  public Rawable get(int index) {\n    return args.get(index);\n  }\n\n  @Override\n  public Iterator<Rawable> iterator() {\n    return args.iterator();\n  }\n\n  /**\n   * Returns the keys used in this command.\n   * <p>\n   * <b>Internal API:</b> This method is internal and should not be used by external code.\n   * It is exposed for internal use by caching ({@link redis.clients.jedis.csc.CacheKey#getRedisKeys()})\n   * and cluster operations.\n   * <p>\n   * <b>Supported types:</b> Keys are stored as either {@link String} or {@code byte[]} depending on\n   * how they were added via {@link #key(Object)} or {@link #addHashSlotKey(String)}/{@link #addHashSlotKey(byte[])}.\n   * Only {@link String} and {@code byte[]} are guaranteed to be supported by downstream consumers.\n   * <p>\n   * <b>Type safety:</b> Consumers must handle both {@link String} and {@code byte[]} types.\n   * Passing other types may cause {@link IllegalArgumentException} when used with caching\n   * (see {@link redis.clients.jedis.csc.AbstractCache#makeKeyForRedisKeysToCacheKeys(Object)})\n   * or cluster operations.\n   * <p>\n   * The returned list is unmodifiable to prevent external modification of the internal key tracking.\n   *\n   * @return unmodifiable list of keys ({@link String} or {@code byte[]})\n   */\n  @Internal\n  public List<Object> getKeys() {\n    return Collections.unmodifiableList(keys);\n  }\n\n  @Internal\n  public Set<Integer> getKeyHashSlots() {\n    // Return cached slots if available (cache is invalidated when keys are added)\n    if (cachedHashSlots != null) {\n      return cachedHashSlots;\n    }\n\n    // Compute hash slots and cache the result\n    Set<Integer> slots = new HashSet<>();\n    for (Object key : keys) {\n      if (key instanceof byte[]) {\n        slots.add(JedisClusterCRC16.getSlot((byte[]) key));\n      } else {\n        slots.add(JedisClusterCRC16.getSlot((String) key));\n      }\n    }\n    // Cache as unmodifiable set to prevent external modification\n    cachedHashSlots = Collections.unmodifiableSet(slots);\n    return cachedHashSlots;\n  }\n\n  /**\n   * @return true if this command has no keys, false otherwise\n   */\n  public boolean isKeyless() {\n    return keys.isEmpty();\n  }\n\n  public boolean isBlocking() {\n    return blocking;\n  }\n\n  public CommandArguments blocking() {\n    this.blocking = true;\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/CommandFlagsRegistry.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.EnumSet;\n\n/**\n * Registry interface for command flags. Provides a mapping from Redis commands to their flags. This\n * interface allows for different implementations of the flags registry.\n */\npublic interface CommandFlagsRegistry {\n\n  /**\n   * Command flags based on command flags exposed by Redis. See\n   * <a href=\"https://redis.io/docs/latest/commands/command/#flags\">Command flags</a> for more\n   * details.\n   * <p>\n   * Flags description:\n   * <ul>\n   * <li>READONLY: Command doesn't modify data</li>\n   * <li>WRITE: Command may modify data</li>\n   * <li>DENYOOM: Command may increase memory usage (deny if out of memory)</li>\n   * <li>ADMIN: Administrative command</li>\n   * <li>PUBSUB: Pub/Sub related command</li>\n   * <li>NOSCRIPT: Command not allowed in scripts</li>\n   * <li>SORT_FOR_SCRIPT: Command output needs sorting for scripts</li>\n   * <li>LOADING: Command allowed while database is loading</li>\n   * <li>STALE: Command allowed on stale replicas</li>\n   * <li>SKIP_MONITOR: Command not shown in MONITOR output</li>\n   * <li>ASKING: Command allowed in cluster ASKING state</li>\n   * <li>FAST: Command has O(1) time complexity</li>\n   * <li>MOVABLEKEYS: Command key positions may vary</li>\n   * <li>MODULE: Module command</li>\n   * <li>BLOCKING: Command may block the client</li>\n   * <li>NO_AUTH: Command allowed without authentication</li>\n   * <li>NO_ASYNC_LOADING: Command not allowed during async loading</li>\n   * <li>NO_MULTI: Command not allowed in MULTI/EXEC</li>\n   * <li>NO_MANDATORY_KEYS: Command may work without keys</li>\n   * <li>ALLOW_BUSY: Command allowed when server is busy</li>\n   * </ul>\n   */\n  enum CommandFlag {\n    READONLY, WRITE, DENYOOM, ADMIN, PUBSUB, NOSCRIPT, SORT_FOR_SCRIPT, LOADING, STALE,\n    SKIP_MONITOR, SKIP_SLOWLOG, ASKING, FAST, MOVABLEKEYS, MODULE, BLOCKING, NO_AUTH,\n    NO_ASYNC_LOADING, NO_MULTI, NO_MANDATORY_KEYS, ALLOW_BUSY\n  }\n\n  /**\n   * Request policy for commands in a clustered deployment. This tip helps clients determine which\n   * shards to send the command to in clustering mode. See\n   * <a href=\"https://redis.io/docs/latest/develop/reference/command-tips/#request_policy\"> Request\n   * policy</a> for more details.\n   * <p>\n   * Policy values:\n   * <ul>\n   * <li>DEFAULT: No specific request policy defined. For commands without key arguments, execute on\n   * an arbitrary shard. For commands with key arguments, route to a single shard based on the hash\n   * slot of input keys.</li>\n   * <li>ALL_NODES: Execute the command on all nodes - masters and replicas alike. Example: CONFIG\n   * SET. Used by commands that don't accept key name arguments and operate atomically per\n   * shard.</li>\n   * <li>ALL_SHARDS: Execute the command on all master shards. Example: DBSIZE. Used by commands\n   * that don't accept key name arguments and operate atomically per shard.</li>\n   * <li>MULTI_SHARD: Execute the command on several shards. The client should split the inputs\n   * according to the hash slots of input key name arguments. Example: DEL, MSET, MGET.</li>\n   * <li>SPECIAL: Indicates a non-trivial form of request policy. Example: SCAN.</li>\n   * </ul>\n   */\n  enum RequestPolicy {\n    DEFAULT, ALL_NODES, ALL_SHARDS, MULTI_SHARD, SPECIAL\n  }\n\n  /**\n   * Response policy for commands in a clustered deployment. This tip helps clients determine how to\n   * aggregate replies from multiple shards in a cluster. See\n   * <a href=\"https://redis.io/docs/latest/develop/reference/command-tips/#response_policy\">\n   * Response policy</a> for more details.\n   * <p>\n   * Policy values:\n   * <ul>\n   * <li>DEFAULT: No specific response policy defined. For commands without key arguments, aggregate\n   * all replies within a single nested data structure. For commands with key arguments, retain the\n   * same order of replies as the input key names.</li>\n   * <li>ONE_SUCCEEDED: Return success if at least one shard didn't reply with an error. Reply with\n   * the first non-error reply obtained. Example: SCRIPT KILL.</li>\n   * <li>ALL_SUCCEEDED: Return successfully only if there are no error replies. A single error reply\n   * should disqualify the aggregate. Example: CONFIG SET, SCRIPT FLUSH.</li>\n   * <li>AGG_LOGICAL_AND: Return the result of a logical AND operation on all replies. Only applies\n   * to integer replies (0 or 1). Example: SCRIPT EXISTS.</li>\n   * <li>AGG_LOGICAL_OR: Return the result of a logical OR operation on all replies. Only applies to\n   * integer replies (0 or 1).</li>\n   * <li>AGG_MIN: Return the minimal value from the replies. Only applies to numerical replies.\n   * Example: WAIT.</li>\n   * <li>AGG_MAX: Return the maximal value from the replies. Only applies to numerical replies.</li>\n   * <li>AGG_SUM: Return the sum of replies. Only applies to numerical replies. Example:\n   * DBSIZE.</li>\n   * <li>SPECIAL: Indicates a non-trivial form of reply policy. Example: INFO.</li>\n   * </ul>\n   */\n  enum ResponsePolicy {\n    DEFAULT, ONE_SUCCEEDED, ALL_SUCCEEDED, AGG_LOGICAL_AND, AGG_LOGICAL_OR, AGG_MIN, AGG_MAX,\n    AGG_SUM, SPECIAL\n  }\n\n  /**\n   * Get the flags for a given command.\n   * @param commandArguments the command arguments containing the command and its parameters\n   * @return EnumSet of CommandFlag for this command, or empty set if command has no flags\n   */\n  EnumSet<CommandFlag> getFlags(CommandArguments commandArguments);\n\n  /**\n   * Get the request policy for a given command. The request policy helps clients determine which\n   * shards to send the command to in a clustered deployment.\n   * @param commandArguments the command arguments containing the command and its parameters\n   * @return RequestPolicy for this command, or DEFAULT if no specific policy is defined\n   */\n  RequestPolicy getRequestPolicy(CommandArguments commandArguments);\n\n  /**\n   * Get the response policy for a given command. The response policy helps clients determine how to\n   * aggregate replies from multiple shards in a cluster.\n   * @param commandArguments the command arguments containing the command and its parameters\n   * @return ResponsePolicy for this command, or DEFAULT if no specific policy is defined\n   */\n  ResponsePolicy getResponsePolicy(CommandArguments commandArguments);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.annots.Experimental;\n\n@Experimental\npublic interface CommandKeyArgumentPreProcessor {\n\n  /**\n   * @param paramKey key name in application\n   * @return key name in Redis server\n   */\n  Object actualKey(Object paramKey);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/CommandObject.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.Iterator;\nimport redis.clients.jedis.args.Rawable;\n\npublic class CommandObject<T> {\n\n  private final CommandArguments arguments;\n  private final Builder<T> builder;\n\n  public CommandObject(CommandArguments args, Builder<T> builder) {\n    this.arguments = args;\n    this.builder = builder;\n  }\n\n  public CommandArguments getArguments() {\n    return arguments;\n  }\n\n  public Builder<T> getBuilder() {\n    return builder;\n  }\n\n  @Override\n  public int hashCode() {\n    int hashCode = 1;\n    for (Rawable e : arguments) {\n      hashCode = 31 * hashCode + e.hashCode();\n    }\n    hashCode = 31 * hashCode + builder.hashCode();\n    return hashCode;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == this) {\n      return true;\n    }\n    if (!(o instanceof CommandObject)) {\n      return false;\n    }\n\n    Iterator<Rawable> e1 = arguments.iterator();\n    Iterator<Rawable> e2 = ((CommandObject) o).arguments.iterator();\n    while (e1.hasNext() && e2.hasNext()) {\n      Rawable o1 = e1.next();\n      Rawable o2 = e2.next();\n      if (!(o1 == null ? o2 == null : o1.equals(o2))) {\n        return false;\n      }\n    }\n    if (e1.hasNext() || e2.hasNext()) {\n      return false;\n    }\n\n    return builder == ((CommandObject) o).builder;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/CommandObjects.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.Command.*;\nimport static redis.clients.jedis.Protocol.Keyword.*;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.args.*;\nimport redis.clients.jedis.bloom.*;\nimport redis.clients.jedis.bloom.RedisBloomProtocol.*;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.json.*;\nimport redis.clients.jedis.json.JsonProtocol.JsonCommand;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.SearchProtocol.*;\nimport redis.clients.jedis.search.SearchResult.SearchResultBuilder;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.hybrid.FTHybridParams;\nimport redis.clients.jedis.search.hybrid.HybridResult;\nimport redis.clients.jedis.search.schemafields.SchemaField;\nimport redis.clients.jedis.timeseries.*;\nimport redis.clients.jedis.timeseries.TimeSeriesProtocol.*;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.CompareCondition;\n\npublic class CommandObjects {\n\n  private RedisProtocol protocol;\n\n  // TODO: restrict?\n  public final void setProtocol(RedisProtocol proto) {\n    this.protocol = proto;\n  }\n\n  // TODO: remove?\n  protected RedisProtocol getProtocol() {\n    return protocol;\n  }\n\n  protected volatile CommandKeyArgumentPreProcessor keyPreProcessor = null;\n  private Lock mapperLock = new ReentrantLock(true);\n  private volatile JsonObjectMapper jsonObjectMapper;\n  private final AtomicInteger searchDialect = new AtomicInteger(SearchProtocol.DEFAULT_DIALECT);\n\n  @Experimental\n  public void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) {\n    this.keyPreProcessor = keyPreProcessor;\n  }\n\n  protected CommandArguments commandArguments(ProtocolCommand command) {\n    CommandArguments comArgs = new CommandArguments(command);\n    if (keyPreProcessor != null) comArgs.setKeyArgumentPreProcessor(keyPreProcessor);\n    return comArgs;\n  }\n\n  private final CommandObject<String> PING_COMMAND_OBJECT = new CommandObject<>(commandArguments(PING), BuilderFactory.STRING);\n\n  public final CommandObject<String> ping() {\n    return PING_COMMAND_OBJECT;\n  }\n\n  public final CommandObject<String> echo(String msg) {\n    return new CommandObject<>(commandArguments(ECHO).add(msg), BuilderFactory.STRING);\n  }\n\n  private final CommandObject<String> FLUSHALL_COMMAND_OBJECT = new CommandObject<>(commandArguments(FLUSHALL), BuilderFactory.STRING);\n\n  public final CommandObject<String> flushAll() {\n    return FLUSHALL_COMMAND_OBJECT;\n  }\n\n  private final CommandObject<String> FLUSHDB_COMMAND_OBJECT = new CommandObject<>(commandArguments(FLUSHDB), BuilderFactory.STRING);\n\n  public final CommandObject<String> flushDB() {\n    return FLUSHDB_COMMAND_OBJECT;\n  }\n\n  public final CommandObject<String> configSet(String parameter, String value) {\n    return new CommandObject<>(commandArguments(Command.CONFIG).add(Keyword.SET).add(parameter).add(value), BuilderFactory.STRING);\n  }\n\n  private final CommandObject<String> INFO_COMMAND_OBJECT = new CommandObject<>(commandArguments(Command.INFO),\n      BuilderFactory.STRING);\n\n  public final CommandObject<String> info() {\n    return INFO_COMMAND_OBJECT;\n  }\n\n  public final CommandObject<String> info(String section) {\n    return new CommandObject<>(commandArguments(Command.INFO).add(section), BuilderFactory.STRING);\n  }\n\n  // Key commands\n  public final CommandObject<Boolean> exists(String key) {\n    return new CommandObject<>(commandArguments(Command.EXISTS).key(key), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> exists(String... keys) {\n    return new CommandObject<>(commandArguments(Command.EXISTS).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Boolean> exists(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.EXISTS).key(key), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> exists(byte[]... keys) {\n    return new CommandObject<>(commandArguments(Command.EXISTS).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> persist(String key) {\n    return new CommandObject<>(commandArguments(Command.PERSIST).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> persist(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.PERSIST).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> type(String key) {\n    return new CommandObject<>(commandArguments(Command.TYPE).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> type(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.TYPE).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> dump(String key) {\n    return new CommandObject<>(commandArguments(Command.DUMP).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> dump(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.DUMP).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<String> restore(String key, long ttl, byte[] serializedValue) {\n    return new CommandObject<>(commandArguments(RESTORE).key(key).add(ttl)\n        .add(serializedValue), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> restore(String key, long ttl, byte[] serializedValue, RestoreParams params) {\n    return new CommandObject<>(commandArguments(RESTORE).key(key).add(ttl)\n        .add(serializedValue).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> restore(byte[] key, long ttl, byte[] serializedValue) {\n    return new CommandObject<>(commandArguments(RESTORE).key(key).add(ttl)\n        .add(serializedValue), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> restore(byte[] key, long ttl, byte[] serializedValue, RestoreParams params) {\n    return new CommandObject<>(commandArguments(RESTORE).key(key).add(ttl)\n        .add(serializedValue).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> expire(String key, long seconds) {\n    return new CommandObject<>(commandArguments(EXPIRE).key(key).add(seconds), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expire(byte[] key, long seconds) {\n    return new CommandObject<>(commandArguments(EXPIRE).key(key).add(seconds), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expire(String key, long seconds, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(EXPIRE).key(key).add(seconds).add(expiryOption),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expire(byte[] key, long seconds, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(EXPIRE).key(key).add(seconds).add(expiryOption),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpire(String key, long milliseconds) {\n    return new CommandObject<>(commandArguments(PEXPIRE).key(key).add(milliseconds), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpire(byte[] key, long milliseconds) {\n    return new CommandObject<>(commandArguments(PEXPIRE).key(key).add(milliseconds), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpire(String key, long milliseconds, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(PEXPIRE).key(key).add(milliseconds).add(expiryOption),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpire(byte[] key, long milliseconds, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(PEXPIRE).key(key).add(milliseconds).add(expiryOption),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expireTime(String key) {\n    return new CommandObject<>(commandArguments(EXPIRETIME).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expireTime(byte[] key) {\n    return new CommandObject<>(commandArguments(EXPIRETIME).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpireTime(String key) {\n    return new CommandObject<>(commandArguments(PEXPIRETIME).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpireTime(byte[] key) {\n    return new CommandObject<>(commandArguments(PEXPIRETIME).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expireAt(String key, long unixTime) {\n    return new CommandObject<>(commandArguments(EXPIREAT).key(key).add(unixTime), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expireAt(byte[] key, long unixTime) {\n    return new CommandObject<>(commandArguments(EXPIREAT).key(key).add(unixTime), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expireAt(String key, long unixTime, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(EXPIREAT).key(key).add(unixTime).add(expiryOption), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> expireAt(byte[] key, long unixTime, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(EXPIREAT).key(key).add(unixTime).add(expiryOption), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpireAt(String key, long millisecondsTimestamp) {\n    return new CommandObject<>(commandArguments(PEXPIREAT).key(key).add(millisecondsTimestamp), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpireAt(byte[] key, long millisecondsTimestamp) {\n    return new CommandObject<>(commandArguments(PEXPIREAT).key(key).add(millisecondsTimestamp), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpireAt(String key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(PEXPIREAT).key(key).add(millisecondsTimestamp).add(expiryOption),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pexpireAt(byte[] key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    return new CommandObject<>(commandArguments(PEXPIREAT).key(key).add(millisecondsTimestamp).add(expiryOption),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> ttl(String key) {\n    return new CommandObject<>(commandArguments(TTL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> ttl(byte[] key) {\n    return new CommandObject<>(commandArguments(TTL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pttl(String key) {\n    return new CommandObject<>(commandArguments(PTTL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pttl(byte[] key) {\n    return new CommandObject<>(commandArguments(PTTL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> touch(String key) {\n    return new CommandObject<>(commandArguments(TOUCH).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> touch(String... keys) {\n    return new CommandObject<>(commandArguments(TOUCH).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> touch(byte[] key) {\n    return new CommandObject<>(commandArguments(TOUCH).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> touch(byte[]... keys) {\n    return new CommandObject<>(commandArguments(TOUCH).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> sort(String key) {\n    return new CommandObject<>(commandArguments(SORT).key(key), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> sort(String key, SortingParams sortingParams) {\n    return new CommandObject<>(commandArguments(SORT).key(key).addParams(sortingParams), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> sort(byte[] key) {\n    return new CommandObject<>(commandArguments(SORT).key(key), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> sort(byte[] key, SortingParams sortingParams) {\n    return new CommandObject<>(commandArguments(SORT).key(key).addParams(sortingParams), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Long> sort(String key, String dstkey) {\n    return new CommandObject<>(commandArguments(SORT).key(key)\n        .add(STORE).key(dstkey), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sort(String key, SortingParams sortingParams, String dstkey) {\n    return new CommandObject<>(commandArguments(SORT).key(key).addParams(sortingParams)\n        .add(STORE).key(dstkey), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sort(byte[] key, byte[] dstkey) {\n    return new CommandObject<>(commandArguments(SORT).key(key)\n        .add(STORE).key(dstkey), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sort(byte[] key, SortingParams sortingParams, byte[] dstkey) {\n    return new CommandObject<>(commandArguments(SORT).key(key).addParams(sortingParams)\n        .add(STORE).key(dstkey), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<byte[]>> sortReadonly(byte[] key, SortingParams sortingParams) {\n    return new CommandObject<>(commandArguments(SORT_RO).key(key).addParams(sortingParams),\n        BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<String>> sortReadonly(String key, SortingParams sortingParams) {\n    return new CommandObject<>(commandArguments(SORT_RO).key(key).addParams(sortingParams),\n        BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Long> del(String key) {\n    return new CommandObject<>(commandArguments(DEL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> del(String... keys) {\n    return new CommandObject<>(commandArguments(DEL).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> del(byte[] key) {\n    return new CommandObject<>(commandArguments(DEL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> del(byte[]... keys) {\n    return new CommandObject<>(commandArguments(DEL).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> delex(String key, CompareCondition cond) {\n    CommandArguments ca = commandArguments(Command.DELEX).key(key);\n    cond.addTo(ca);\n    return new CommandObject<>(ca, BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> delex(byte[] key, CompareCondition cond) {\n    CommandArguments ca = commandArguments(Command.DELEX).key(key);\n    cond.addTo(ca);\n    return new CommandObject<>(ca, BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> unlink(String key) {\n    return new CommandObject<>(commandArguments(UNLINK).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> unlink(String... keys) {\n    return new CommandObject<>(commandArguments(UNLINK).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> unlink(byte[] key) {\n    return new CommandObject<>(commandArguments(UNLINK).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> unlink(byte[]... keys) {\n    return new CommandObject<>(commandArguments(UNLINK).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Boolean> copy(String srcKey, String dstKey, boolean replace) {\n    CommandArguments args = commandArguments(Command.COPY).key(srcKey).key(dstKey);\n    if (replace) {\n      args.add(REPLACE);\n    }\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> copy(byte[] srcKey, byte[] dstKey, boolean replace) {\n    CommandArguments args = commandArguments(Command.COPY).key(srcKey).key(dstKey);\n    if (replace) {\n      args.add(REPLACE);\n    }\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<String> rename(String oldkey, String newkey) {\n    return new CommandObject<>(commandArguments(RENAME).key(oldkey).key(newkey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> renamenx(String oldkey, String newkey) {\n    return new CommandObject<>(commandArguments(RENAMENX).key(oldkey).key(newkey), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> rename(byte[] oldkey, byte[] newkey) {\n    return new CommandObject<>(commandArguments(RENAME).key(oldkey).key(newkey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> renamenx(byte[] oldkey, byte[] newkey) {\n    return new CommandObject<>(commandArguments(RENAMENX).key(oldkey).key(newkey), BuilderFactory.LONG);\n  }\n\n  public CommandObject<Long> dbSize() {\n    return new CommandObject<>(commandArguments(DBSIZE), BuilderFactory.LONG);\n  }\n\n  public CommandObject<Set<String>> keys(String pattern) {\n    CommandArguments args = commandArguments(Command.KEYS).add(pattern);\n    return new CommandObject<>(args, BuilderFactory.STRING_SET);\n  }\n\n  public CommandObject<Set<byte[]>> keys(byte[] pattern) {\n    CommandArguments args = commandArguments(Command.KEYS).add(pattern);\n    return new CommandObject<>(args, BuilderFactory.BINARY_SET);\n  }\n\n  public CommandObject<ScanResult<String>> scan(String cursor) {\n    return new CommandObject<>(commandArguments(SCAN).add(cursor), BuilderFactory.SCAN_RESPONSE);\n  }\n\n  public CommandObject<ScanResult<String>> scan(String cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params), BuilderFactory.SCAN_RESPONSE);\n  }\n\n  public CommandObject<ScanResult<String>> scan(String cursor, ScanParams params, String type) {\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).add(Keyword.TYPE).add(type), BuilderFactory.SCAN_RESPONSE);\n  }\n\n  public CommandObject<ScanResult<byte[]>> scan(byte[] cursor) {\n    return new CommandObject<>(commandArguments(SCAN).add(cursor), BuilderFactory.SCAN_BINARY_RESPONSE);\n  }\n\n  public CommandObject<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params), BuilderFactory.SCAN_BINARY_RESPONSE);\n  }\n\n  public CommandObject<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params, byte[] type) {\n    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).add(Keyword.TYPE).add(type), BuilderFactory.SCAN_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<String> randomKey() {\n    return new CommandObject<>(commandArguments(RANDOMKEY), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> randomBinaryKey() {\n    return new CommandObject<>(commandArguments(RANDOMKEY), BuilderFactory.BINARY);\n  }\n  // Key commands\n\n  // String commands\n  public final CommandObject<String> set(String key, String value) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> set(String key, String value, SetParams params) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> set(byte[] key, byte[] value) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> set(byte[] key, byte[] value, SetParams params) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> get(String key) {\n    return new CommandObject<>(commandArguments(Command.GET).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> digestKey(String key) {\n    return new CommandObject<>(commandArguments(Command.DIGEST).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> setGet(String key, String value) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value).add(Keyword.GET), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> setGet(String key, String value, SetParams params) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value).addParams(params).add(Keyword.GET),\n        BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> getDel(String key) {\n    return new CommandObject<>(commandArguments(Command.GETDEL).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> getEx(String key, GetExParams params) {\n    return new CommandObject<>(commandArguments(Command.GETEX).key(key).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> digestKey(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.DIGEST).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> get(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.GET).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> setGet(byte[] key, byte[] value) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value).add(Keyword.GET), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> setGet(byte[] key, byte[] value, SetParams params) {\n    return new CommandObject<>(commandArguments(Command.SET).key(key).add(value).addParams(params).add(Keyword.GET),\n        BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> getDel(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.GETDEL).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> getEx(byte[] key, GetExParams params) {\n    return new CommandObject<>(commandArguments(Command.GETEX).key(key).addParams(params), BuilderFactory.BINARY);\n  }\n\n  /**\n   * @deprecated Use {@link CommandObjects#setGet(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  public final CommandObject<String> getSet(String key, String value) {\n    return new CommandObject<>(commandArguments(Command.GETSET).key(key).add(value), BuilderFactory.STRING);\n  }\n\n  /**\n   * @deprecated Use {@link CommandObjects#setGet(byte[], byte[])}.\n   */\n  @Deprecated\n  public final CommandObject<byte[]> getSet(byte[] key, byte[] value) {\n    return new CommandObject<>(commandArguments(Command.GETSET).key(key).add(value), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<Long> setnx(String key, String value) {\n    return new CommandObject<>(commandArguments(SETNX).key(key).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> setex(String key, long seconds, String value) {\n    return new CommandObject<>(commandArguments(SETEX).key(key).add(seconds).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> psetex(String key, long milliseconds, String value) {\n    return new CommandObject<>(commandArguments(PSETEX).key(key).add(milliseconds).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> setnx(byte[] key, byte[] value) {\n    return new CommandObject<>(commandArguments(SETNX).key(key).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> setex(byte[] key, long seconds, byte[] value) {\n    return new CommandObject<>(commandArguments(SETEX).key(key).add(seconds).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> psetex(byte[] key, long milliseconds, byte[] value) {\n    return new CommandObject<>(commandArguments(PSETEX).key(key).add(milliseconds).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Boolean> setbit(String key, long offset, boolean value) {\n    return new CommandObject<>(commandArguments(SETBIT).key(key).add(offset).add(value), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> setbit(byte[] key, long offset, boolean value) {\n    return new CommandObject<>(commandArguments(SETBIT).key(key).add(offset).add(value), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> getbit(String key, long offset) {\n    return new CommandObject<>(commandArguments(GETBIT).key(key).add(offset), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> getbit(byte[] key, long offset) {\n    return new CommandObject<>(commandArguments(GETBIT).key(key).add(offset), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> setrange(String key, long offset, String value) {\n    return new CommandObject<>(commandArguments(SETRANGE).key(key).add(offset).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> setrange(byte[] key, long offset, byte[] value) {\n    return new CommandObject<>(commandArguments(SETRANGE).key(key).add(offset).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> getrange(String key, long startOffset, long endOffset) {\n    return new CommandObject<>(commandArguments(GETRANGE).key(key).add(startOffset).add(endOffset), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> getrange(byte[] key, long startOffset, long endOffset) {\n    return new CommandObject<>(commandArguments(GETRANGE).key(key).add(startOffset).add(endOffset), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<String>> mget(String... keys) {\n    return new CommandObject<>(commandArguments(MGET).keys((Object[]) keys), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> mget(byte[]... keys) {\n    return new CommandObject<>(commandArguments(MGET).keys((Object[]) keys), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<String> mset(String... keysvalues) {\n    return new CommandObject<>(addFlatKeyValueArgs(commandArguments(MSET), keysvalues), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> msetnx(String... keysvalues) {\n    return new CommandObject<>(addFlatKeyValueArgs(commandArguments(MSETNX), keysvalues), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Boolean> msetex(MSetExParams params, String... keysvalues) {\n    CommandArguments args = commandArguments(Command.MSETEX).add(keysvalues.length / 2);\n    addFlatKeyValueArgs(args, keysvalues);\n    args.addParams(params);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> msetex(MSetExParams params, byte[]... keysvalues) {\n    CommandArguments args = commandArguments(Command.MSETEX).add(keysvalues.length / 2);\n    addFlatKeyValueArgs(args, keysvalues);\n    args.addParams(params);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<String> mset(byte[]... keysvalues) {\n    return new CommandObject<>(addFlatKeyValueArgs(commandArguments(MSET), keysvalues), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> msetnx(byte[]... keysvalues) {\n    return new CommandObject<>(addFlatKeyValueArgs(commandArguments(MSETNX), keysvalues), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> incr(String key) {\n    return new CommandObject<>(commandArguments(Command.INCR).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> incrBy(String key, long increment) {\n    return new CommandObject<>(commandArguments(INCRBY).key(key).add(increment), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> incrByFloat(String key, double increment) {\n    return new CommandObject<>(commandArguments(INCRBYFLOAT).key(key).add(increment), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Long> incr(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.INCR).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> incrBy(byte[] key, long increment) {\n    return new CommandObject<>(commandArguments(INCRBY).key(key).add(increment), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> incrByFloat(byte[] key, double increment) {\n    return new CommandObject<>(commandArguments(INCRBYFLOAT).key(key).add(increment), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Long> decr(String key) {\n    return new CommandObject<>(commandArguments(DECR).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> decrBy(String key, long decrement) {\n    return new CommandObject<>(commandArguments(DECRBY).key(key).add(decrement), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> decr(byte[] key) {\n    return new CommandObject<>(commandArguments(DECR).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> decrBy(byte[] key, long decrement) {\n    return new CommandObject<>(commandArguments(DECRBY).key(key).add(decrement), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> append(String key, String value) {\n    return new CommandObject<>(commandArguments(APPEND).key(key).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> append(byte[] key, byte[] value) {\n    return new CommandObject<>(commandArguments(APPEND).key(key).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> substr(String key, int start, int end) {\n    return new CommandObject<>(commandArguments(SUBSTR).key(key).add(start).add(end), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> substr(byte[] key, int start, int end) {\n    return new CommandObject<>(commandArguments(SUBSTR).key(key).add(start).add(end), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<Long> strlen(String key) {\n    return new CommandObject<>(commandArguments(STRLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> strlen(byte[] key) {\n    return new CommandObject<>(commandArguments(STRLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitcount(String key) {\n    return new CommandObject<>(commandArguments(BITCOUNT).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitcount(String key, long start, long end) {\n    return new CommandObject<>(commandArguments(BITCOUNT).key(key).add(start).add(end), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitcount(String key, long start, long end, BitCountOption option) {\n    return new CommandObject<>(commandArguments(BITCOUNT).key(key).add(start).add(end).add(option), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitcount(byte[] key) {\n    return new CommandObject<>(commandArguments(BITCOUNT).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitcount(byte[] key, long start, long end) {\n    return new CommandObject<>(commandArguments(BITCOUNT).key(key).add(start).add(end), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitcount(byte[] key, long start, long end, BitCountOption option) {\n    return new CommandObject<>(commandArguments(BITCOUNT).key(key).add(start).add(end).add(option), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitpos(String key, boolean value) {\n    return new CommandObject<>(commandArguments(BITPOS).key(key).add(value ? 1 : 0), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitpos(String key, boolean value, BitPosParams params) {\n    return new CommandObject<>(commandArguments(BITPOS).key(key).add(value ? 1 : 0).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitpos(byte[] key, boolean value) {\n    return new CommandObject<>(commandArguments(BITPOS).key(key).add(value ? 1 : 0), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitpos(byte[] key, boolean value, BitPosParams params) {\n    return new CommandObject<>(commandArguments(BITPOS).key(key).add(value ? 1 : 0).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> bitfield(String key, String... arguments) {\n    return new CommandObject<>(commandArguments(BITFIELD).key(key).addObjects((Object[]) arguments), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> bitfieldReadonly(String key, String... arguments) {\n    return new CommandObject<>(commandArguments(BITFIELD_RO).key(key).addObjects((Object[]) arguments), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> bitfield(byte[] key, byte[]... arguments) {\n    return new CommandObject<>(commandArguments(BITFIELD).key(key).addObjects((Object[]) arguments), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> bitfieldReadonly(byte[] key, byte[]... arguments) {\n    return new CommandObject<>(commandArguments(BITFIELD_RO).key(key).addObjects((Object[]) arguments), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<Long> bitop(BitOP op, String destKey, String... srcKeys) {\n    return new CommandObject<>(commandArguments(BITOP).add(op).key(destKey).keys((Object[]) srcKeys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> bitop(BitOP op, byte[] destKey, byte[]... srcKeys) {\n    return new CommandObject<>(commandArguments(BITOP).add(op).key(destKey).keys((Object[]) srcKeys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<LCSMatchResult> lcs(String keyA, String keyB, LCSParams params) {\n    return new CommandObject<>(commandArguments(Command.LCS).key(keyA).key(keyB)\n        .addParams(params), BuilderFactory.STR_ALGO_LCS_RESULT_BUILDER);\n  }\n\n  public final CommandObject<LCSMatchResult> lcs(byte[] keyA, byte[] keyB, LCSParams params) {\n    return new CommandObject<>(commandArguments(Command.LCS).key(keyA).key(keyB)\n        .addParams(params), BuilderFactory.STR_ALGO_LCS_RESULT_BUILDER);\n  }\n  // String commands\n\n  // List commands\n  public final CommandObject<Long> rpush(String key, String... strings) {\n    return new CommandObject<>(commandArguments(RPUSH).key(key).addObjects((Object[]) strings), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> rpush(byte[] key, byte[]... strings) {\n    return new CommandObject<>(commandArguments(RPUSH).key(key).addObjects((Object[]) strings), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lpush(String key, String... strings) {\n    return new CommandObject<>(commandArguments(LPUSH).key(key).addObjects((Object[]) strings), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lpush(byte[] key, byte[]... strings) {\n    return new CommandObject<>(commandArguments(LPUSH).key(key).addObjects((Object[]) strings), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> llen(String key) {\n    return new CommandObject<>(commandArguments(LLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> llen(byte[] key) {\n    return new CommandObject<>(commandArguments(LLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> lrange(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(LRANGE).key(key).add(start).add(stop), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> lrange(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(LRANGE).key(key).add(start).add(stop), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<String> ltrim(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(LTRIM).key(key).add(start).add(stop), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ltrim(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(LTRIM).key(key).add(start).add(stop), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> lindex(String key, long index) {\n    return new CommandObject<>(commandArguments(LINDEX).key(key).add(index), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> lindex(byte[] key, long index) {\n    return new CommandObject<>(commandArguments(LINDEX).key(key).add(index), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<String> lset(String key, long index, String value) {\n    return new CommandObject<>(commandArguments(LSET).key(key).add(index).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> lset(byte[] key, long index, byte[] value) {\n    return new CommandObject<>(commandArguments(LSET).key(key).add(index).add(value), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> lrem(String key, long count, String value) {\n    return new CommandObject<>(commandArguments(LREM).key(key).add(count).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lrem(byte[] key, long count, byte[] value) {\n    return new CommandObject<>(commandArguments(LREM).key(key).add(count).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> lpop(String key) {\n    return new CommandObject<>(commandArguments(LPOP).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> lpop(String key, int count) {\n    return new CommandObject<>(commandArguments(LPOP).key(key).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<byte[]> lpop(byte[] key) {\n    return new CommandObject<>(commandArguments(LPOP).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<byte[]>> lpop(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(LPOP).key(key).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<String> rpop(String key) {\n    return new CommandObject<>(commandArguments(RPOP).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> rpop(String key, int count) {\n    return new CommandObject<>(commandArguments(RPOP).key(key).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<byte[]> rpop(byte[] key) {\n    return new CommandObject<>(commandArguments(RPOP).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<byte[]>> rpop(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(RPOP).key(key).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Long> lpos(String key, String element) {\n    return new CommandObject<>(commandArguments(LPOS).key(key).add(element), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lpos(String key, String element, LPosParams params) {\n    return new CommandObject<>(commandArguments(LPOS).key(key).add(element).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> lpos(String key, String element, LPosParams params, long count) {\n    return new CommandObject<>(commandArguments(LPOS).key(key).add(element)\n        .addParams(params).add(COUNT).add(count), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<Long> lpos(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(LPOS).key(key).add(element), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lpos(byte[] key, byte[] element, LPosParams params) {\n    return new CommandObject<>(commandArguments(LPOS).key(key).add(element).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> lpos(byte[] key, byte[] element, LPosParams params, long count) {\n    return new CommandObject<>(commandArguments(LPOS).key(key).add(element)\n        .addParams(params).add(COUNT).add(count), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<Long> linsert(String key, ListPosition where, String pivot, String value) {\n    return new CommandObject<>(commandArguments(LINSERT).key(key).add(where)\n        .add(pivot).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value) {\n    return new CommandObject<>(commandArguments(LINSERT).key(key).add(where)\n        .add(pivot).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lpushx(String key, String... strings) {\n    return new CommandObject<>(commandArguments(LPUSHX).key(key).addObjects((Object[]) strings), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> rpushx(String key, String... strings) {\n    return new CommandObject<>(commandArguments(RPUSHX).key(key).addObjects((Object[]) strings), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> lpushx(byte[] key, byte[]... args) {\n    return new CommandObject<>(commandArguments(LPUSHX).key(key).addObjects((Object[]) args), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> rpushx(byte[] key, byte[]... args) {\n    return new CommandObject<>(commandArguments(RPUSHX).key(key).addObjects((Object[]) args), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> blpop(int timeout, String key) {\n    return new CommandObject<>(commandArguments(BLPOP).blocking().key(key).add(timeout), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> blpop(int timeout, String... keys) {\n    return new CommandObject<>(commandArguments(BLPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, String>> blpop(double timeout, String key) {\n    return new CommandObject<>(commandArguments(BLPOP).blocking().key(key).add(timeout), BuilderFactory.KEYED_ELEMENT);\n  }\n\n  public final CommandObject<KeyValue<String, String>> blpop(double timeout, String... keys) {\n    return new CommandObject<>(commandArguments(BLPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.KEYED_ELEMENT);\n  }\n\n  public final CommandObject<List<byte[]>> blpop(int timeout, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BLPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], byte[]>> blpop(double timeout, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BLPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.BINARY_KEYED_ELEMENT);\n  }\n\n  public final CommandObject<List<String>> brpop(int timeout, String key) {\n    return new CommandObject<>(commandArguments(BRPOP).blocking().key(key).add(timeout), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> brpop(int timeout, String... keys) {\n    return new CommandObject<>(commandArguments(BRPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, String>> brpop(double timeout, String key) {\n    return new CommandObject<>(commandArguments(BRPOP).blocking().key(key).add(timeout), BuilderFactory.KEYED_ELEMENT);\n  }\n\n  public final CommandObject<KeyValue<String, String>> brpop(double timeout, String... keys) {\n    return new CommandObject<>(commandArguments(BRPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.KEYED_ELEMENT);\n  }\n\n  public final CommandObject<List<byte[]>> brpop(int timeout, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BRPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], byte[]>> brpop(double timeout, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BRPOP).blocking().keys((Object[]) keys).add(timeout), BuilderFactory.BINARY_KEYED_ELEMENT);\n  }\n\n  public final CommandObject<String> rpoplpush(String srckey, String dstkey) {\n    return new CommandObject<>(commandArguments(RPOPLPUSH).key(srckey).key(dstkey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> brpoplpush(String source, String destination, int timeout) {\n    return new CommandObject<>(commandArguments(BRPOPLPUSH).blocking().key(source)\n        .key(destination).add(timeout), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> rpoplpush(byte[] srckey, byte[] dstkey) {\n    return new CommandObject<>(commandArguments(RPOPLPUSH).key(srckey).key(dstkey), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> brpoplpush(byte[] source, byte[] destination, int timeout) {\n    return new CommandObject<>(commandArguments(BRPOPLPUSH).blocking().key(source)\n        .key(destination).add(timeout), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<String> lmove(String srcKey, String dstKey, ListDirection from, ListDirection to) {\n    return new CommandObject<>(commandArguments(LMOVE).key(srcKey).key(dstKey)\n        .add(from).add(to), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> blmove(String srcKey, String dstKey, ListDirection from, ListDirection to, double timeout) {\n    return new CommandObject<>(commandArguments(BLMOVE).blocking().key(srcKey)\n        .key(dstKey).add(from).add(to).add(timeout), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> lmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to) {\n    return new CommandObject<>(commandArguments(LMOVE).key(srcKey).key(dstKey)\n        .add(from).add(to), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<byte[]> blmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to, double timeout) {\n    return new CommandObject<>(commandArguments(BLMOVE).blocking().key(srcKey)\n        .key(dstKey).add(from).add(to).add(timeout), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<KeyValue<String, List<String>>> lmpop(ListDirection direction, String... keys) {\n    return new CommandObject<>(commandArguments(LMPOP).add(keys.length).keys((Object[]) keys)\n        .add(direction), BuilderFactory.KEYED_STRING_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, List<String>>> lmpop(ListDirection direction, int count, String... keys) {\n    return new CommandObject<>(commandArguments(LMPOP).add(keys.length).keys((Object[]) keys)\n        .add(direction).add(COUNT).add(count), BuilderFactory.KEYED_STRING_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, List<String>>> blmpop(double timeout, ListDirection direction, String... keys) {\n    return new CommandObject<>(commandArguments(BLMPOP).blocking().add(timeout)\n        .add(keys.length).keys((Object[]) keys).add(direction), BuilderFactory.KEYED_STRING_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, List<String>>> blmpop(double timeout, ListDirection direction, int count, String... keys) {\n    return new CommandObject<>(commandArguments(BLMPOP).blocking().add(timeout)\n        .add(keys.length).keys((Object[]) keys).add(direction).add(COUNT).add(count),\n        BuilderFactory.KEYED_STRING_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<byte[]>>> lmpop(ListDirection direction, byte[]... keys) {\n    return new CommandObject<>(commandArguments(LMPOP).add(keys.length).keys((Object[]) keys)\n        .add(direction), BuilderFactory.KEYED_BINARY_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<byte[]>>> lmpop(ListDirection direction, int count, byte[]... keys) {\n    return new CommandObject<>(commandArguments(LMPOP).add(keys.length).keys((Object[]) keys)\n        .add(direction).add(COUNT).add(count), BuilderFactory.KEYED_BINARY_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<byte[]>>> blmpop(double timeout, ListDirection direction, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BLMPOP).blocking().add(timeout)\n        .add(keys.length).keys((Object[]) keys).add(direction), BuilderFactory.KEYED_BINARY_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<byte[]>>> blmpop(double timeout, ListDirection direction, int count, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BLMPOP).blocking().add(timeout)\n        .add(keys.length).keys((Object[]) keys).add(direction).add(COUNT).add(count),\n        BuilderFactory.KEYED_BINARY_LIST);\n  }\n  // List commands\n\n  // Hash commands\n  public final CommandObject<Long> hset(String key, String field, String value) {\n    return new CommandObject<>(commandArguments(HSET).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hset(String key, Map<String, String> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HSET).key(key), hash), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hsetex(String key, HSetExParams params, String field, String value) {\n    return new CommandObject<>(commandArguments(HSETEX).key(key)\n      .addParams(params).add(FIELDS).add(1).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hsetex(String key, HSetExParams params, Map<String, String> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HSETEX).key(key)\n      .addParams(params).add(FIELDS).add(hash.size()), hash), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> hget(String key, String field) {\n    return new CommandObject<>(commandArguments(HGET).key(key).add(field), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> hgetex(String key, HGetExParams params, String... fields) {\n    return new CommandObject<>(commandArguments(Command.HGETEX).key(key)\n      .addParams(params).add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> hgetdel(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HGETDEL).key(key)\n      .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Long> hsetnx(String key, String field, String value) {\n    return new CommandObject<>(commandArguments(HSETNX).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> hmset(String key, Map<String, String> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HMSET).key(key), hash), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> hmget(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HMGET).key(key).addObjects((Object[]) fields), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Long> hset(byte[] key, byte[] field, byte[] value) {\n    return new CommandObject<>(commandArguments(HSET).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hset(byte[] key, Map<byte[], byte[]> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HSET).key(key), hash), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) {\n    return new CommandObject<>(commandArguments(HSETEX).key(key)\n      .addParams(params).add(FIELDS).add(1).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hsetex(byte[] key, HSetExParams params, Map<byte[], byte[]> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HSETEX).key(key)\n      .addParams(params).add(FIELDS).add(hash.size()), hash), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<byte[]> hget(byte[] key, byte[] field) {\n    return new CommandObject<>(commandArguments(HGET).key(key).add(field), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<byte[]>> hgetex(byte[] key, HGetExParams params, byte[]... fields) {\n    return new CommandObject<>(commandArguments(Command.HGETEX).key(key)\n      .addParams(params).add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> hgetdel(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HGETDEL).key(key).add(FIELDS)\n      .add(fields.length).addObjects((Object[]) fields), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Long> hsetnx(byte[] key, byte[] field, byte[] value) {\n    return new CommandObject<>(commandArguments(HSETNX).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> hmset(byte[] key, Map<byte[], byte[]> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HMSET).key(key), hash), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<byte[]>> hmget(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HMGET).key(key).addObjects((Object[]) fields), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Long> hincrBy(String key, String field, long value) {\n    return new CommandObject<>(commandArguments(HINCRBY).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> hincrByFloat(String key, String field, double value) {\n    return new CommandObject<>(commandArguments(HINCRBYFLOAT).key(key).add(field).add(value), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Boolean> hexists(String key, String field) {\n    return new CommandObject<>(commandArguments(HEXISTS).key(key).add(field), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> hdel(String key, String... field) {\n    return new CommandObject<>(commandArguments(HDEL).key(key).addObjects((Object[]) field), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hlen(String key) {\n    return new CommandObject<>(commandArguments(HLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hincrBy(byte[] key, byte[] field, long value) {\n    return new CommandObject<>(commandArguments(HINCRBY).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> hincrByFloat(byte[] key, byte[] field, double value) {\n    return new CommandObject<>(commandArguments(HINCRBYFLOAT).key(key).add(field).add(value), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Boolean> hexists(byte[] key, byte[] field) {\n    return new CommandObject<>(commandArguments(HEXISTS).key(key).add(field), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> hdel(byte[] key, byte[]... field) {\n    return new CommandObject<>(commandArguments(HDEL).key(key).addObjects((Object[]) field), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hlen(byte[] key) {\n    return new CommandObject<>(commandArguments(HLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<String>> hkeys(String key) {\n    return new CommandObject<>(commandArguments(HKEYS).key(key), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<List<String>> hvals(String key) {\n    return new CommandObject<>(commandArguments(HVALS).key(key), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Set<byte[]>> hkeys(byte[] key) {\n    return new CommandObject<>(commandArguments(HKEYS).key(key), BuilderFactory.BINARY_SET);\n  }\n\n  public final CommandObject<List<byte[]>> hvals(byte[] key) {\n    return new CommandObject<>(commandArguments(HVALS).key(key), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Map<String, String>> hgetAll(String key) {\n    return new CommandObject<>(commandArguments(HGETALL).key(key), BuilderFactory.STRING_MAP);\n  }\n\n  public final CommandObject<String> hrandfield(String key) {\n    return new CommandObject<>(commandArguments(HRANDFIELD).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> hrandfield(String key, long count) {\n    return new CommandObject<>(commandArguments(HRANDFIELD).key(key).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Map.Entry<String, String>>> hrandfieldWithValues(String key, long count) {\n    return new CommandObject<>(commandArguments(HRANDFIELD).key(key).add(count).add(WITHVALUES),\n        protocol != RedisProtocol.RESP3 ? BuilderFactory.STRING_PAIR_LIST : BuilderFactory.STRING_PAIR_LIST_FROM_PAIRS);\n  }\n\n  public final CommandObject<Map<byte[], byte[]>> hgetAll(byte[] key) {\n    return new CommandObject<>(commandArguments(HGETALL).key(key), BuilderFactory.BINARY_MAP);\n  }\n\n  public final CommandObject<byte[]> hrandfield(byte[] key) {\n    return new CommandObject<>(commandArguments(HRANDFIELD).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<byte[]>> hrandfield(byte[] key, long count) {\n    return new CommandObject<>(commandArguments(HRANDFIELD).key(key).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Map.Entry<byte[], byte[]>>> hrandfieldWithValues(byte[] key, long count) {\n    return new CommandObject<>(commandArguments(HRANDFIELD).key(key).add(count).add(WITHVALUES),\n        protocol != RedisProtocol.RESP3 ? BuilderFactory.BINARY_PAIR_LIST : BuilderFactory.BINARY_PAIR_LIST_FROM_PAIRS);\n  }\n\n  public final CommandObject<ScanResult<Map.Entry<String, String>>> hscan(String key, String cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(HSCAN).key(key).add(cursor).addParams(params), BuilderFactory.HSCAN_RESPONSE);\n  }\n\n  public final CommandObject<ScanResult<String>> hscanNoValues(String key, String cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(HSCAN).key(key).add(cursor).addParams(params).add(NOVALUES), BuilderFactory.SCAN_RESPONSE);\n  }\n\n  public final CommandObject<ScanResult<Map.Entry<byte[], byte[]>>> hscan(byte[] key, byte[] cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(HSCAN).key(key).add(cursor).addParams(params), BuilderFactory.HSCAN_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<ScanResult<byte[]>> hscanNoValues(byte[] key, byte[] cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(HSCAN).key(key).add(cursor).addParams(params).add(NOVALUES), BuilderFactory.SCAN_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<Long> hstrlen(String key, String field) {\n    return new CommandObject<>(commandArguments(HSTRLEN).key(key).add(field), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hstrlen(byte[] key, byte[] field) {\n    return new CommandObject<>(commandArguments(HSTRLEN).key(key).add(field), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> hexpire(String key, long seconds, String... fields) {\n    return new CommandObject<>(commandArguments(HEXPIRE).key(key).add(seconds)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpire(String key, long seconds, ExpiryOption condition, String... fields) {\n    return new CommandObject<>(commandArguments(HEXPIRE).key(key).add(seconds).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpire(String key, long milliseconds, String... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIRE).key(key).add(milliseconds)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIRE).key(key).add(milliseconds).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpireAt(String key, long unixTimeSeconds, String... fields) {\n    return new CommandObject<>(commandArguments(HEXPIREAT).key(key).add(unixTimeSeconds)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) {\n    return new CommandObject<>(commandArguments(HEXPIREAT).key(key).add(unixTimeSeconds).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpireAt(String key, long unixTimeMillis, String... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIREAT).key(key).add(unixTimeMillis)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIREAT).key(key).add(unixTimeMillis).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpire(byte[] key, long seconds, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HEXPIRE).key(key).add(seconds)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HEXPIRE).key(key).add(seconds).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpire(byte[] key, long milliseconds, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIRE).key(key).add(milliseconds)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIRE).key(key).add(milliseconds).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HEXPIREAT).key(key).add(unixTimeSeconds)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HEXPIREAT).key(key).add(unixTimeSeconds).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIREAT).key(key).add(unixTimeMillis)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIREAT).key(key).add(unixTimeMillis).add(condition)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpireTime(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HEXPIRETIME).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpireTime(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIRETIME).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> httl(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HTTL).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpttl(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HPTTL).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hexpireTime(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HEXPIRETIME).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpexpireTime(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPEXPIRETIME).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> httl(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HTTL).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpttl(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPTTL).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpersist(String key, String... fields) {\n    return new CommandObject<>(commandArguments(HPERSIST).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> hpersist(byte[] key, byte[]... fields) {\n    return new CommandObject<>(commandArguments(HPERSIST).key(key)\n        .add(FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST);\n  }\n  // Hash commands\n\n  // Set commands\n  public final CommandObject<Long> sadd(String key, String... members) {\n    return new CommandObject<>(commandArguments(SADD).key(key).addObjects((Object[]) members), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sadd(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(SADD).key(key).addObjects((Object[]) members), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<String>> smembers(String key) {\n    return new CommandObject<>(commandArguments(SMEMBERS).key(key), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<Set<byte[]>> smembers(byte[] key) {\n    return new CommandObject<>(commandArguments(SMEMBERS).key(key), BuilderFactory.BINARY_SET);\n  }\n\n  public final CommandObject<Long> srem(String key, String... members) {\n    return new CommandObject<>(commandArguments(SREM).key(key).addObjects((Object[]) members), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> srem(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(SREM).key(key).addObjects((Object[]) members), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> spop(String key) {\n    return new CommandObject<>(commandArguments(SPOP).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> spop(byte[] key) {\n    return new CommandObject<>(commandArguments(SPOP).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<Set<String>> spop(String key, long count) {\n    return new CommandObject<>(commandArguments(SPOP).key(key).add(count), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<Set<byte[]>> spop(byte[] key, long count) {\n    return new CommandObject<>(commandArguments(SPOP).key(key).add(count), BuilderFactory.BINARY_SET);\n  }\n\n  public final CommandObject<Long> scard(String key) {\n    return new CommandObject<>(commandArguments(SCARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> scard(byte[] key) {\n    return new CommandObject<>(commandArguments(SCARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Boolean> sismember(String key, String member) {\n    return new CommandObject<>(commandArguments(SISMEMBER).key(key).add(member), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> sismember(byte[] key, byte[] member) {\n    return new CommandObject<>(commandArguments(SISMEMBER).key(key).add(member), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<Boolean>> smismember(String key, String... members) {\n    return new CommandObject<>(commandArguments(SMISMEMBER).key(key).addObjects((Object[]) members), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> smismember(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(SMISMEMBER).key(key).addObjects((Object[]) members), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<String> srandmember(String key) {\n    return new CommandObject<>(commandArguments(SRANDMEMBER).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> srandmember(byte[] key) {\n    return new CommandObject<>(commandArguments(SRANDMEMBER).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<String>> srandmember(String key, int count) {\n    return new CommandObject<>(commandArguments(SRANDMEMBER).key(key).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> srandmember(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(SRANDMEMBER).key(key).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<ScanResult<String>> sscan(String key, String cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(SSCAN).key(key).add(cursor).addParams(params), BuilderFactory.SSCAN_RESPONSE);\n  }\n\n  public final CommandObject<ScanResult<byte[]>> sscan(byte[] key, byte[] cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(SSCAN).key(key).add(cursor).addParams(params), BuilderFactory.SSCAN_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<Set<String>> sdiff(String... keys) {\n    return new CommandObject<>(commandArguments(SDIFF).keys((Object[]) keys), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<Long> sdiffstore(String dstkey, String... keys) {\n    return new CommandObject<>(commandArguments(SDIFFSTORE).key(dstkey).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<byte[]>> sdiff(byte[]... keys) {\n    return new CommandObject<>(commandArguments(SDIFF).keys((Object[]) keys), BuilderFactory.BINARY_SET);\n  }\n\n  public final CommandObject<Long> sdiffstore(byte[] dstkey, byte[]... keys) {\n    return new CommandObject<>(commandArguments(SDIFFSTORE).key(dstkey).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<String>> sinter(String... keys) {\n    return new CommandObject<>(commandArguments(SINTER).keys((Object[]) keys), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<Long> sinterstore(String dstkey, String... keys) {\n    return new CommandObject<>(commandArguments(SINTERSTORE).key(dstkey).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sintercard(String... keys) {\n    return new CommandObject<>(commandArguments(SINTERCARD).add(keys.length).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sintercard(int limit, String... keys) {\n    return new CommandObject<>(commandArguments(SINTERCARD).add(keys.length).keys((Object[]) keys).add(LIMIT).add(limit),BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<byte[]>> sinter(byte[]... keys) {\n    return new CommandObject<>(commandArguments(SINTER).keys((Object[]) keys), BuilderFactory.BINARY_SET);\n  }\n\n  public final CommandObject<Long> sinterstore(byte[] dstkey, byte[]... keys) {\n    return new CommandObject<>(commandArguments(SINTERSTORE).key(dstkey).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sintercard(byte[]... keys) {\n    return new CommandObject<>(commandArguments(SINTERCARD).add(keys.length).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> sintercard(int limit, byte[]... keys) {\n    return new CommandObject<>(commandArguments(SINTERCARD).add(keys.length).keys((Object[]) keys).add(LIMIT).add(limit),BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<String>> sunion(String... keys) {\n    return new CommandObject<>(commandArguments(SUNION).keys((Object[]) keys), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<Long> sunionstore(String dstkey, String... keys) {\n    return new CommandObject<>(commandArguments(SUNIONSTORE).key(dstkey).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<byte[]>> sunion(byte[]... keys) {\n    return new CommandObject<>(commandArguments(SUNION).keys((Object[]) keys), BuilderFactory.BINARY_SET);\n  }\n\n  public final CommandObject<Long> sunionstore(byte[] dstkey, byte[]... keys) {\n    return new CommandObject<>(commandArguments(SUNIONSTORE).key(dstkey).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> smove(String srckey, String dstkey, String member) {\n    return new CommandObject<>(commandArguments(SMOVE).key(srckey).key(dstkey).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> smove(byte[] srckey, byte[] dstkey, byte[] member) {\n    return new CommandObject<>(commandArguments(SMOVE).key(srckey).key(dstkey).add(member), BuilderFactory.LONG);\n  }\n  // Set commands\n\n  // Sorted Set commands\n  public final CommandObject<Long> zadd(String key, double score, String member) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).add(score).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zadd(String key, double score, String member, ZAddParams params) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).addParams(params)\n        .add(score).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zadd(String key, Map<String, Double> scoreMembers) {\n    return new CommandObject<>(addSortedSetFlatMapArgs(commandArguments(ZADD).key(key), scoreMembers), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {\n    return new CommandObject<>(addSortedSetFlatMapArgs(commandArguments(ZADD).key(key).addParams(params), scoreMembers), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> zaddIncr(String key, double score, String member, ZAddParams params) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).add(Keyword.INCR)\n        .addParams(params).add(score).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Long> zadd(byte[] key, double score, byte[] member) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).add(score).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zadd(byte[] key, double score, byte[] member, ZAddParams params) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).addParams(params)\n        .add(score).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zadd(byte[] key, Map<byte[], Double> scoreMembers) {\n    return new CommandObject<>(addSortedSetFlatMapArgs(commandArguments(ZADD).key(key), scoreMembers), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params) {\n    return new CommandObject<>(addSortedSetFlatMapArgs(commandArguments(ZADD).key(key).addParams(params), scoreMembers), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> zaddIncr(byte[] key, double score, byte[] member, ZAddParams params) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).add(Keyword.INCR)\n        .addParams(params).add(score).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> zincrby(String key, double increment, String member) {\n    return new CommandObject<>(commandArguments(ZINCRBY).key(key).add(increment).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> zincrby(String key, double increment, String member, ZIncrByParams params) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).addParams(params).add(increment).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> zincrby(byte[] key, double increment, byte[] member) {\n    return new CommandObject<>(commandArguments(ZINCRBY).key(key).add(increment).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> zincrby(byte[] key, double increment, byte[] member, ZIncrByParams params) {\n    return new CommandObject<>(commandArguments(ZADD).key(key).addParams(params).add(increment).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Long> zrem(String key, String... members) {\n    return new CommandObject<>(commandArguments(ZREM).key(key).addObjects((Object[]) members), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zrem(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(ZREM).key(key).addObjects((Object[]) members), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zrank(String key, String member) {\n    return new CommandObject<>(commandArguments(ZRANK).key(key).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zrevrank(String key, String member) {\n    return new CommandObject<>(commandArguments(ZREVRANK).key(key).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<KeyValue<Long, Double>> zrankWithScore(String key, String member) {\n    return new CommandObject<>(commandArguments(ZRANK).key(key).add(member).add(WITHSCORE), BuilderFactory.ZRANK_WITHSCORE_PAIR);\n  }\n\n  public final CommandObject<KeyValue<Long, Double>> zrevrankWithScore(String key, String member) {\n    return new CommandObject<>(commandArguments(ZREVRANK).key(key).add(member).add(WITHSCORE), BuilderFactory.ZRANK_WITHSCORE_PAIR);\n  }\n\n  public final CommandObject<Long> zrank(byte[] key, byte[] member) {\n    return new CommandObject<>(commandArguments(ZRANK).key(key).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zrevrank(byte[] key, byte[] member) {\n    return new CommandObject<>(commandArguments(ZREVRANK).key(key).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<KeyValue<Long, Double>> zrankWithScore(byte[] key, byte[] member) {\n    return new CommandObject<>(commandArguments(ZRANK).key(key).add(member).add(WITHSCORE), BuilderFactory.ZRANK_WITHSCORE_PAIR);\n  }\n\n  public final CommandObject<KeyValue<Long, Double>> zrevrankWithScore(byte[] key, byte[] member) {\n    return new CommandObject<>(commandArguments(ZREVRANK).key(key).add(member).add(WITHSCORE), BuilderFactory.ZRANK_WITHSCORE_PAIR);\n  }\n\n  public final CommandObject<String> zrandmember(String key) {\n    return new CommandObject<>(commandArguments(ZRANDMEMBER).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> zrandmember(String key, long count) {\n    return new CommandObject<>(commandArguments(ZRANDMEMBER).key(key).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrandmemberWithScores(String key, long count) {\n    return new CommandObject<>(commandArguments(ZRANDMEMBER).key(key).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<byte[]> zrandmember(byte[] key) {\n    return new CommandObject<>(commandArguments(ZRANDMEMBER).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<byte[]>> zrandmember(byte[] key, long count) {\n    return new CommandObject<>(commandArguments(ZRANDMEMBER).key(key).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrandmemberWithScores(byte[] key, long count) {\n    return new CommandObject<>(commandArguments(ZRANDMEMBER).key(key).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zcard(String key) {\n    return new CommandObject<>(commandArguments(ZCARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> zscore(String key, String member) {\n    return new CommandObject<>(commandArguments(ZSCORE).key(key).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<List<Double>> zmscore(String key, String... members) {\n    return new CommandObject<>(commandArguments(ZMSCORE).key(key).addObjects((Object[]) members), BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<Long> zcard(byte[] key) {\n    return new CommandObject<>(commandArguments(ZCARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> zscore(byte[] key, byte[] member) {\n    return new CommandObject<>(commandArguments(ZSCORE).key(key).add(member), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<List<Double>> zmscore(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(ZMSCORE).key(key).addObjects((Object[]) members), BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<Tuple> zpopmax(String key) {\n    return new CommandObject<>(commandArguments(ZPOPMAX).key(key), BuilderFactory.TUPLE);\n  }\n\n  public final CommandObject<List<Tuple>> zpopmax(String key, int count) {\n    return new CommandObject<>(commandArguments(ZPOPMAX).key(key).add(count), getTupleListBuilder());\n  }\n\n  public final CommandObject<Tuple> zpopmin(String key) {\n    return new CommandObject<>(commandArguments(ZPOPMIN).key(key), BuilderFactory.TUPLE);\n  }\n\n  public final CommandObject<List<Tuple>> zpopmin(String key, int count) {\n    return new CommandObject<>(commandArguments(ZPOPMIN).key(key).add(count), getTupleListBuilder());\n  }\n\n  public final CommandObject<Tuple> zpopmax(byte[] key) {\n    return new CommandObject<>(commandArguments(ZPOPMAX).key(key), BuilderFactory.TUPLE);\n  }\n\n  public final CommandObject<List<Tuple>> zpopmax(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(ZPOPMAX).key(key).add(count), getTupleListBuilder());\n  }\n\n  public final CommandObject<Tuple> zpopmin(byte[] key) {\n    return new CommandObject<>(commandArguments(ZPOPMIN).key(key), BuilderFactory.TUPLE);\n  }\n\n  public final CommandObject<List<Tuple>> zpopmin(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(ZPOPMIN).key(key).add(count), getTupleListBuilder());\n  }\n\n  public final CommandObject<KeyValue<String, Tuple>> bzpopmax(double timeout, String... keys) {\n    return new CommandObject<>(commandArguments(BZPOPMAX).blocking().keys((Object[]) keys).add(timeout),\n        BuilderFactory.KEYED_TUPLE);\n  }\n\n  public final CommandObject<KeyValue<String, Tuple>> bzpopmin(double timeout, String... keys) {\n    return new CommandObject<>(commandArguments(BZPOPMIN).blocking().keys((Object[]) keys).add(timeout),\n        BuilderFactory.KEYED_TUPLE);\n  }\n\n  public final CommandObject<KeyValue<byte[], Tuple>> bzpopmax(double timeout, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BZPOPMAX).blocking().keys((Object[]) keys)\n        .add(timeout), BuilderFactory.BINARY_KEYED_TUPLE);\n  }\n\n  public final CommandObject<KeyValue<byte[], Tuple>> bzpopmin(double timeout, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BZPOPMIN).blocking().keys((Object[]) keys)\n        .add(timeout), BuilderFactory.BINARY_KEYED_TUPLE);\n  }\n\n  public final CommandObject<Long> zcount(String key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZCOUNT).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zcount(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZCOUNT).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zcount(byte[] key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZCOUNT).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zcount(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZCOUNT).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> zrange(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key).add(start).add(stop), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrange(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZREVRANGE).key(key).add(start).add(stop), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrangeWithScores(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key)\n        .add(start).add(stop).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeWithScores(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZREVRANGE).key(key)\n        .add(start).add(stop).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<String>> zrange(String key, ZRangeParams zRangeParams) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key).addParams(zRangeParams), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrangeWithScores(String key, ZRangeParams zRangeParams) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key).addParams(zRangeParams).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zrangestore(String dest, String src, ZRangeParams zRangeParams) {\n    return new CommandObject<>(commandArguments(ZRANGESTORE).key(dest).add(src).addParams(zRangeParams), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> zrangeByScore(String key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrangeByScore(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrangeByScore(String key, double max, double min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrangeByScore(String key, String max, String min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrangeByScore(String key, double min, double max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrangeByScore(String key, String min, String max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrangeByScore(String key, double max, double min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrangeByScore(String key, String max, String min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(String key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(String key, double max, double min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(String key, String max, String min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(String key, double min, double max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(String key, String min, String max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<byte[]>> zrange(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key).add(start).add(stop), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrange(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZREVRANGE).key(key).add(start).add(stop), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrangeWithScores(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key)\n        .add(start).add(stop).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeWithScores(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZREVRANGE).key(key)\n        .add(start).add(stop).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<byte[]>> zrange(byte[] key, ZRangeParams zRangeParams) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key).addParams(zRangeParams), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrangeWithScores(byte[] key, ZRangeParams zRangeParams) {\n    return new CommandObject<>(commandArguments(ZRANGE).key(key).addParams(zRangeParams).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams) {\n    return new CommandObject<>(commandArguments(ZRANGESTORE).key(dest).add(src).addParams(zRangeParams), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<byte[]>> zrangeByScore(byte[] key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrangeByScore(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrangeByScore(byte[] key, double max, double min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrangeByScore(byte[] key, byte[] max, byte[] min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrangeByScore(byte[] key, double min, double max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrangeByScore(byte[] key, double max, double min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(byte[] key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, double max, double min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYSCORE).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYSCORE).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zremrangeByRank(String key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYRANK).key(key).add(start).add(stop), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zremrangeByScore(String key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zremrangeByScore(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zremrangeByRank(byte[] key, long start, long stop) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYRANK).key(key).add(start).add(stop), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zremrangeByScore(byte[] key, double min, double max) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zremrangeByScore(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYSCORE).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zlexcount(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZLEXCOUNT).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> zrangeByLex(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYLEX).key(key).add(min).add(max), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrangeByLex(String key, String min, String max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYLEX).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrangeByLex(String key, String max, String min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYLEX).key(key).add(max).add(min), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> zrevrangeByLex(String key, String max, String min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYLEX).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Long> zremrangeByLex(String key, String min, String max) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYLEX).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zlexcount(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZLEXCOUNT).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<byte[]>> zrangeByLex(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZRANGEBYLEX).key(key).add(min).add(max), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZRANGEBYLEX).key(key).add(min).add(max)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrangeByLex(byte[] key, byte[] max, byte[] min) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYLEX).key(key).add(max).add(min), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return new CommandObject<>(commandArguments(ZREVRANGEBYLEX).key(key).add(max).add(min)\n        .add(LIMIT).add(offset).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Long> zremrangeByLex(byte[] key, byte[] min, byte[] max) {\n    return new CommandObject<>(commandArguments(ZREMRANGEBYLEX).key(key).add(min).add(max), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<ScanResult<Tuple>> zscan(String key, String cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(ZSCAN).key(key).add(cursor).addParams(params), BuilderFactory.ZSCAN_RESPONSE);\n  }\n\n  public final CommandObject<ScanResult<Tuple>> zscan(byte[] key, byte[] cursor, ScanParams params) {\n    return new CommandObject<>(commandArguments(ZSCAN).key(key).add(cursor).addParams(params), BuilderFactory.ZSCAN_RESPONSE);\n  }\n\n  public final CommandObject<List<String>> zdiff(String... keys) {\n    return new CommandObject<>(commandArguments(ZDIFF).add(keys.length).keys((Object[]) keys),\n        BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zdiffWithScores(String... keys) {\n    return new CommandObject<>(commandArguments(ZDIFF).add(keys.length).keys((Object[]) keys)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  /**\n   * @deprecated Use {@link #zdiffstore(java.lang.String, java.lang.String...)}.\n   */\n  @Deprecated\n  public final CommandObject<Long> zdiffStore(String dstkey, String... keys) {\n    return zdiffstore(dstkey, keys);\n  }\n\n  public final CommandObject<Long> zdiffstore(String dstkey, String... keys) {\n    return new CommandObject<>(commandArguments(ZDIFFSTORE).key(dstkey)\n        .add(keys.length).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<byte[]>> zdiff(byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZDIFF).add(keys.length).keys((Object[]) keys), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zdiffWithScores(byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZDIFF).add(keys.length).keys((Object[]) keys)\n        .add(WITHSCORES), getTupleListBuilder());\n  }\n\n  /**\n   * @deprecated Use {@link #zdiffstore(byte[], byte[][])}.\n   */\n  @Deprecated\n  public final CommandObject<Long> zdiffStore(byte[] dstkey, byte[]... keys) {\n    return zdiffstore(dstkey, keys);\n  }\n\n  public final CommandObject<Long> zdiffstore(byte[] dstkey, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZDIFFSTORE).key(dstkey)\n        .add(keys.length).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> zinter(ZParams params, String... keys) {\n    return new CommandObject<>(commandArguments(ZINTER).add(keys.length).keys((Object[]) keys)\n        .addParams(params), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zinterWithScores(ZParams params, String... keys) {\n    return new CommandObject<>(commandArguments(ZINTER).add(keys.length).keys((Object[]) keys)\n        .addParams(params).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zinterstore(String dstkey, String... keys) {\n    return new CommandObject<>(commandArguments(ZINTERSTORE).key(dstkey)\n        .add(keys.length).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zinterstore(String dstkey, ZParams params, String... keys) {\n    return new CommandObject<>(commandArguments(ZINTERSTORE).key(dstkey)\n        .add(keys.length).keys((Object[]) keys).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zintercard(String... keys) {\n    return new CommandObject<>(commandArguments(ZINTERCARD).add(keys.length)\n        .keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zintercard(long limit, String... keys) {\n    return new CommandObject<>(commandArguments(ZINTERCARD).add(keys.length)\n        .keys((Object[]) keys).add(LIMIT).add(limit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zinterstore(byte[] dstkey, byte[]... sets) {\n    return new CommandObject<>(commandArguments(ZINTERSTORE).key(dstkey)\n        .add(sets.length).keys((Object[]) sets), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zinterstore(byte[] dstkey, ZParams params, byte[]... sets) {\n    return new CommandObject<>(commandArguments(ZINTERSTORE).key(dstkey)\n        .add(sets.length).keys((Object[]) sets).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zintercard(byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZINTERCARD).add(keys.length)\n        .keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zintercard(long limit, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZINTERCARD).add(keys.length)\n        .keys((Object[]) keys).add(LIMIT).add(limit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<byte[]>> zinter(ZParams params, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZINTER).add(keys.length).keys((Object[]) keys)\n        .addParams(params), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zinterWithScores(ZParams params, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZINTER).add(keys.length).keys((Object[]) keys)\n        .addParams(params).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zunionstore(String dstkey, String... sets) {\n    return new CommandObject<>(commandArguments(ZUNIONSTORE).key(dstkey)\n        .add(sets.length).keys((Object[]) sets), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zunionstore(String dstkey, ZParams params, String... sets) {\n    return new CommandObject<>(commandArguments(ZUNIONSTORE).key(dstkey)\n        .add(sets.length).keys((Object[]) sets).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> zunion(ZParams params, String... keys) {\n    return new CommandObject<>(commandArguments(ZUNION).add(keys.length).keys((Object[]) keys)\n        .addParams(params), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zunionWithScores(ZParams params, String... keys) {\n    return new CommandObject<>(commandArguments(ZUNION).add(keys.length).keys((Object[]) keys)\n        .addParams(params).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<Long> zunionstore(byte[] dstkey, byte[]... sets) {\n    return new CommandObject<>(commandArguments(ZUNIONSTORE).key(dstkey)\n        .add(sets.length).keys((Object[]) sets), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> zunionstore(byte[] dstkey, ZParams params, byte[]... sets) {\n    return new CommandObject<>(commandArguments(ZUNIONSTORE).key(dstkey)\n        .add(sets.length).keys((Object[]) sets).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<byte[]>> zunion(ZParams params, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZUNION).add(keys.length).keys((Object[]) keys)\n        .addParams(params), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> zunionWithScores(ZParams params, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZUNION).add(keys.length).keys((Object[]) keys)\n        .addParams(params).add(WITHSCORES), getTupleListBuilder());\n  }\n\n  public final CommandObject<KeyValue<String, List<Tuple>>> zmpop(SortedSetOption option, String... keys) {\n    return new CommandObject<>(commandArguments(ZMPOP).add(keys.length).keys((Object[]) keys)\n        .add(option), BuilderFactory.KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, List<Tuple>>> zmpop(SortedSetOption option, int count, String... keys) {\n    return new CommandObject<>(commandArguments(ZMPOP).add(keys.length).keys((Object[]) keys)\n        .add(option).add(COUNT).add(count), BuilderFactory.KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, List<Tuple>>> bzmpop(double timeout, SortedSetOption option, String... keys) {\n    return new CommandObject<>(commandArguments(BZMPOP).blocking().add(timeout).add(keys.length)\n        .keys((Object[]) keys).add(option), BuilderFactory.KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<String, List<Tuple>>> bzmpop(double timeout, SortedSetOption option, int count, String... keys) {\n    return new CommandObject<>(commandArguments(BZMPOP).blocking().add(timeout).add(keys.length)\n        .keys((Object[]) keys).add(option).add(COUNT).add(count), BuilderFactory.KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<Tuple>>> zmpop(SortedSetOption option, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZMPOP).add(keys.length).keys((Object[]) keys)\n        .add(option), BuilderFactory.BINARY_KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<Tuple>>> zmpop(SortedSetOption option, int count, byte[]... keys) {\n    return new CommandObject<>(commandArguments(ZMPOP).add(keys.length).keys((Object[]) keys)\n        .add(option).add(COUNT).add(count), BuilderFactory.BINARY_KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<Tuple>>> bzmpop(double timeout, SortedSetOption option, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BZMPOP).blocking().add(timeout).add(keys.length)\n        .keys((Object[]) keys).add(option), BuilderFactory.BINARY_KEYED_TUPLE_LIST);\n  }\n\n  public final CommandObject<KeyValue<byte[], List<Tuple>>> bzmpop(double timeout, SortedSetOption option, int count, byte[]... keys) {\n    return new CommandObject<>(commandArguments(BZMPOP).blocking().add(timeout).add(keys.length)\n        .keys((Object[]) keys).add(option).add(COUNT).add(count), BuilderFactory.BINARY_KEYED_TUPLE_LIST);\n  }\n\n  private Builder<List<Tuple>> getTupleListBuilder() {\n    return protocol == RedisProtocol.RESP3 ? BuilderFactory.TUPLE_LIST_RESP3 : BuilderFactory.TUPLE_LIST;\n  }\n  // Sorted Set commands\n\n  // Geo commands\n  public final CommandObject<Long> geoadd(String key, double longitude, double latitude, String member) {\n    return new CommandObject<>(commandArguments(GEOADD).key(key).add(longitude).add(latitude).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {\n    return new CommandObject<>(addGeoCoordinateFlatMapArgs(commandArguments(GEOADD).key(key), memberCoordinateMap), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap) {\n    return new CommandObject<>(addGeoCoordinateFlatMapArgs(commandArguments(GEOADD).key(key).addParams(params), memberCoordinateMap), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> geodist(String key, String member1, String member2) {\n    return new CommandObject<>(commandArguments(GEODIST).key(key).add(member1).add(member2), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> geodist(String key, String member1, String member2, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEODIST).key(key).add(member1).add(member2).add(unit), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<List<String>> geohash(String key, String... members) {\n    return new CommandObject<>(commandArguments(GEOHASH).key(key).addObjects((Object[]) members), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<GeoCoordinate>> geopos(String key, String... members) {\n    return new CommandObject<>(commandArguments(GEOPOS).key(key).addObjects((Object[]) members),\n        BuilderFactory.GEO_COORDINATE_LIST);\n  }\n\n  public final CommandObject<Long> geoadd(byte[] key, double longitude, double latitude, byte[] member) {\n    return new CommandObject<>(commandArguments(GEOADD).key(key).add(longitude).add(latitude).add(member), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    return new CommandObject<>(addGeoCoordinateFlatMapArgs(commandArguments(GEOADD).key(key), memberCoordinateMap), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geoadd(byte[] key, GeoAddParams params, Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    return new CommandObject<>(addGeoCoordinateFlatMapArgs(commandArguments(GEOADD).key(key).addParams(params), memberCoordinateMap), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Double> geodist(byte[] key, byte[] member1, byte[] member2) {\n    return new CommandObject<>(commandArguments(GEODIST).key(key).add(member1).add(member2), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEODIST).key(key).add(member1).add(member2).add(unit), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<List<byte[]>> geohash(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(GEOHASH).key(key).addObjects((Object[]) members), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<GeoCoordinate>> geopos(byte[] key, byte[]... members) {\n    return new CommandObject<>(commandArguments(GEOPOS).key(key).addObjects((Object[]) members),\n        BuilderFactory.GEO_COORDINATE_LIST);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadius(String key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUS).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadius(String key,\n      double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUS).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUS_RO).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusReadonly(String key,\n      double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUS_RO).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<Long> georadiusStore(String key, double longitude, double latitude,\n      double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return new CommandObject<>(commandArguments(GEORADIUS).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit).addParams(param).addParams(storeParam), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMember(String key, String member, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER).key(key).add(member)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMember(String key,\n      String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER).key(key).add(member)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER_RO).key(key).add(member)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMemberReadonly(String key,\n      String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER_RO).key(key).add(member)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<Long> georadiusByMemberStore(String key, String member,\n      double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER).key(key).add(member)\n        .add(radius).add(unit).addParams(param).addParams(storeParam), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUS).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadius(byte[] key,\n      double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUS).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusReadonly(byte[] key,\n      double longitude, double latitude, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUS_RO).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusReadonly(byte[] key,\n      double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUS_RO).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<Long> georadiusStore(byte[] key, double longitude, double latitude,\n      double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return new CommandObject<>(commandArguments(GEORADIUS).key(key).add(longitude).add(latitude)\n        .add(radius).add(unit).addParams(param).addParams(storeParam), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER).key(key).add(member)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER).key(key).add(member)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER_RO).key(key).add(member)\n        .add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER_RO).key(key).add(member)\n        .add(radius).add(unit).addParams(param), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<Long> georadiusByMemberStore(byte[] key, byte[] member,\n      double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return new CommandObject<>(commandArguments(GEORADIUSBYMEMBER).key(key).add(member)\n        .add(radius).add(unit).addParams(param).addParams(storeParam), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(String key, String member,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key).add(FROMMEMBER).add(member)\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(String key, GeoCoordinate coord,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(String key, String member,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key).add(FROMMEMBER).add(member)\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(String key, GeoCoordinate coord,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(String key, GeoSearchParam params) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key).addParams(params),\n        BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<Long> geosearchStore(String dest, String src, String member,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).add(FROMMEMBER).add(member)\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(String dest, String src, GeoCoordinate coord,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).add(FROMLONLAT).add(coord.getLongitude())\n        .add(coord.getLatitude()).add(BYRADIUS).add(radius).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(String dest, String src, String member,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).add(FROMMEMBER).add(member)\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(String dest, String src, GeoCoordinate coord,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(String dest, String src, GeoSearchParam params) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStoreStoreDist(String dest, String src, GeoSearchParam params) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).addParams(params).add(STOREDIST), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(byte[] key, byte[] member,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key).add(FROMMEMBER).add(member)\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(byte[] key, GeoCoordinate coord,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(byte[] key, byte[] member,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key).add(FROMMEMBER).add(member)\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(byte[] key, GeoCoordinate coord,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<List<GeoRadiusResponse>> geosearch(byte[] key, GeoSearchParam params) {\n    return new CommandObject<>(commandArguments(GEOSEARCH).key(key).addParams(params),\n        BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT);\n  }\n\n  public final CommandObject<Long> geosearchStore(byte[] dest, byte[] src, byte[] member,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).add(FROMMEMBER).add(member)\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord,\n      double radius, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYRADIUS).add(radius).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(byte[] dest, byte[] src, byte[] member,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).add(FROMMEMBER).add(member)\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord,\n      double width, double height, GeoUnit unit) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src)\n        .add(FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude())\n        .add(BYBOX).add(width).add(height).add(unit), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStore(byte[] dest, byte[] src, GeoSearchParam params) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> geosearchStoreStoreDist(byte[] dest, byte[] src, GeoSearchParam params) {\n    return new CommandObject<>(commandArguments(GEOSEARCHSTORE).key(dest).add(src).addParams(params).add(STOREDIST), BuilderFactory.LONG);\n  }\n  // Geo commands\n\n  // Hyper Log Log commands\n  public final CommandObject<Long> pfadd(String key, String... elements) {\n    return new CommandObject<>(commandArguments(PFADD).key(key).addObjects((Object[]) elements), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> pfmerge(String destkey, String... sourcekeys) {\n    return new CommandObject<>(commandArguments(PFMERGE).key(destkey).keys((Object[]) sourcekeys), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> pfadd(byte[] key, byte[]... elements) {\n    return new CommandObject<>(commandArguments(PFADD).key(key).addObjects((Object[]) elements), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> pfmerge(byte[] destkey, byte[]... sourcekeys) {\n    return new CommandObject<>(commandArguments(PFMERGE).key(destkey).keys((Object[]) sourcekeys), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> pfcount(String key) {\n    return new CommandObject<>(commandArguments(PFCOUNT).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pfcount(String... keys) {\n    return new CommandObject<>(commandArguments(PFCOUNT).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pfcount(byte[] key) {\n    return new CommandObject<>(commandArguments(PFCOUNT).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> pfcount(byte[]... keys) {\n    return new CommandObject<>(commandArguments(PFCOUNT).keys((Object[]) keys), BuilderFactory.LONG);\n  }\n  // Hyper Log Log commands\n\n  // Stream commands\n  public final CommandObject<StreamEntryID> xadd(String key, StreamEntryID id, Map<String, String> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(XADD).key(key).add(id == null ? StreamEntryID.NEW_ENTRY : id), hash),\n        BuilderFactory.STREAM_ENTRY_ID);\n  }\n\n  public final CommandObject<StreamEntryID> xadd(String key, XAddParams params, Map<String, String> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(XADD).key(key).addParams(params), hash),\n        BuilderFactory.STREAM_ENTRY_ID);\n  }\n\n  public final CommandObject<Long> xlen(String key) {\n    return new CommandObject<>(commandArguments(XLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<byte[]> xadd(byte[] key, XAddParams params, Map<byte[], byte[]> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(XADD).key(key).addParams(params), hash),\n        BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<Long> xlen(byte[] key) {\n    return new CommandObject<>(commandArguments(XLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrange(String key, StreamEntryID start, StreamEntryID end) {\n    return new CommandObject<>(commandArguments(XRANGE).key(key).add(start == null ? \"-\" : start).add(end == null ? \"+\" : end),\n        BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrange(String key, StreamEntryID start, StreamEntryID end, int count) {\n    return new CommandObject<>(commandArguments(XRANGE).key(key).add(start == null ? \"-\" : start).add(end == null ? \"+\" : end)\n        .add(COUNT).add(count), BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrevrange(String key, StreamEntryID end, StreamEntryID start) {\n    return new CommandObject<>(commandArguments(XREVRANGE).key(key).add(end == null ? \"+\" : end).add(start == null ? \"-\" : start),\n        BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrevrange(String key, StreamEntryID end, StreamEntryID start, int count) {\n    return new CommandObject<>(commandArguments(XREVRANGE).key(key).add(end == null ? \"+\" : end).add(start == null ? \"-\" : start)\n        .add(COUNT).add(count), BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrange(String key, String start, String end) {\n    return new CommandObject<>(commandArguments(XRANGE).key(key).add(start).add(end), BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrange(String key, String start, String end, int count) {\n    return new CommandObject<>(commandArguments(XRANGE).key(key).add(start).add(end).add(COUNT).add(count), BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrevrange(String key, String end, String start) {\n    return new CommandObject<>(commandArguments(XREVRANGE).key(key).add(end).add(start), BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xrevrange(String key, String end, String start, int count) {\n    return new CommandObject<>(commandArguments(XREVRANGE).key(key).add(end).add(start).add(COUNT).add(count), BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<Object>> xrange(byte[] key, byte[] start, byte[] end) {\n    return new CommandObject<>(commandArguments(XRANGE).key(key).add(start == null ? \"-\" : start).add(end == null ? \"+\" : end),\n        BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> xrange(byte[] key, byte[] start, byte[] end, int count) {\n    return new CommandObject<>(commandArguments(XRANGE).key(key).add(start == null ? \"-\" : start).add(end == null ? \"+\" : end)\n        .add(COUNT).add(count), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> xrevrange(byte[] key, byte[] end, byte[] start) {\n    return new CommandObject<>(commandArguments(XREVRANGE).key(key).add(end == null ? \"+\" : end).add(start == null ? \"-\" : start),\n        BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> xrevrange(byte[] key, byte[] end, byte[] start, int count) {\n    return new CommandObject<>(commandArguments(XREVRANGE).key(key).add(end == null ? \"+\" : end).add(start == null ? \"-\" : start)\n        .add(COUNT).add(count), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<Long> xack(String key, String group, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XACK).key(key).add(group).addObjects((Object[]) ids), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xackdel(String key, String group, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(trimMode).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<Long> xack(byte[] key, byte[] group, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XACK).key(key).add(group).addObjects((Object[]) ids), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xackdel(byte[] key, byte[] group, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(trimMode).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<String> xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream) {\n    CommandArguments args = commandArguments(XGROUP).add(CREATE).key(key)\n        .add(groupName).add(id == null ? \"0-0\" : id);\n    if (makeStream) args.add(MKSTREAM);\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> xgroupSetID(String key, String groupName, StreamEntryID id) {\n    return new CommandObject<>(commandArguments(XGROUP).add(SETID)\n        .key(key).add(groupName).add(id), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> xgroupDestroy(String key, String groupName) {\n    return new CommandObject<>(commandArguments(XGROUP).add(DESTROY)\n        .key(key).add(groupName), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Boolean> xgroupCreateConsumer(String key, String groupName, String consumerName) {\n    return new CommandObject<>(commandArguments(XGROUP).add(CREATECONSUMER)\n        .key(key).add(groupName).add(consumerName), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> xgroupDelConsumer(String key, String groupName, String consumerName) {\n    return new CommandObject<>(commandArguments(XGROUP).add(DELCONSUMER)\n        .key(key).add(groupName).add(consumerName), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream) {\n    CommandArguments args = commandArguments(XGROUP).add(CREATE).key(key)\n        .add(groupName).add(id);\n    if (makeStream) args.add(MKSTREAM);\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> xgroupSetID(byte[] key, byte[] groupName, byte[] id) {\n    return new CommandObject<>(commandArguments(XGROUP).add(SETID)\n        .key(key).add(groupName).add(id), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> xgroupDestroy(byte[] key, byte[] groupName) {\n    return new CommandObject<>(commandArguments(XGROUP).add(DESTROY)\n        .key(key).add(groupName), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Boolean> xgroupCreateConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    return new CommandObject<>(commandArguments(XGROUP).add(CREATECONSUMER)\n        .key(key).add(groupName).add(consumerName), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> xgroupDelConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    return new CommandObject<>(commandArguments(XGROUP).add(DELCONSUMER)\n        .key(key).add(groupName).add(consumerName), BuilderFactory.LONG);\n  }\n  public final CommandObject<Long> xdel(String key, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XDEL).key(key).addObjects((Object[]) ids), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xdelex(String key, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XDELEX).key(key).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XDELEX).key(key).add(trimMode).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<Long> xtrim(String key, long maxLen, boolean approximate) {\n    CommandArguments args = commandArguments(XTRIM).key(key).add(MAXLEN);\n    if (approximate) args.add(Protocol.BYTES_TILDE);\n    args.add(maxLen);\n    return new CommandObject<>(args, BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> xtrim(String key, XTrimParams params) {\n    return new CommandObject<>(commandArguments(XTRIM).key(key).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> xdel(byte[] key, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XDEL).key(key).addObjects((Object[]) ids), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xdelex(byte[] key, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XDELEX).key(key).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<List<StreamEntryDeletionResult>> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XDELEX).key(key).add(trimMode).add(\"IDS\").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST);\n  }\n\n  public final CommandObject<Long> xtrim(byte[] key, long maxLen, boolean approximateLength) {\n    CommandArguments args = commandArguments(XTRIM).key(key).add(MAXLEN);\n    if (approximateLength) args.add(Protocol.BYTES_TILDE);\n    args.add(maxLen);\n    return new CommandObject<>(args, BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> xtrim(byte[] key, XTrimParams params) {\n    return new CommandObject<>(commandArguments(XTRIM).key(key).addParams(params), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<StreamPendingSummary> xpending(String key, String groupName) {\n    return new CommandObject<>(commandArguments(XPENDING).key(key).add(groupName),\n        BuilderFactory.STREAM_PENDING_SUMMARY);\n  }\n\n  public final CommandObject<List<StreamPendingEntry>> xpending(String key, String groupName, XPendingParams params) {\n    return new CommandObject<>(commandArguments(XPENDING).key(key).add(groupName)\n        .addParams(params), BuilderFactory.STREAM_PENDING_ENTRY_LIST);\n  }\n\n  public final CommandObject<Object> xpending(byte[] key, byte[] groupName) {\n    return new CommandObject<>(commandArguments(XPENDING).key(key).add(groupName),\n        BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<List<Object>> xpending(byte[] key, byte[] groupName, XPendingParams params) {\n    return new CommandObject<>(commandArguments(XPENDING).key(key).add(groupName)\n        .addParams(params), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<StreamEntry>> xclaim(String key, String group,\n      String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XCLAIM).key(key).add(group)\n        .add(consumerName).add(minIdleTime).addObjects((Object[]) ids).addParams(params),\n        BuilderFactory.STREAM_ENTRY_LIST);\n  }\n\n  public final CommandObject<List<StreamEntryID>> xclaimJustId(String key, String group,\n      String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    return new CommandObject<>(commandArguments(XCLAIM).key(key).add(group)\n        .add(consumerName).add(minIdleTime).addObjects((Object[]) ids).addParams(params)\n        .add(JUSTID), BuilderFactory.STREAM_ENTRY_ID_LIST);\n  }\n\n  public final CommandObject<Map.Entry<StreamEntryID, List<StreamEntry>>> xautoclaim(String key,\n      String group, String consumerName, long minIdleTime, StreamEntryID start,\n      XAutoClaimParams params) {\n    return new CommandObject<>(commandArguments(XAUTOCLAIM).key(key).add(group)\n        .add(consumerName).add(minIdleTime).add(start).addParams(params),\n        BuilderFactory.STREAM_AUTO_CLAIM_RESPONSE);\n  }\n\n  public final CommandObject<Map.Entry<StreamEntryID, List<StreamEntryID>>> xautoclaimJustId(\n      String key, String group, String consumerName, long minIdleTime, StreamEntryID start,\n      XAutoClaimParams params) {\n    return new CommandObject<>(commandArguments(XAUTOCLAIM).key(key).add(group)\n        .add(consumerName).add(minIdleTime).add(start).addParams(params)\n        .add(JUSTID), BuilderFactory.STREAM_AUTO_CLAIM_JUSTID_RESPONSE);\n  }\n\n  public final CommandObject<List<byte[]>> xclaim(byte[] key, byte[] group,\n      byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XCLAIM).key(key).add(group)\n        .add(consumerName).add(minIdleTime).addObjects((Object[]) ids).addParams(params),\n        BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<byte[]>> xclaimJustId(byte[] key, byte[] group,\n      byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) {\n    return new CommandObject<>(commandArguments(XCLAIM).key(key).add(group)\n        .add(consumerName).add(minIdleTime).addObjects((Object[]) ids).addParams(params)\n        .add(JUSTID), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<List<Object>> xautoclaim(byte[] key, byte[] groupName,\n      byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) {\n    return new CommandObject<>(commandArguments(XAUTOCLAIM).key(key).add(groupName)\n        .add(consumerName).add(minIdleTime).add(start).addParams(params),\n        BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> xautoclaimJustId(byte[] key, byte[] groupName,\n      byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) {\n    return new CommandObject<>(commandArguments(XAUTOCLAIM).key(key).add(groupName)\n        .add(consumerName).add(minIdleTime).add(start).addParams(params)\n        .add(JUSTID), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<StreamInfo> xinfoStream(String key) {\n    return new CommandObject<>(commandArguments(XINFO).add(STREAM).key(key), BuilderFactory.STREAM_INFO);\n  }\n\n  public final CommandObject<Object> xinfoStream(byte[] key) {\n    return new CommandObject<>(commandArguments(XINFO).add(STREAM).key(key), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<StreamFullInfo> xinfoStreamFull(String key) {\n    return new CommandObject<>(commandArguments(XINFO).add(STREAM).key(key).add(FULL), BuilderFactory.STREAM_FULL_INFO);\n  }\n\n  public final CommandObject<StreamFullInfo> xinfoStreamFull(String key, int count) {\n    return new CommandObject<>(commandArguments(XINFO).add(STREAM).key(key).add(FULL).add(COUNT).add(count), BuilderFactory.STREAM_FULL_INFO);\n  }\n\n  public final CommandObject<Object> xinfoStreamFull(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(XINFO).add(STREAM).key(key).add(FULL).add(COUNT).add(count), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> xinfoStreamFull(byte[] key) {\n    return new CommandObject<>(commandArguments(XINFO).add(STREAM).key(key).add(FULL), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<List<StreamGroupInfo>> xinfoGroups(String key) {\n    return new CommandObject<>(commandArguments(XINFO).add(GROUPS).key(key), BuilderFactory.STREAM_GROUP_INFO_LIST);\n  }\n\n  public final CommandObject<List<Object>> xinfoGroups(byte[] key) {\n    return new CommandObject<>(commandArguments(XINFO).add(GROUPS).key(key), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  /**\n   * @deprecated Use {@link #xinfoConsumers2(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  public final CommandObject<List<StreamConsumersInfo>> xinfoConsumers(String key, String group) {\n    return new CommandObject<>(commandArguments(XINFO).add(CONSUMERS).key(key).add(group), BuilderFactory.STREAM_CONSUMERS_INFO_LIST);\n  }\n\n  public final CommandObject<List<StreamConsumerInfo>> xinfoConsumers2(String key, String group) {\n    return new CommandObject<>(commandArguments(XINFO).add(CONSUMERS).key(key).add(group), BuilderFactory.STREAM_CONSUMER_INFO_LIST);\n  }\n\n  public final CommandObject<List<Object>> xinfoConsumers(byte[] key, byte[] group) {\n    return new CommandObject<>(commandArguments(XINFO).add(CONSUMERS).key(key).add(group), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Map.Entry<String, List<StreamEntry>>>> xread(\n      XReadParams xReadParams, Map<String, StreamEntryID> streams) {\n    CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n    Set<Map.Entry<String, StreamEntryID>> entrySet = streams.entrySet();\n    entrySet.forEach(entry -> args.key(entry.getKey()));\n    entrySet.forEach(entry -> args.add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_RESPONSE);\n  }\n\n  public final CommandObject<Map<String, List<StreamEntry>>> xreadAsMap(\n      XReadParams xReadParams, Map<String, StreamEntryID> streams) {\n    CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n    Set<Map.Entry<String, StreamEntryID>> entrySet = streams.entrySet();\n    entrySet.forEach(entry -> args.key(entry.getKey()));\n    entrySet.forEach(entry -> args.add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_MAP_RESPONSE);\n  }\n\n  public final CommandObject<List<Map.Entry<String, List<StreamEntry>>>> xreadGroup(\n      String groupName, String consumer, XReadGroupParams xReadGroupParams,\n      Map<String, StreamEntryID> streams) {\n    CommandArguments args = commandArguments(XREADGROUP)\n        .add(GROUP).add(groupName).add(consumer)\n        .addParams(xReadGroupParams).add(STREAMS);\n    Set<Map.Entry<String, StreamEntryID>> entrySet = streams.entrySet();\n    entrySet.forEach(entry -> args.key(entry.getKey()));\n    entrySet.forEach(entry -> args.add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_RESPONSE);\n  }\n\n  public final CommandObject<Map<String, List<StreamEntry>>> xreadGroupAsMap(\n      String groupName, String consumer, XReadGroupParams xReadGroupParams,\n      Map<String, StreamEntryID> streams) {\n    CommandArguments args = commandArguments(XREADGROUP)\n        .add(GROUP).add(groupName).add(consumer)\n        .addParams(xReadGroupParams).add(STREAMS);\n    Set<Map.Entry<String, StreamEntryID>> entrySet = streams.entrySet();\n    entrySet.forEach(entry -> args.key(entry.getKey()));\n    entrySet.forEach(entry -> args.add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_MAP_RESPONSE);\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, replaced by {@link #xreadBinary(XReadParams, Map)} or\n   * {@link #xreadBinaryAsMap(XReadParams, Map)} for type safety and better stream entry parsing.\n   */\n  @Deprecated\n  public final CommandObject<List<Object>> xread(XReadParams xReadParams, Map.Entry<byte[], byte[]>... streams) {\n    CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n    for (Map.Entry<byte[], byte[]> entry : streams) {\n      args.key(entry.getKey());\n    }\n    for (Map.Entry<byte[], byte[]> entry : streams) {\n      args.add(entry.getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadBinary(\n      XReadParams xReadParams, Map.Entry<byte[], StreamEntryID>... streams) {\n    CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.key(entry.getKey());\n    }\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.add(entry.getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<Map<byte[], List<StreamEntryBinary>>> xreadBinaryAsMap(\n      XReadParams xReadParams, Map.Entry<byte[], StreamEntryID>... streams) {\n    CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.key(entry.getKey());\n    }\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.add(entry.getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_MAP_RESPONSE);\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use {@link #xreadGroupBinary(byte[], byte[], XReadGroupParams, Map)} or\n   * {@link #xreadGroupBinaryAsMap(byte[], byte[], XReadGroupParams, Map)} instead.\n   */\n  @Deprecated\n  public final CommandObject<List<Object>> xreadGroup(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map.Entry<byte[], byte[]>... streams) {\n    CommandArguments args = commandArguments(XREADGROUP)\n        .add(GROUP).add(groupName).add(consumer)\n        .addParams(xReadGroupParams).add(STREAMS);\n    for (Map.Entry<byte[], byte[]> entry : streams) {\n      args.key(entry.getKey());\n    }\n    for (Map.Entry<byte[], byte[]> entry : streams) {\n      args.add(entry.getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadGroupBinary(\n      byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams,\n      Map.Entry<byte[], StreamEntryID>... streams) {\n    CommandArguments args = commandArguments(XREADGROUP)\n        .add(GROUP).add(groupName).add(consumer)\n        .addParams(xReadGroupParams).add(STREAMS);\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.key(entry.getKey());\n    }\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.add(entry.getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<Map<byte[], List<StreamEntryBinary>>> xreadGroupBinaryAsMap(\n      byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams,\n      Map.Entry<byte[], StreamEntryID>... streams) {\n    CommandArguments args = commandArguments(XREADGROUP)\n        .add(GROUP).add(groupName).add(consumer)\n        .addParams(xReadGroupParams).add(STREAMS);\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.key(entry.getKey());\n    }\n    for (Map.Entry<byte[], StreamEntryID> entry : streams) {\n      args.add(entry.getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_MAP_RESPONSE);\n  }\n\n  public final CommandObject<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadBinary(\n          XReadParams xReadParams, Map<byte[], StreamEntryID> streams) {\n    CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n    Set<Map.Entry<byte[], StreamEntryID>> entrySet = streams.entrySet();\n    entrySet.forEach(entry -> args.key(entry.getKey()));\n    entrySet.forEach(entry -> args.add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_RESPONSE);\n  }\n\n  public final CommandObject<Map<byte[], List<StreamEntryBinary>>> xreadBinaryAsMap(\n          XReadParams xReadParams, Map<byte[], StreamEntryID> streams) {\n      CommandArguments args = commandArguments(XREAD).addParams(xReadParams).add(STREAMS);\n      Set<Map.Entry<byte[], StreamEntryID>> entrySet = streams.entrySet();\n      entrySet.forEach(entry -> args.key(entry.getKey()));\n      entrySet.forEach(entry -> args.add(entry.getValue()));\n      return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_MAP_RESPONSE);\n  }\n\n  public final CommandObject<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadGroupBinary(\n          byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams,\n          Map<byte[], StreamEntryID> streams) {\n      CommandArguments args = commandArguments(XREADGROUP)\n              .add(GROUP).add(groupName).add(consumer)\n              .addParams(xReadGroupParams).add(STREAMS);\n      Set<Map.Entry<byte[], StreamEntryID>> entrySet = streams.entrySet();\n      entrySet.forEach(entry -> args.key(entry.getKey()));\n      entrySet.forEach(entry -> args.add(entry.getValue()));\n      return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_RESPONSE);\n  }\n\n    public final CommandObject<Map<byte[], List<StreamEntryBinary>>> xreadGroupBinaryAsMap(\n            byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams,\n            Map<byte[], StreamEntryID> streams) {\n        CommandArguments args = commandArguments(XREADGROUP)\n                .add(GROUP).add(groupName).add(consumer)\n                .addParams(xReadGroupParams).add(STREAMS);\n        Set<Map.Entry<byte[], StreamEntryID>> entrySet = streams.entrySet();\n        entrySet.forEach(entry -> args.key(entry.getKey()));\n        entrySet.forEach(entry -> args.add(entry.getValue()));\n        return new CommandObject<>(args, BuilderFactory.STREAM_READ_BINARY_MAP_RESPONSE);\n    }\n\n  public final CommandObject<String> xcfgset(String key, XCfgSetParams params) {\n    return new CommandObject<>(commandArguments(XCFGSET).key(key).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> xcfgset(byte[] key, XCfgSetParams params) {\n    return new CommandObject<>(commandArguments(XCFGSET).key(key).addParams(params), BuilderFactory.BINARY);\n  }\n  // Stream commands\n\n  // Scripting commands\n  public final CommandObject<Object> eval(String script) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(0), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(String script, String sampleKey) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(0).addHashSlotKey(sampleKey), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(String script, int keyCount, String... params) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(keyCount)\n        .addObjects((Object[]) params).addHashSlotKeys(Arrays.copyOf(params, keyCount)),\n        BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(String script, List<String> keys, List<String> args) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> evalReadonly(String script, List<String> keys, List<String> args) {\n    return new CommandObject<>(commandArguments(EVAL_RO).add(script).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(byte[] script) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(0), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(byte[] script, byte[] sampleKey) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(0).addHashSlotKey(sampleKey), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(byte[] script, int keyCount, byte[]... params) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(keyCount)\n        .addObjects((Object[]) params).addHashSlotKeys(Arrays.copyOf(params, keyCount)),\n        BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> eval(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    return new CommandObject<>(commandArguments(EVAL).add(script).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> evalReadonly(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    return new CommandObject<>(commandArguments(EVAL_RO).add(script).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(String sha1) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(0), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(String sha1, String sampleKey) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(0).addHashSlotKey(sampleKey), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(String sha1, int keyCount, String... params) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(keyCount)\n        .addObjects((Object[]) params).addHashSlotKeys(Arrays.copyOf(params, keyCount)),\n        BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(String sha1, List<String> keys, List<String> args) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> evalshaReadonly(String sha1, List<String> keys, List<String> args) {\n    return new CommandObject<>(commandArguments(EVALSHA_RO).add(sha1).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(byte[] sha1) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(0), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(byte[] sha1, byte[] sampleKey) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(0).addHashSlotKey(sampleKey), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(byte[] sha1, int keyCount, byte[]... params) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(keyCount)\n        .addObjects((Object[]) params).addHashSlotKeys(Arrays.copyOf(params, keyCount)),\n        BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> evalsha(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    return new CommandObject<>(commandArguments(EVALSHA).add(sha1).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> evalshaReadonly(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    return new CommandObject<>(commandArguments(EVALSHA_RO).add(sha1).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<List<Boolean>> scriptExists(List<String> sha1s) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(Keyword.EXISTS).addObjects(sha1s), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> scriptExists(String sampleKey, String... sha1s) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(Keyword.EXISTS).addObjects((Object[]) sha1s)\n        .addHashSlotKey(sampleKey), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<String> scriptLoad(String script) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(LOAD).add(script), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> scriptLoad(String script, String sampleKey) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(LOAD).add(script).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  private final CommandObject<String> SCRIPT_FLUSH_COMMAND_OBJECT = new CommandObject<>(commandArguments(SCRIPT).add(FLUSH), BuilderFactory.STRING);\n\n  public final CommandObject<String> scriptFlush() {\n    return SCRIPT_FLUSH_COMMAND_OBJECT;\n  }\n\n  public final CommandObject<String> scriptFlush(String sampleKey) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> scriptFlush(String sampleKey, FlushMode flushMode) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH).add(flushMode).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  private final CommandObject<String> SCRIPT_KILL_COMMAND_OBJECT = new CommandObject<>(commandArguments(SCRIPT).add(KILL), BuilderFactory.STRING);\n\n  public final CommandObject<String> scriptKill() {\n    return SCRIPT_KILL_COMMAND_OBJECT;\n  }\n\n  public final CommandObject<String> scriptKill(String sampleKey) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(KILL).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<Boolean>> scriptExists(byte[] sampleKey, byte[]... sha1s) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(Keyword.EXISTS).addObjects((Object[]) sha1s)\n        .addHashSlotKey(sampleKey), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<byte[]> scriptLoad(byte[] script, byte[] sampleKey) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(LOAD).add(script).addHashSlotKey(sampleKey), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<String> scriptFlush(byte[] sampleKey) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> scriptFlush(byte[] sampleKey, FlushMode flushMode) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH).add(flushMode).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> scriptKill(byte[] sampleKey) {\n    return new CommandObject<>(commandArguments(SCRIPT).add(KILL).addHashSlotKey(sampleKey), BuilderFactory.STRING);\n  }\n\n  private final CommandObject<String> SLOWLOG_RESET_COMMAND_OBJECT\n      = new CommandObject<>(commandArguments(SLOWLOG).add(Keyword.RESET), BuilderFactory.STRING);\n\n  public final CommandObject<String> slowlogReset() {\n    return SLOWLOG_RESET_COMMAND_OBJECT;\n  }\n\n  // Hotkeys commands\n  public CommandObject<String> hotkeysStart(HotkeysParams params) {\n    return new CommandObject<>(commandArguments(HOTKEYS).add(Keyword.START).addParams(params),\n        BuilderFactory.STRING);\n  }\n\n  public CommandObject<String> hotkeysStop() {\n    return new CommandObject<>(commandArguments(HOTKEYS).add(Keyword.STOP), BuilderFactory.STRING);\n  }\n\n  public CommandObject<String> hotkeysReset() {\n    return new CommandObject<>(commandArguments(HOTKEYS).add(Keyword.RESET), BuilderFactory.STRING);\n  }\n\n  public CommandObject<HotkeysInfo> hotkeysGet() {\n    return new CommandObject<>(commandArguments(HOTKEYS).add(Keyword.GET),\n        HotkeysInfo.HOTKEYS_INFO_BUILDER);\n  }\n  // End Hotkeys commands\n\n  public final CommandObject<Object> fcall(String name, List<String> keys, List<String> args) {\n    return new CommandObject<>(commandArguments(FCALL).add(name).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<Object> fcallReadonly(String name, List<String> keys, List<String> args) {\n    return new CommandObject<>(commandArguments(FCALL_RO).add(name).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.AGGRESSIVE_ENCODED_OBJECT);\n  }\n\n  public final CommandObject<String> functionDelete(String libraryName) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(DELETE).add(libraryName), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<LibraryInfo>> functionList() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST), LibraryInfo.LIBRARY_INFO_LIST);\n  }\n\n  public final CommandObject<List<LibraryInfo>> functionList(String libraryNamePattern) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST).add(LIBRARYNAME)\n        .add(libraryNamePattern), LibraryInfo.LIBRARY_INFO_LIST);\n  }\n\n  public final CommandObject<List<LibraryInfo>> functionListWithCode() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST).add(WITHCODE), LibraryInfo.LIBRARY_INFO_LIST);\n  }\n\n  public final CommandObject<List<LibraryInfo>> functionListWithCode(String libraryNamePattern) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST).add(LIBRARYNAME)\n        .add(libraryNamePattern).add(WITHCODE), LibraryInfo.LIBRARY_INFO_LIST);\n  }\n\n  public final CommandObject<String> functionLoad(String functionCode) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LOAD).add(functionCode), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> functionLoadReplace(String functionCode) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LOAD).add(REPLACE).add(functionCode), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<FunctionStats> functionStats() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(STATS), FunctionStats.FUNCTION_STATS_BUILDER);\n  }\n\n  public final CommandObject<Object> functionStatsBinary() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(STATS), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<String> functionFlush() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(FLUSH), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> functionFlush(FlushMode mode) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(FLUSH).add(mode), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> functionKill() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(KILL), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Object> fcall(byte[] name, List<byte[]> keys, List<byte[]> args) {\n    return new CommandObject<>(commandArguments(FCALL).add(name).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<Object> fcallReadonly(byte[] name, List<byte[]> keys, List<byte[]> args) {\n    return new CommandObject<>(commandArguments(FCALL_RO).add(name).add(keys.size())\n        .keys(keys).addObjects(args), BuilderFactory.RAW_OBJECT);\n  }\n\n  public final CommandObject<String> functionDelete(byte[] libraryName) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(DELETE).add(libraryName), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> functionDump() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(Keyword.DUMP), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<Object>> functionListBinary() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> functionList(byte[] libraryNamePattern) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST).add(LIBRARYNAME)\n        .add(libraryNamePattern), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> functionListWithCodeBinary() {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST).add(WITHCODE), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<List<Object>> functionListWithCode(byte[] libraryNamePattern) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LIST).add(LIBRARYNAME).\n        add(libraryNamePattern).add(WITHCODE), BuilderFactory.RAW_OBJECT_LIST);\n  }\n\n  public final CommandObject<String> functionLoad(byte[] functionCode) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LOAD).add(functionCode), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> functionLoadReplace(byte[] functionCode) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(LOAD).add(REPLACE).add(functionCode), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> functionRestore(byte[] serializedValue) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(RESTORE).add(serializedValue),\n        BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> functionRestore(byte[] serializedValue, FunctionRestorePolicy policy) {\n    return new CommandObject<>(commandArguments(FUNCTION).add(RESTORE).add(serializedValue)\n        .add(policy.getRaw()), BuilderFactory.STRING);\n  }\n  // Scripting commands\n\n  // Miscellaneous commands\n  public final CommandObject<Boolean> copy(String srcKey, String dstKey, int dstDB, boolean replace) {\n    CommandArguments args = commandArguments(Command.COPY).key(srcKey).key(dstKey).add(DB).add(dstDB);\n    if (replace) args.add(REPLACE);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> copy(byte[] srcKey, byte[] dstKey, int dstDB, boolean replace) {\n    CommandArguments args = commandArguments(Command.COPY).key(srcKey).key(dstKey).add(DB).add(dstDB);\n    if (replace) args.add(REPLACE);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, String key, int timeout) {\n    return migrate(host, port, key, 0, timeout);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, String key, int destinationDB, int timeout) {\n    return new CommandObject<>(commandArguments(MIGRATE).add(host).add(port).key(key)\n        .add(destinationDB).add(timeout), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, int timeout, MigrateParams params, String... keys) {\n    return migrate(host, port, 0, timeout, params, keys);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, int destinationDB, int timeout,\n      MigrateParams params, String... keys) {\n    return new CommandObject<>(commandArguments(MIGRATE).add(host).add(port).add(new byte[0])\n        .add(destinationDB).add(timeout).addParams(params).add(Keyword.KEYS).keys((Object[]) keys),\n        BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, byte[] key, int timeout) {\n    return migrate(host, port, key, 0, timeout);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, byte[] key, int destinationDB, int timeout) {\n    return new CommandObject<>(commandArguments(MIGRATE).add(host).add(port).key(key)\n        .add(destinationDB).add(timeout), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, int timeout, MigrateParams params, byte[]... keys) {\n    return migrate(host, port, 0, timeout, params, keys);\n  }\n\n  public final CommandObject<String> migrate(String host, int port, int destinationDB, int timeout,\n      MigrateParams params, byte[]... keys) {\n    return new CommandObject<>(commandArguments(MIGRATE).add(host).add(port).add(new byte[0])\n        .add(destinationDB).add(timeout).addParams(params).add(Keyword.KEYS).keys((Object[]) keys),\n        BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> memoryUsage(String key) {\n    return new CommandObject<>(commandArguments(MEMORY).add(USAGE).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> memoryUsage(String key, int samples) {\n    return new CommandObject<>(commandArguments(MEMORY).add(USAGE).key(key).add(SAMPLES).add(samples), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> memoryUsage(byte[] key) {\n    return new CommandObject<>(commandArguments(MEMORY).add(USAGE).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> memoryUsage(byte[] key, int samples) {\n    return new CommandObject<>(commandArguments(MEMORY).add(USAGE).key(key).add(SAMPLES).add(samples), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> objectRefcount(String key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(REFCOUNT).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> objectEncoding(String key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(ENCODING).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> objectIdletime(String key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(IDLETIME).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> objectFreq(String key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(FREQ).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> objectRefcount(byte[] key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(REFCOUNT).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<byte[]> objectEncoding(byte[] key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(ENCODING).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<Long> objectIdletime(byte[] key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(IDLETIME).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> objectFreq(byte[] key) {\n    return new CommandObject<>(commandArguments(OBJECT).add(FREQ).key(key), BuilderFactory.LONG);\n  }\n\n  public CommandObject<Long> waitReplicas(int replicas, long timeout) {\n    return new CommandObject<>(commandArguments(WAIT).add(replicas).add(timeout), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> waitReplicas(String sampleKey, int replicas, long timeout) {\n    return new CommandObject<>(commandArguments(WAIT).add(replicas).add(timeout).addHashSlotKey(sampleKey), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> waitReplicas(byte[] sampleKey, int replicas, long timeout) {\n    return new CommandObject<>(commandArguments(WAIT).add(replicas).add(timeout).addHashSlotKey(sampleKey), BuilderFactory.LONG);\n  }\n\n  public CommandObject<KeyValue<Long, Long>> waitAOF(long numLocal, long numReplicas, long timeout) {\n    return new CommandObject<>(commandArguments(WAITAOF).add(numLocal).add(numReplicas).add(timeout), BuilderFactory.LONG_LONG_PAIR);\n  }\n\n  public CommandObject<KeyValue<Long, Long>> waitAOF(byte[] sampleKey, long numLocal, long numReplicas, long timeout) {\n    return new CommandObject<>(commandArguments(WAITAOF).add(numLocal).add(numReplicas).add(timeout).addHashSlotKey(sampleKey), BuilderFactory.LONG_LONG_PAIR);\n  }\n\n  public CommandObject<KeyValue<Long, Long>> waitAOF(String sampleKey, long numLocal, long numReplicas, long timeout) {\n    return new CommandObject<>(commandArguments(WAITAOF).add(numLocal).add(numReplicas).add(timeout).addHashSlotKey(sampleKey), BuilderFactory.LONG_LONG_PAIR);\n  }\n\n  public final CommandObject<Long> publish(String channel, String message) {\n    return new CommandObject<>(commandArguments(PUBLISH).add(channel).add(message), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> publish(byte[] channel, byte[] message) {\n    return new CommandObject<>(commandArguments(PUBLISH).add(channel).add(message), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> spublish(String channel, String message) {\n    return new CommandObject<>(commandArguments(SPUBLISH).key(channel).add(message), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> spublish(byte[] channel, byte[] message) {\n    return new CommandObject<>(commandArguments(SPUBLISH).key(channel).add(message), BuilderFactory.LONG);\n  }\n  // Miscellaneous commands\n\n  // RediSearch commands\n  public final CommandObject<Long> hsetObject(String key, String field, Object value) {\n    return new CommandObject<>(commandArguments(HSET).key(key).add(field).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> hsetObject(String key, Map<String, Object> hash) {\n    return new CommandObject<>(addFlatMapArgs(commandArguments(HSET).key(key), hash), BuilderFactory.LONG);\n  }\n\n  private boolean isRoundRobinSearchCommand(SearchCommand sc) {\n\n    return !(sc.equals(SearchCommand.SUGGET) || sc.equals(SearchCommand.SUGADD) || sc.equals(\n        SearchCommand.SUGLEN) || sc.equals(SearchCommand.SUGDEL) || sc.equals(\n        SearchCommand.CURSOR));\n  }\n\n  private CommandArguments checkAndRoundRobinSearchCommand(SearchCommand sc, String idx) {\n    CommandArguments ca = commandArguments(sc);\n    if (isRoundRobinSearchCommand(sc)) {\n      ca.add(idx);\n    } else {\n      ca.key(idx);\n    }\n    return ca;\n  }\n\n  private CommandArguments checkAndRoundRobinSearchCommand(SearchCommand sc, String idx1,\n      String idx2) {\n    CommandArguments ca = commandArguments(sc);\n    if (isRoundRobinSearchCommand(sc)) {\n      ca.add(idx1).add(idx2);\n    } else {\n      ca.key(idx1).key(idx2);\n    }\n    return ca;\n  }\n\n  private CommandArguments checkAndRoundRobinSearchCommand(SearchCommand sc, byte[] indexName) {\n    CommandArguments ca = commandArguments(sc);\n    if (isRoundRobinSearchCommand(sc)) {\n      ca.add(indexName);\n    } else {\n      ca.key(indexName);\n    }\n    return ca;\n  }\n\n  private <T> CommandObject<T> directSearchCommand(CommandObject<T> object, String indexName) {\n    object.getArguments().addHashSlotKey(indexName);\n    return object;\n  }\n\n  public final CommandObject<String> ftCreate(String indexName, IndexOptions indexOptions, Schema schema) {\n    CommandArguments args = checkAndRoundRobinSearchCommand(SearchCommand.CREATE, indexName)\n        .addParams(indexOptions).add(SearchKeyword.SCHEMA);\n    schema.fields.forEach(field -> args.addParams(field));\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftCreate(String indexName, FTCreateParams createParams,\n      Iterable<SchemaField> schemaFields) {\n    CommandArguments args = checkAndRoundRobinSearchCommand(SearchCommand.CREATE, indexName)\n        .addParams(createParams).add(SearchKeyword.SCHEMA);\n    schemaFields.forEach(field -> args.addParams(field));\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftAlter(String indexName, Schema schema) {\n    CommandArguments args = checkAndRoundRobinSearchCommand(SearchCommand.ALTER, indexName)\n        .add(SearchKeyword.SCHEMA).add(SearchKeyword.ADD);\n    schema.fields.forEach(field -> args.addParams(field));\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftAlter(String indexName, Iterable<SchemaField> schemaFields) {\n    CommandArguments args = checkAndRoundRobinSearchCommand(SearchCommand.ALTER, indexName)\n        .add(SearchKeyword.SCHEMA).add(SearchKeyword.ADD);\n    schemaFields.forEach(field -> args.addParams(field));\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftAliasAdd(String aliasName, String indexName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.ALIASADD, aliasName, indexName), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftAliasUpdate(String aliasName, String indexName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.ALIASUPDATE, aliasName, indexName), BuilderFactory.STRING);\n  }\n\n   public final CommandObject<String> ftAliasDel(String aliasName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.ALIASDEL, aliasName), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftDropIndex(String indexName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.DROPINDEX, indexName), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> ftDropIndexDD(String indexName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.DROPINDEX, indexName).add(SearchKeyword.DD),\n        BuilderFactory.STRING);\n  }\n\n  public final CommandObject<SearchResult> ftSearch(String indexName, String query) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName).add(query),\n        getSearchResultBuilder(null, () -> new SearchResultBuilder(true, false, true)));\n  }\n\n  public final CommandObject<SearchResult> ftSearch(String indexName, String query, FTSearchParams params) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName)\n        .add(query).addParams(params.dialectOptional(searchDialect.get())),\n        getSearchResultBuilder(params.getReturnFieldDecodeMap(), () -> new SearchResultBuilder(\n            !params.getNoContent(), params.getWithScores(), true, params.getReturnFieldDecodeMap())));\n  }\n\n  public final CommandObject<SearchResult> ftSearch(String indexName, Query query) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName)\n        .addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(null,\n        () -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)));\n  }\n\n  @Deprecated\n  public final CommandObject<SearchResult> ftSearch(byte[] indexName, Query query) {\n    if (protocol == RedisProtocol.RESP3) {\n      throw new UnsupportedOperationException(\"binary ft.search is not implemented with resp3.\");\n    }\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName)\n        .addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(null,\n        () -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), false)));\n  }\n\n  public final CommandObject<String> ftExplain(String indexName, Query query) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.EXPLAIN, indexName)\n        .addParams(query.dialectOptional(searchDialect.get())), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> ftExplainCLI(String indexName, Query query) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.EXPLAINCLI, indexName)\n        .addParams(query.dialectOptional(searchDialect.get())), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<AggregationResult> ftAggregate(String indexName, AggregationBuilder aggr) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.AGGREGATE, indexName)\n        .addParams(aggr.dialectOptional(searchDialect.get())), !aggr.isWithCursor() ? AggregationResult.SEARCH_AGGREGATION_RESULT\n        : AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR);\n  }\n\n  public final CommandObject<AggregationResult> ftCursorRead(String indexName, long cursorId, int count) {\n    return new CommandObject<>(commandArguments(SearchCommand.CURSOR).add(SearchKeyword.READ)\n        .key(indexName).add(cursorId).add(SearchKeyword.COUNT).add(count),\n        AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR);\n  }\n\n  public final CommandObject<String> ftCursorDel(String indexName, long cursorId) {\n    return new CommandObject<>(commandArguments(SearchCommand.CURSOR).add(SearchKeyword.DEL)\n        .key(indexName).add(cursorId), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Map.Entry<AggregationResult, ProfilingInfo>> ftProfileAggregate(\n      String indexName, FTProfileParams profileParams, AggregationBuilder aggr) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)\n        .add(SearchKeyword.AGGREGATE).addParams(profileParams).add(SearchKeyword.QUERY)\n        .addParams(aggr.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>(\n        !aggr.isWithCursor() ? AggregationResult.SEARCH_AGGREGATION_RESULT\n        : AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR));\n  }\n\n  public final CommandObject<Map.Entry<SearchResult, ProfilingInfo>> ftProfileSearch(\n      String indexName, FTProfileParams profileParams, Query query) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)\n        .add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY)\n        .addParams(query.dialectOptional(searchDialect.get())),\n        new SearchProfileResponseBuilder<>(getSearchResultBuilder(null,\n            () -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true))));\n  }\n\n  public final CommandObject<Map.Entry<SearchResult, ProfilingInfo>> ftProfileSearch(\n      String indexName, FTProfileParams profileParams, String query, FTSearchParams searchParams) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)\n        .add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY).add(query)\n        .addParams(searchParams.dialectOptional(searchDialect.get())),\n        new SearchProfileResponseBuilder<>(getSearchResultBuilder(searchParams.getReturnFieldDecodeMap(),\n            () -> new SearchResultBuilder(!searchParams.getNoContent(), searchParams.getWithScores(), true,\n                searchParams.getReturnFieldDecodeMap()))));\n  }\n\n  private Builder<SearchResult> getSearchResultBuilder(\n      Map<String, Boolean> isReturnFieldDecode, Supplier<Builder<SearchResult>> resp2) {\n    if (protocol == RedisProtocol.RESP3) {\n      return isReturnFieldDecode == null ? SearchResult.SEARCH_RESULT_BUILDER\n          : new SearchResult.PerFieldDecoderSearchResultBuilder(isReturnFieldDecode);\n    }\n    return resp2.get();\n  }\n\n  public final CommandObject<String> ftSynUpdate(String indexName, String synonymGroupId, String... terms) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SYNUPDATE, indexName)\n        .add(synonymGroupId).addObjects((Object[]) terms), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Map<String, List<String>>> ftSynDump(String indexName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SYNDUMP, indexName),\n        SearchBuilderFactory.SEARCH_SYNONYM_GROUPS);\n  }\n\n  public final CommandObject<Long> ftDictAdd(String dictionary, String... terms) {\n    return new CommandObject<>(commandArguments(SearchCommand.DICTADD).add(dictionary).addObjects((Object[]) terms),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> ftDictDel(String dictionary, String... terms) {\n    return new CommandObject<>(commandArguments(SearchCommand.DICTDEL).add(dictionary).addObjects((Object[]) terms),\n        BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<String>> ftDictDump(String dictionary) {\n    return new CommandObject<>(commandArguments(SearchCommand.DICTDUMP).add(dictionary), BuilderFactory.STRING_SET);\n  }\n\n  public final CommandObject<Long> ftDictAddBySampleKey(String indexName, String dictionary, String... terms) {\n    return directSearchCommand(ftDictAdd(dictionary, terms), indexName);\n  }\n\n  public final CommandObject<Long> ftDictDelBySampleKey(String indexName, String dictionary, String... terms) {\n    return directSearchCommand(ftDictDel(dictionary, terms), indexName);\n  }\n\n  public final CommandObject<Set<String>> ftDictDumpBySampleKey(String indexName, String dictionary) {\n    return directSearchCommand(ftDictDump(dictionary), indexName);\n  }\n\n  public final CommandObject<Map<String, Map<String, Double>>> ftSpellCheck(String index, String query) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SPELLCHECK, index).add(query),\n        SearchBuilderFactory.SEARCH_SPELLCHECK_RESPONSE);\n  }\n\n  public final CommandObject<Map<String, Map<String, Double>>> ftSpellCheck(String index, String query,\n      FTSpellCheckParams spellCheckParams) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SPELLCHECK, index).add(query)\n        .addParams(spellCheckParams.dialectOptional(searchDialect.get())), SearchBuilderFactory.SEARCH_SPELLCHECK_RESPONSE);\n  }\n\n  public final CommandObject<Map<String, Object>> ftInfo(String indexName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.INFO, indexName),\n        protocol == RedisProtocol.RESP3 ? BuilderFactory.AGGRESSIVE_ENCODED_OBJECT_MAP : BuilderFactory.ENCODED_OBJECT_MAP);\n  }\n\n  public final CommandObject<Set<String>> ftTagVals(String indexName, String fieldName) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.TAGVALS, indexName)\n        .add(fieldName), BuilderFactory.STRING_SET);\n  }\n\n  @Experimental\n  public final CommandObject<HybridResult> ftHybrid(String indexName, FTHybridParams hybridParams) {\n    return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.HYBRID, indexName)\n        .addParams(hybridParams), HybridResult.HYBRID_RESULT_BUILDER);\n  }\n\n  @Deprecated\n  public final CommandObject<Map<String, Object>> ftConfigGet(String option) {\n    return new CommandObject<>(commandArguments(SearchCommand.CONFIG).add(SearchKeyword.GET).add(option),\n        protocol == RedisProtocol.RESP3 ? BuilderFactory.AGGRESSIVE_ENCODED_OBJECT_MAP : BuilderFactory.ENCODED_OBJECT_MAP_FROM_PAIRS);\n  }\n\n  @Deprecated\n  public final CommandObject<Map<String, Object>> ftConfigGet(String indexName, String option) {\n    return directSearchCommand(ftConfigGet(option), indexName);\n  }\n\n  @Deprecated\n  public final CommandObject<String> ftConfigSet(String option, String value) {\n    return new CommandObject<>(commandArguments(SearchCommand.CONFIG).add(SearchKeyword.SET).add(option).add(value), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final CommandObject<String> ftConfigSet(String indexName, String option, String value) {\n    return directSearchCommand(ftConfigSet(option, value), indexName);\n  }\n\n  public final CommandObject<Long> ftSugAdd(String key, String string, double score) {\n    return new CommandObject<>(commandArguments(SearchCommand.SUGADD).key(key).add(string).add(score), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> ftSugAddIncr(String key, String string, double score) {\n    return new CommandObject<>(commandArguments(SearchCommand.SUGADD).key(key).add(string).add(score).add(SearchKeyword.INCR), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<String>> ftSugGet(String key, String prefix) {\n    return new CommandObject<>(commandArguments(SearchCommand.SUGGET).key(key).add(prefix), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> ftSugGet(String key, String prefix, boolean fuzzy, int max) {\n    CommandArguments args = commandArguments(SearchCommand.SUGGET).key(key).add(prefix);\n    if (fuzzy) args.add(SearchKeyword.FUZZY);\n    args.add(SearchKeyword.MAX).add(max);\n    return new CommandObject<>(args, BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> ftSugGetWithScores(String key, String prefix) {\n    return new CommandObject<>(commandArguments(SearchCommand.SUGGET).key(key).add(prefix)\n        .add(SearchKeyword.WITHSCORES), BuilderFactory.TUPLE_LIST);\n  }\n\n  public final CommandObject<List<Tuple>> ftSugGetWithScores(String key, String prefix, boolean fuzzy, int max) {\n    CommandArguments args = commandArguments(SearchCommand.SUGGET).key(key).add(prefix);\n    if (fuzzy) args.add(SearchKeyword.FUZZY);\n    args.add(SearchKeyword.MAX).add(max);\n    args.add(SearchKeyword.WITHSCORES);\n    return new CommandObject<>(args, BuilderFactory.TUPLE_LIST);\n  }\n\n  public final CommandObject<Boolean> ftSugDel(String key, String string) {\n    return new CommandObject<>(commandArguments(SearchCommand.SUGDEL).key(key).add(string), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> ftSugLen(String key) {\n    return new CommandObject<>(commandArguments(SearchCommand.SUGLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Set<String>> ftList() {\n    return new CommandObject<>(commandArguments(SearchCommand._LIST), BuilderFactory.STRING_SET);\n  }\n  // RediSearch commands\n\n  // RedisJSON commands\n  public final CommandObject<String> jsonSet(String key, Path2 path, Object object) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(object), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> jsonSetWithEscape(String key, Path2 path, Object object) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(\n        getJsonObjectMapper().toJson(object)), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final CommandObject<String> jsonSet(String key, Path path, Object pojo) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(\n        getJsonObjectMapper().toJson(pojo)), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final CommandObject<String> jsonSetWithPlainString(String key, Path path, String string) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(string), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> jsonSet(String key, Path2 path, Object object, JsonSetParams params) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(object).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> jsonSetWithEscape(String key, Path2 path, Object object, JsonSetParams params) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(\n        getJsonObjectMapper().toJson(object)).addParams(params), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final CommandObject<String> jsonSet(String key, Path path, Object pojo, JsonSetParams params) {\n    return new CommandObject<>(commandArguments(JsonCommand.SET).key(key).add(path).add(\n        getJsonObjectMapper().toJson(pojo)).addParams(params), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> jsonMerge(String key, Path2 path, Object object) {\n    return new CommandObject<>(commandArguments(JsonCommand.MERGE).key(key).add(path).add(object), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final CommandObject<String> jsonMerge(String key, Path path, Object pojo) {\n    return new CommandObject<>(commandArguments(JsonCommand.MERGE).key(key).add(path).add(\n        getJsonObjectMapper().toJson(pojo)), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Object> jsonGet(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), JSON_GENERIC_OBJECT);\n  }\n\n  @Deprecated\n  public final <T> CommandObject<T> jsonGet(String key, Class<T> clazz) {\n    return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), new JsonObjectBuilder<>(clazz));\n  }\n\n  public final CommandObject<Object> jsonGet(String key, Path2... paths) {\n    return new CommandObject<>(commandArguments(JsonCommand.GET).key(key).addObjects((Object[]) paths), JsonBuilderFactory.JSON_OBJECT);\n  }\n\n  @Deprecated\n  public final CommandObject<Object> jsonGet(String key, Path... paths) {\n    return new CommandObject<>(commandArguments(JsonCommand.GET).key(key).addObjects((Object[]) paths), JSON_GENERIC_OBJECT);\n  }\n\n  @Deprecated\n  public final CommandObject<String> jsonGetAsPlainString(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.GET).key(key).add(path), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final <T> CommandObject<T> jsonGet(String key, Class<T> clazz, Path... paths) {\n    return new CommandObject<>(commandArguments(JsonCommand.GET).key(key).addObjects((Object[]) paths), new JsonObjectBuilder<>(clazz));\n  }\n\n  public final CommandObject<List<JSONArray>> jsonMGet(Path2 path, String... keys) {\n    return new CommandObject<>(commandArguments(JsonCommand.MGET).keys((Object[]) keys).add(path), JsonBuilderFactory.JSON_ARRAY_LIST);\n  }\n\n  @Deprecated\n  public final <T> CommandObject<List<T>> jsonMGet(Path path, Class<T> clazz, String... keys) {\n    return new CommandObject<>(commandArguments(JsonCommand.MGET).keys((Object[]) keys).add(path), new JsonObjectListBuilder<>(clazz));\n  }\n\n  public final CommandObject<Long> jsonDel(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.DEL).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> jsonDel(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.DEL).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonDel(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.DEL).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> jsonClear(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.CLEAR).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> jsonClear(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.CLEAR).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonClear(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.CLEAR).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Boolean>> jsonToggle(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.TOGGLE).key(key).add(path), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<String> jsonToggle(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.TOGGLE).key(key).add(path), BuilderFactory.STRING);\n  }\n\n  @Deprecated\n  public final CommandObject<Class<?>> jsonType(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key), JsonBuilderFactory.JSON_TYPE);\n  }\n\n  public final CommandObject<List<Class<?>>> jsonType(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key).add(path),\n        protocol != RedisProtocol.RESP3 ? JsonBuilderFactory.JSON_TYPE_LIST : JsonBuilderFactory.JSON_TYPE_RESPONSE_RESP3_COMPATIBLE);\n  }\n\n  @Deprecated\n  public final CommandObject<Class<?>> jsonType(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key).add(path), JsonBuilderFactory.JSON_TYPE);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonStrAppend(String key, Object string) {\n    return new CommandObject<>(commandArguments(JsonCommand.STRAPPEND).key(key).add(\n        getJsonObjectMapper().toJson(string)), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonStrAppend(String key, Path2 path, Object string) {\n    return new CommandObject<>(commandArguments(JsonCommand.STRAPPEND).key(key).add(path).add(\n        getJsonObjectMapper().toJson(string)), BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonStrAppend(String key, Path path, Object string) {\n    return new CommandObject<>(commandArguments(JsonCommand.STRAPPEND).key(key).add(path).add(\n        getJsonObjectMapper().toJson(string)), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonStrLen(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.STRLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonStrLen(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.STRLEN).key(key).add(path), BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonStrLen(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.STRLEN).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Object> jsonNumIncrBy(String key, Path2 path, double value) {\n    return new CommandObject<>(commandArguments(JsonCommand.NUMINCRBY).key(key).add(path).add(value),\n        JsonBuilderFactory.JSON_ARRAY_OR_DOUBLE_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Double> jsonNumIncrBy(String key, Path path, double value) {\n    return new CommandObject<>(commandArguments(JsonCommand.NUMINCRBY).key(key).add(path).add(value), BuilderFactory.DOUBLE);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrAppend(String key, String path, JSONObject... objects) {\n    CommandArguments args = commandArguments(JsonCommand.ARRAPPEND).key(key).add(path);\n    for (Object object : objects) {\n      args.add(object);\n    }\n    return new CommandObject<>(args, BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonArrAppend(String key, Path2 path, Object... objects) {\n    CommandArguments args = commandArguments(JsonCommand.ARRAPPEND).key(key).add(path).addObjects(objects);\n    return new CommandObject<>(args, BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> jsonArrAppendWithEscape(String key, Path2 path, Object... objects) {\n    CommandArguments args = commandArguments(JsonCommand.ARRAPPEND).key(key).add(path);\n    for (Object object : objects) {\n      args.add(getJsonObjectMapper().toJson(object));\n    }\n    return new CommandObject<>(args, BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrAppend(String key, Path path, Object... pojos) {\n    CommandArguments args = commandArguments(JsonCommand.ARRAPPEND).key(key).add(path);\n    for (Object pojo : pojos) {\n      args.add(getJsonObjectMapper().toJson(pojo));\n    }\n    return new CommandObject<>(args, BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonArrIndex(String key, Path2 path, Object scalar) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRINDEX).key(key).add(path).add(scalar), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> jsonArrIndexWithEscape(String key, Path2 path, Object scalar) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRINDEX).key(key).add(path).add(\n        getJsonObjectMapper().toJson(scalar)), BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrIndex(String key, Path path, Object scalar) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRINDEX).key(key).add(path).add(\n        getJsonObjectMapper().toJson(scalar)), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonArrInsert(String key, Path2 path, int index, Object... objects) {\n    CommandArguments args = commandArguments(JsonCommand.ARRINSERT).key(key).add(path).add(index).addObjects(objects);\n    return new CommandObject<>(args, BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> jsonArrInsertWithEscape(String key, Path2 path, int index, Object... objects) {\n    CommandArguments args = commandArguments(JsonCommand.ARRINSERT).key(key).add(path).add(index);\n    for (Object object : objects) {\n      args.add(getJsonObjectMapper().toJson(object));\n    }\n    return new CommandObject<>(args, BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrInsert(String key, Path path, int index, Object... pojos) {\n    CommandArguments args = commandArguments(JsonCommand.ARRINSERT).key(key).add(path).add(index);\n    for (Object pojo : pojos) {\n      args.add(getJsonObjectMapper().toJson(pojo));\n    }\n    return new CommandObject<>(args, BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Object> jsonArrPop(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key), new JsonObjectBuilder<>(Object.class));\n  }\n\n  @Deprecated\n  public final <T> CommandObject<T> jsonArrPop(String key, Class<T> clazz) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key), new JsonObjectBuilder<>(clazz));\n  }\n\n  public final CommandObject<List<Object>> jsonArrPop(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key).add(path), new JsonObjectListBuilder<>(Object.class));\n  }\n\n  @Deprecated\n  public final CommandObject<Object> jsonArrPop(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key).add(path), new JsonObjectBuilder<>(Object.class));\n  }\n\n  @Deprecated\n  public final <T> CommandObject<T> jsonArrPop(String key, Class<T> clazz, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key).add(path), new JsonObjectBuilder<>(clazz));\n  }\n\n  public final CommandObject<List<Object>> jsonArrPop(String key, Path2 path, int index) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key).add(path).add(index), new JsonObjectListBuilder<>(Object.class));\n  }\n\n  @Deprecated\n  public final CommandObject<Object> jsonArrPop(String key, Path path, int index) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key).add(path).add(index), new JsonObjectBuilder<>(Object.class));\n  }\n\n  @Deprecated\n  public final <T> CommandObject<T> jsonArrPop(String key, Class<T> clazz, Path path, int index) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRPOP).key(key).add(path).add(index), new JsonObjectBuilder<>(clazz));\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrLen(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRLEN).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonArrLen(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRLEN).key(key).add(path), BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrLen(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRLEN).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonArrTrim(String key, Path2 path, int start, int stop) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRTRIM).key(key).add(path).add(start).add(stop), BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonArrTrim(String key, Path path, int start, int stop) {\n    return new CommandObject<>(commandArguments(JsonCommand.ARRTRIM).key(key).add(path).add(start).add(stop), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonObjLen(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.OBJLEN).key(key), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonObjLen(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.OBJLEN).key(key).add(path), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonObjLen(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.OBJLEN).key(key).add(path), BuilderFactory.LONG_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<List<String>> jsonObjKeys(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.OBJKEYS).key(key), BuilderFactory.STRING_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<List<String>> jsonObjKeys(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.OBJKEYS).key(key).add(path), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<List<String>>> jsonObjKeys(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.OBJKEYS).key(key).add(path), BuilderFactory.STRING_LIST_LIST);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonDebugMemory(String key) {\n    return new CommandObject<>(commandArguments(JsonCommand.DEBUG).add(\"MEMORY\").key(key), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> jsonDebugMemory(String key, Path path) {\n    return new CommandObject<>(commandArguments(JsonCommand.DEBUG).add(\"MEMORY\").key(key).add(path), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> jsonDebugMemory(String key, Path2 path) {\n    return new CommandObject<>(commandArguments(JsonCommand.DEBUG).add(\"MEMORY\").key(key).add(path), BuilderFactory.LONG_LIST);\n  }\n  // RedisJSON commands\n\n  // RedisTimeSeries commands\n  public final CommandObject<String> tsCreate(String key) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.CREATE).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tsCreate(String key, TSCreateParams createParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.CREATE).key(key).addParams(createParams), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> tsDel(String key, long fromTimestamp, long toTimestamp) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.DEL).key(key)\n        .add(fromTimestamp).add(toTimestamp), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<String> tsAlter(String key, TSAlterParams alterParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.ALTER).key(key).addParams(alterParams), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> tsAdd(String key, double value) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(Protocol.BYTES_ASTERISK).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsAdd(String key, long timestamp, double value) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(timestamp).add(value), BuilderFactory.LONG);\n  }\n\n  @Deprecated\n  public final CommandObject<Long> tsAdd(String key, long timestamp, double value, TSCreateParams createParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(timestamp).add(value)\n        .addParams(createParams), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsAdd(String key, long timestamp, double value, TSAddParams addParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(timestamp).add(value)\n        .addParams(addParams), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Long>> tsMAdd(Map.Entry<String, TSElement>... entries) {\n    CommandArguments args = commandArguments(TimeSeriesCommand.MADD);\n    for (Map.Entry<String, TSElement> entry : entries) {\n      args.key(entry.getKey()).add(entry.getValue().getTimestamp()).add(entry.getValue().getValue());\n    }\n    return new CommandObject<>(args, BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<Long> tsIncrBy(String key, double value) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.INCRBY).key(key).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsIncrBy(String key, double value, long timestamp) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.INCRBY).key(key).add(value)\n        .add(TimeSeriesKeyword.TIMESTAMP).add(timestamp), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsIncrBy(String key, double addend, TSIncrByParams incrByParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.INCRBY).key(key).add(addend)\n        .addParams(incrByParams), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsDecrBy(String key, double value) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.DECRBY).key(key).add(value), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsDecrBy(String key, double value, long timestamp) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.DECRBY).key(key).add(value)\n        .add(TimeSeriesKeyword.TIMESTAMP).add(timestamp), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.DECRBY).key(key).add(subtrahend)\n        .addParams(decrByParams), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<TSElement>> tsRange(String key, long fromTimestamp, long toTimestamp) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.RANGE).key(key)\n        .add(fromTimestamp).add(toTimestamp), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST);\n  }\n\n  public final CommandObject<List<TSElement>> tsRange(String key, TSRangeParams rangeParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.RANGE).key(key)\n        .addParams(rangeParams), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST);\n  }\n\n  public final CommandObject<List<TSElement>> tsRevRange(String key, long fromTimestamp, long toTimestamp) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.REVRANGE).key(key)\n        .add(fromTimestamp).add(toTimestamp), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST);\n  }\n\n  public final CommandObject<List<TSElement>> tsRevRange(String key, TSRangeParams rangeParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.REVRANGE).key(key)\n        .addParams(rangeParams), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST);\n  }\n\n  public final CommandObject<Map<String, TSMRangeElements>> tsMRange(long fromTimestamp, long toTimestamp, String... filters) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.MRANGE).add(fromTimestamp)\n        .add(toTimestamp).add(TimeSeriesKeyword.FILTER).addObjects((Object[]) filters),\n        getTimeseriesMultiRangeResponseBuilder());\n  }\n\n  public final CommandObject<Map<String, TSMRangeElements>> tsMRange(TSMRangeParams multiRangeParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.MRANGE)\n        .addParams(multiRangeParams), getTimeseriesMultiRangeResponseBuilder());\n  }\n\n  public final CommandObject<Map<String, TSMRangeElements>> tsMRevRange(long fromTimestamp, long toTimestamp, String... filters) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.MREVRANGE).add(fromTimestamp)\n        .add(toTimestamp).add(TimeSeriesKeyword.FILTER).addObjects((Object[]) filters),\n        getTimeseriesMultiRangeResponseBuilder());\n  }\n\n  public final CommandObject<Map<String, TSMRangeElements>> tsMRevRange(TSMRangeParams multiRangeParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.MREVRANGE).addParams(multiRangeParams),\n        getTimeseriesMultiRangeResponseBuilder());\n  }\n\n  public final CommandObject<TSElement> tsGet(String key) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.GET).key(key), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT);\n  }\n\n  public final CommandObject<TSElement> tsGet(String key, TSGetParams getParams) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.GET).key(key).addParams(getParams), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT);\n  }\n\n  public final CommandObject<Map<String, TSMGetElement>> tsMGet(TSMGetParams multiGetParams, String... filters) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.MGET).addParams(multiGetParams)\n        .add(TimeSeriesKeyword.FILTER).addObjects((Object[]) filters),\n        protocol == RedisProtocol.RESP3 ? TimeSeriesBuilderFactory.TIMESERIES_MGET_RESPONSE_RESP3\n            : TimeSeriesBuilderFactory.TIMESERIES_MGET_RESPONSE);\n  }\n\n  public final CommandObject<String> tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType,\n      long timeBucket) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.CREATERULE).key(sourceKey).key(destKey)\n        .add(TimeSeriesKeyword.AGGREGATION).add(aggregationType).add(timeBucket), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType,\n      long bucketDuration, long alignTimestamp) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.CREATERULE).key(sourceKey).key(destKey)\n        .add(TimeSeriesKeyword.AGGREGATION).add(aggregationType).add(bucketDuration).add(alignTimestamp), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tsDeleteRule(String sourceKey, String destKey) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.DELETERULE).key(sourceKey).key(destKey), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> tsQueryIndex(String... filters) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.QUERYINDEX)\n        .addObjects((Object[]) filters), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<TSInfo> tsInfo(String key) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.INFO).key(key), getTimeseriesInfoBuilder());\n  }\n\n  public final CommandObject<TSInfo> tsInfoDebug(String key) {\n    return new CommandObject<>(commandArguments(TimeSeriesCommand.INFO).key(key).add(TimeSeriesKeyword.DEBUG),\n        getTimeseriesInfoBuilder());\n  }\n\n  private Builder<Map<String, TSMRangeElements>> getTimeseriesMultiRangeResponseBuilder() {\n    return protocol == RedisProtocol.RESP3 ? TimeSeriesBuilderFactory.TIMESERIES_MRANGE_RESPONSE_RESP3\n        : TimeSeriesBuilderFactory.TIMESERIES_MRANGE_RESPONSE;\n  }\n\n  private Builder<TSInfo> getTimeseriesInfoBuilder() {\n    return protocol == RedisProtocol.RESP3 ? TSInfo.TIMESERIES_INFO_RESP3 : TSInfo.TIMESERIES_INFO;\n  }\n  // RedisTimeSeries commands\n\n  // RedisBloom commands\n  public final CommandObject<String> bfReserve(String key, double errorRate, long capacity) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.RESERVE).key(key)\n        .add(errorRate).add(capacity), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> bfReserve(String key, double errorRate, long capacity, BFReserveParams reserveParams) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.RESERVE).key(key)\n        .add(errorRate).add(capacity).addParams(reserveParams), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Boolean> bfAdd(String key, String item) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.ADD).key(key).add(item), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<Boolean>> bfMAdd(String key, String... items) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.MADD).key(key).\n        addObjects((Object[]) items), BuilderFactory.BOOLEAN_WITH_ERROR_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> bfInsert(String key, String... items) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.INSERT).key(key)\n        .add(RedisBloomKeyword.ITEMS).addObjects((Object[]) items), BuilderFactory.BOOLEAN_WITH_ERROR_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> bfInsert(String key, BFInsertParams insertParams, String... items) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.INSERT).key(key).addParams(insertParams)\n        .add(RedisBloomKeyword.ITEMS).addObjects((Object[]) items), BuilderFactory.BOOLEAN_WITH_ERROR_LIST);\n  }\n\n  public final CommandObject<Boolean> bfExists(String key, String item) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.EXISTS).key(key).add(item), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<Boolean>> bfMExists(String key, String... items) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.MEXISTS).key(key).\n        addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<Map.Entry<Long, byte[]>> bfScanDump(String key, long iterator) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.SCANDUMP).key(key).add(iterator), BLOOM_SCANDUMP_RESPONSE);\n  }\n\n  public final CommandObject<String> bfLoadChunk(String key, long iterator, byte[] data) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.LOADCHUNK).key(key).add(iterator).add(data), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Long> bfCard(String key) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.CARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Map<String, Object>> bfInfo(String key) {\n    return new CommandObject<>(commandArguments(BloomFilterCommand.INFO).key(key), BuilderFactory.ENCODED_OBJECT_MAP);\n  }\n\n  public final CommandObject<String> cfReserve(String key, long capacity) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.RESERVE).key(key).add(capacity), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> cfReserve(String key, long capacity, CFReserveParams reserveParams) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.RESERVE).key(key).add(capacity).addParams(reserveParams), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Boolean> cfAdd(String key, String item) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.ADD).key(key).add(item), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> cfAddNx(String key, String item) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.ADDNX).key(key).add(item), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<Boolean>> cfInsert(String key, String... items) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.INSERT).key(key)\n        .add(RedisBloomKeyword.ITEMS).addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> cfInsert(String key, CFInsertParams insertParams, String... items) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.INSERT).key(key)\n        .addParams(insertParams).add(RedisBloomKeyword.ITEMS).addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> cfInsertNx(String key, String... items) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.INSERTNX).key(key)\n        .add(RedisBloomKeyword.ITEMS).addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> cfInsertNx(String key, CFInsertParams insertParams, String... items) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.INSERTNX).key(key)\n        .addParams(insertParams).add(RedisBloomKeyword.ITEMS).addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<Boolean> cfExists(String key, String item) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.EXISTS).key(key).add(item), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<Boolean>> cfMExists(String key, String... items) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.MEXISTS).key(key)\n        .addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<Boolean> cfDel(String key, String item) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.DEL).key(key).add(item), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Long> cfCount(String key, String item) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.COUNT).key(key).add(item), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Map.Entry<Long, byte[]>> cfScanDump(String key, long iterator) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.SCANDUMP).key(key).add(iterator), BLOOM_SCANDUMP_RESPONSE);\n  }\n\n  public final CommandObject<String> cfLoadChunk(String key, long iterator, byte[] data) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.LOADCHUNK).key(key).add(iterator).add(data), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Map<String, Object>> cfInfo(String key) {\n    return new CommandObject<>(commandArguments(CuckooFilterCommand.INFO).key(key), BuilderFactory.ENCODED_OBJECT_MAP);\n  }\n\n  public final CommandObject<String> cmsInitByDim(String key, long width, long depth) {\n    return new CommandObject<>(commandArguments(CountMinSketchCommand.INITBYDIM).key(key).add(width)\n        .add(depth), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> cmsInitByProb(String key, double error, double probability) {\n    return new CommandObject<>(commandArguments(CountMinSketchCommand.INITBYPROB).key(key).add(error)\n        .add(probability), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<Long>> cmsIncrBy(String key, Map<String, Long> itemIncrements) {\n    CommandArguments args = commandArguments(CountMinSketchCommand.INCRBY).key(key);\n    itemIncrements.entrySet().forEach(entry -> args.add(entry.getKey()).add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> cmsQuery(String key, String... items) {\n    return new CommandObject<>(commandArguments(CountMinSketchCommand.QUERY).key(key)\n        .addObjects((Object[]) items), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<String> cmsMerge(String destKey, String... keys) {\n    return new CommandObject<>(commandArguments(CountMinSketchCommand.MERGE).key(destKey)\n        .add(keys.length).keys((Object[]) keys), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> cmsMerge(String destKey, Map<String, Long> keysAndWeights) {\n    CommandArguments args = commandArguments(CountMinSketchCommand.MERGE).key(destKey);\n    args.add(keysAndWeights.size());\n    keysAndWeights.entrySet().forEach(entry -> args.key(entry.getKey()));\n    args.add(RedisBloomKeyword.WEIGHTS);\n    keysAndWeights.entrySet().forEach(entry -> args.add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Map<String, Object>> cmsInfo(String key) {\n    return new CommandObject<>(commandArguments(CountMinSketchCommand.INFO).key(key), BuilderFactory.ENCODED_OBJECT_MAP);\n  }\n\n  public final CommandObject<String> topkReserve(String key, long topk) {\n    return new CommandObject<>(commandArguments(TopKCommand.RESERVE).key(key).add(topk), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> topkReserve(String key, long topk, long width, long depth, double decay) {\n    return new CommandObject<>(commandArguments(TopKCommand.RESERVE).key(key).add(topk)\n        .add(width).add(depth).add(decay), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> topkAdd(String key, String... items) {\n    return new CommandObject<>(commandArguments(TopKCommand.ADD).key(key).addObjects((Object[]) items), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<String>> topkIncrBy(String key, Map<String, Long> itemIncrements) {\n    CommandArguments args = commandArguments(TopKCommand.INCRBY).key(key);\n    itemIncrements.entrySet().forEach(entry -> args.add(entry.getKey()).add(entry.getValue()));\n    return new CommandObject<>(args, BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<List<Boolean>> topkQuery(String key, String... items) {\n    return new CommandObject<>(commandArguments(TopKCommand.QUERY).key(key).addObjects((Object[]) items), BuilderFactory.BOOLEAN_LIST);\n  }\n\n  public final CommandObject<List<String>> topkList(String key) {\n    return new CommandObject<>(commandArguments(TopKCommand.LIST).key(key), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Map<String, Long>> topkListWithCount(String key) {\n    return new CommandObject<>(commandArguments(TopKCommand.LIST).key(key)\n        .add(RedisBloomKeyword.WITHCOUNT), BuilderFactory.STRING_LONG_MAP);\n  }\n\n  public final CommandObject<Map<String, Object>> topkInfo(String key) {\n    return new CommandObject<>(commandArguments(TopKCommand.INFO).key(key), BuilderFactory.ENCODED_OBJECT_MAP);\n  }\n\n  public final CommandObject<String> tdigestCreate(String key) {\n    return new CommandObject<>(commandArguments(TDigestCommand.CREATE).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tdigestCreate(String key, int compression) {\n    return new CommandObject<>(commandArguments(TDigestCommand.CREATE).key(key).add(RedisBloomKeyword.COMPRESSION)\n        .add(compression), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tdigestReset(String key) {\n    return new CommandObject<>(commandArguments(TDigestCommand.RESET).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tdigestMerge(String destinationKey, String... sourceKeys) {\n    return new CommandObject<>(commandArguments(TDigestCommand.MERGE).key(destinationKey)\n        .add(sourceKeys.length).keys((Object[]) sourceKeys), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> tdigestMerge(TDigestMergeParams mergeParams,\n      String destinationKey, String... sourceKeys) {\n    return new CommandObject<>(commandArguments(TDigestCommand.MERGE).key(destinationKey)\n        .add(sourceKeys.length).keys((Object[]) sourceKeys).addParams(mergeParams), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<Map<String, Object>> tdigestInfo(String key) {\n    return new CommandObject<>(commandArguments(TDigestCommand.INFO).key(key), BuilderFactory.ENCODED_OBJECT_MAP);\n  }\n\n  public final CommandObject<String> tdigestAdd(String key, double... values) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.ADD).key(key), values),\n        BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<Double>> tdigestCDF(String key, double... values) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.CDF).key(key), values),\n        BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<List<Double>> tdigestQuantile(String key, double... quantiles) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.QUANTILE).key(key),\n        quantiles), BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<Double> tdigestMin(String key) {\n    return new CommandObject<>(commandArguments(TDigestCommand.MIN).key(key), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> tdigestMax(String key) {\n    return new CommandObject<>(commandArguments(TDigestCommand.MAX).key(key), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<Double> tdigestTrimmedMean(String key, double lowCutQuantile, double highCutQuantile) {\n    return new CommandObject<>(commandArguments(TDigestCommand.TRIMMED_MEAN).key(key).add(lowCutQuantile)\n        .add(highCutQuantile), BuilderFactory.DOUBLE);\n  }\n\n  public final CommandObject<List<Long>> tdigestRank(String key, double... values) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.RANK).key(key),\n        values), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Long>> tdigestRevRank(String key, double... values) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.REVRANK).key(key),\n        values), BuilderFactory.LONG_LIST);\n  }\n\n  public final CommandObject<List<Double>> tdigestByRank(String key, long... ranks) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.BYRANK).key(key),\n        ranks), BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<List<Double>> tdigestByRevRank(String key, long... ranks) {\n    return new CommandObject<>(addFlatArgs(commandArguments(TDigestCommand.BYREVRANK).key(key),\n        ranks), BuilderFactory.DOUBLE_LIST);\n  }\n  // RedisBloom commands\n\n  // Transaction commands\n  public final CommandObject<String> watch(String... keys) {\n    return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<String> watch(byte[]... keys) {\n    return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.STRING);\n  }\n  // Transaction commands\n\n  /**\n   * Get the instance for JsonObjectMapper if not null, otherwise a new instance reference with\n   * default implementation will be created and returned.\n   * <p>This process of checking whether or not\n   * the instance reference exists follows <a\n   * href=\"https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java\"\n   * target=\"_blank\">'double-checked lock optimization'</a> approach to reduce the overhead of\n   * acquiring a lock by testing the lock criteria (the \"lock hint\") before acquiring the lock.</p>\n   * @return the JsonObjectMapper instance reference\n   * @see DefaultGsonObjectMapper\n   */\n  private JsonObjectMapper getJsonObjectMapper() {\n    JsonObjectMapper localRef = this.jsonObjectMapper;\n    if (Objects.isNull(localRef)) {\n      mapperLock.lock();\n\n      try {\n        localRef = this.jsonObjectMapper;\n        if (Objects.isNull(localRef)) {\n          this.jsonObjectMapper = localRef = new DefaultGsonObjectMapper();\n        }\n      } finally {\n        mapperLock.unlock();\n      }\n    }\n    return localRef;\n  }\n\n  public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {\n    this.jsonObjectMapper = jsonObjectMapper;\n  }\n\n  public void setDefaultSearchDialect(int dialect) {\n    if (dialect == 0) throw new IllegalArgumentException(\"DIALECT=0 cannot be set.\");\n    this.searchDialect.set(dialect);\n  }\n\n  private class SearchProfileResponseBuilder<T> extends Builder<Map.Entry<T, ProfilingInfo>> {\n\n    private static final String PROFILE_STR_REDIS7 = \"profile\";\n    private static final String PROFILE_STR_REDIS8 = \"Profile\";\n    private static final String RESULTS_STR_REDIS7 = \"results\";\n    private static final String RESULTS_STR_REDIS8 = \"Results\";\n\n    private final Builder<T> resultsBuilder;\n\n    public SearchProfileResponseBuilder(Builder<T> resultsBuilder) {\n      this.resultsBuilder = resultsBuilder;\n    }\n\n    @Override\n    public Map.Entry<T, ProfilingInfo> build(Object data) {\n      List list = (List) data;\n      if (list == null || list.isEmpty()) return null;\n\n      if (list.get(0) instanceof KeyValue) { // RESP3\n        Object resultsData = null, profileData = null;\n\n        for (KeyValue keyValue : (List<KeyValue>) data) {\n          String keyStr = BuilderFactory.STRING.build(keyValue.getKey());\n          switch (keyStr) {\n            case PROFILE_STR_REDIS7:\n            case PROFILE_STR_REDIS8:\n              profileData = keyValue.getValue();\n              break;\n            case RESULTS_STR_REDIS7:\n              resultsData = data;\n              break;\n            case RESULTS_STR_REDIS8:\n              resultsData = keyValue.getValue();\n              break;\n          }\n        }\n\n        assert resultsData != null : \"Could not detect Results data.\";\n        assert profileData != null : \"Could not detect Profile data.\";\n        return KeyValue.of(resultsBuilder.build(resultsData),\n                ProfilingInfo.PROFILING_INFO_BUILDER.build(profileData));\n      }\n\n      return KeyValue.of(resultsBuilder.build(list.get(0)),\n          ProfilingInfo.PROFILING_INFO_BUILDER.build(list.get(1)));\n    }\n  }\n\n  private class JsonObjectBuilder<T> extends Builder<T> {\n\n    private final Class<T> clazz;\n\n    public JsonObjectBuilder(Class<T> clazz) {\n      this.clazz = clazz;\n    }\n\n    @Override\n    public T build(Object data) {\n      return getJsonObjectMapper().fromJson(BuilderFactory.STRING.build(data), clazz);\n    }\n  }\n\n  /**\n   * {@link JsonObjectBuilder} for {@code Object.class}.\n   */\n  private final Builder<Object> JSON_GENERIC_OBJECT = new JsonObjectBuilder<>(Object.class);\n\n  private class JsonObjectListBuilder<T> extends Builder<List<T>> {\n\n    private final Class<T> clazz;\n\n    public JsonObjectListBuilder(Class<T> clazz) {\n      this.clazz = clazz;\n    }\n\n    @Override\n    public List<T> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n      List<String> list = BuilderFactory.STRING_LIST.build(data);\n      return list.stream().map(s -> getJsonObjectMapper().fromJson(s, clazz)).collect(Collectors.toList());\n    }\n  }\n\n  private static final Builder<Map.Entry<Long, byte[]>> BLOOM_SCANDUMP_RESPONSE = new Builder<Map.Entry<Long, byte[]>>() {\n    @Override\n    public Map.Entry<Long, byte[]> build(Object data) {\n      List<Object> list = (List<Object>) data;\n      return new KeyValue<>(BuilderFactory.LONG.build(list.get(0)), BuilderFactory.BINARY.build(list.get(1)));\n    }\n  };\n\n  private CommandArguments addFlatArgs(CommandArguments args, long... values) {\n    for (long value : values) {\n      args.add(value);\n    }\n    return args;\n  }\n\n  private CommandArguments addFlatArgs(CommandArguments args, double... values) {\n    for (double value : values) {\n      args.add(value);\n    }\n    return args;\n  }\n\n  private CommandArguments addFlatKeyValueArgs(CommandArguments args, String... keyvalues) {\n    for (int i = 0; i < keyvalues.length; i += 2) {\n      args.key(keyvalues[i]).add(keyvalues[i + 1]);\n    }\n    return args;\n  }\n\n  private CommandArguments addFlatKeyValueArgs(CommandArguments args, byte[]... keyvalues) {\n    for (int i = 0; i < keyvalues.length; i += 2) {\n      args.key(keyvalues[i]).add(keyvalues[i + 1]);\n    }\n    return args;\n  }\n\n  private CommandArguments addFlatMapArgs(CommandArguments args, Map<?, ?> map) {\n    for (Map.Entry<? extends Object, ? extends Object> entry : map.entrySet()) {\n      args.add(entry.getKey());\n      args.add(entry.getValue());\n    }\n    return args;\n  }\n\n  private CommandArguments addSortedSetFlatMapArgs(CommandArguments args, Map<?, Double> map) {\n    for (Map.Entry<? extends Object, Double> entry : map.entrySet()) {\n      args.add(entry.getValue());\n      args.add(entry.getKey());\n    }\n    return args;\n  }\n\n  private CommandArguments addGeoCoordinateFlatMapArgs(CommandArguments args, Map<?, GeoCoordinate> map) {\n    for (Map.Entry<? extends Object, GeoCoordinate> entry : map.entrySet()) {\n      GeoCoordinate ord = entry.getValue();\n      args.add(ord.getLongitude());\n      args.add(ord.getLatitude());\n      args.add(entry.getKey());\n    }\n    return args;\n  }\n\n  // Vector Set commands\n  public final CommandObject<Boolean> vadd(String key, float[] vector, String element) {\n\n    return vadd(key, vector, element, null);\n  }\n\n  public final CommandObject<Boolean> vadd(String key, float[] vector, String element, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    addVectors(vector, args);\n    args.add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vaddFP32(String key, byte[] vectorBlob, String element) {\n    return vaddFP32(key, vectorBlob, element, null);\n  }\n\n  public final CommandObject<Boolean> vaddFP32(String key, byte[] vectorBlob, String element, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    args.add(Keyword.FP32).add(vectorBlob).add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vadd(byte[] key, float[] vector, byte[] element) {\n    return vadd(key, vector, element, null);\n  }\n\n  public final CommandObject<Boolean> vadd(byte[] key, float[] vector, byte[] element, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    addVectors(vector, args);\n    args.add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element) {\n    return vaddFP32(key, vectorBlob, element, null);\n  }\n\n  public final CommandObject<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    args.add(Keyword.FP32).add(vectorBlob).add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vadd(String key, float[] vector, String element, int reduceDim, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    args.add(Keyword.REDUCE).add(reduceDim);\n    addVectors(vector, args);\n    args.add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vaddFP32(String key, byte[] vectorBlob, String element, int reduceDim, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    args.add(Keyword.REDUCE).add(reduceDim);\n    args.add(Keyword.FP32).add(vectorBlob).add(element);\n    args.addParams(params);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vadd(byte[] key, float[] vector, byte[] element, int reduceDim, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    args.add(Keyword.REDUCE).add(reduceDim);\n    addVectors(vector, args);\n    args.add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  private static void addOptionalParams(VAddParams params, CommandArguments args) {\n    if (params != null) {\n      args.addParams(params);\n    }\n  }\n\n  private static void addVectors(float[] vector, CommandArguments args) {\n    args.add(Keyword.VALUES).add(vector.length);\n    for (float value : vector) {\n      args.add(value);\n    }\n  }\n\n  public final CommandObject<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, int reduceDim, VAddParams params) {\n    CommandArguments args = commandArguments(Command.VADD).key(key);\n    args.add(Keyword.REDUCE).add(reduceDim);\n    args.add(Keyword.FP32).add(vectorBlob).add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<String>> vsim(String key, float[] vector) {\n\n    return vsim(key, vector, null);\n  }\n\n  public final CommandObject<List<String>> vsim(String key, float[] vector, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    addVectors(vector, args);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.STRING_LIST);\n  }\n\n  private static void addOptionalParams(VSimParams params, CommandArguments args) {\n    if (params != null) {\n      args.addParams(params);\n    }\n  }\n\n  public final CommandObject<Map<String, Double>> vsimWithScores(String key, float[] vector, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    addVectors(vector, args);\n    args.add(Keyword.WITHSCORES);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.STRING_DOUBLE_MAP);\n  }\n\n  public final CommandObject<Map<String, VSimScoreAttribs>> vsimWithScoresAndAttribs(String key, float[] vector, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    addVectors(vector, args);\n    args.add(Keyword.WITHSCORES);\n    args.add(Keyword.WITHATTRIBS);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_MAP);\n  }\n\n  public final CommandObject<List<String>> vsimByElement(String key, String element) {\n    return vsimByElement(key, element, null);\n  }\n\n  public final CommandObject<List<String>> vsimByElement(String key, String element, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    args.add(Keyword.ELE).add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<Map<String, Double>> vsimByElementWithScores(String key, String element, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    args.add(Keyword.ELE).add(element);\n    args.add(Keyword.WITHSCORES);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.STRING_DOUBLE_MAP);\n  }\n\n  public final CommandObject<Map<String, VSimScoreAttribs>> vsimByElementWithScoresAndAttribs(String key, String element, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    args.add(Keyword.ELE).add(element);\n    args.add(Keyword.WITHSCORES);\n    args.add(Keyword.WITHATTRIBS);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_MAP);\n  }\n\n  public final CommandObject<List<byte[]>> vsim(byte[] key, float[] vector) {\n    return vsim(key, vector, null);\n  }\n\n  public final CommandObject<List<byte[]>> vsim(byte[] key, float[] vector, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    addVectors(vector, args);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Map<byte[], Double>> vsimWithScores(byte[] key, float[] vector, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    addVectors(vector, args);\n    args.add(Keyword.WITHSCORES);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BINARY_DOUBLE_MAP);\n  }\n\n  public final CommandObject<Map<byte[], VSimScoreAttribs>> vsimWithScoresAndAttribs(byte[] key, float[] vector, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    addVectors(vector, args);\n    args.add(Keyword.WITHSCORES);\n    args.add(Keyword.WITHATTRIBS);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_BINARY_MAP);\n  }\n\n  public final CommandObject<List<byte[]>> vsimByElement(byte[] key, byte[] element) {\n    return vsimByElement(key, element, null);\n  }\n\n  public final CommandObject<List<byte[]>> vsimByElement(byte[] key, byte[] element, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    args.add(Keyword.ELE).add(element);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<Map<byte[], Double>> vsimByElementWithScores(byte[] key, byte[] element, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    args.add(Keyword.ELE).add(element);\n    args.add(Keyword.WITHSCORES);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.BINARY_DOUBLE_MAP);\n  }\n\n  public final CommandObject<Map<byte[], VSimScoreAttribs>> vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, VSimParams params) {\n    CommandArguments args = commandArguments(Command.VSIM).key(key);\n    args.add(Keyword.ELE).add(element);\n    args.add(Keyword.WITHSCORES);\n    args.add(Keyword.WITHATTRIBS);\n    addOptionalParams(params, args);\n    return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_BINARY_MAP);\n  }\n\n  public final CommandObject<Long> vdim(String key) {\n    return new CommandObject<>(commandArguments(Command.VDIM).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> vdim(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.VDIM).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> vcard(String key) {\n    return new CommandObject<>(commandArguments(Command.VCARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<Long> vcard(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.VCARD).key(key), BuilderFactory.LONG);\n  }\n\n  public final CommandObject<List<Double>> vemb(String key, String element) {\n    return new CommandObject<>(commandArguments(Command.VEMB).key(key).add(element), BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<RawVector> vembRaw(String key, String element) {\n    return new CommandObject<>(commandArguments(Command.VEMB).key(key).add(element).add(Keyword.RAW), BuilderFactory.VEMB_RAW_RESULT);\n  }\n\n  public final CommandObject<List<Double>> vemb(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(Command.VEMB).key(key).add(element), BuilderFactory.DOUBLE_LIST);\n  }\n\n  public final CommandObject<RawVector> vembRaw(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(Command.VEMB).key(key).add(element).add(Keyword.RAW), BuilderFactory.VEMB_RAW_RESULT);\n  }\n\n  public final CommandObject<Boolean> vrem(String key, String element) {\n    return new CommandObject<>(commandArguments(Command.VREM).key(key).add(element), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vrem(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(Command.VREM).key(key).add(element), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<List<List<String>>> vlinks(String key, String element) {\n    return new CommandObject<>(commandArguments(Command.VLINKS).key(key).add(element), BuilderFactory.STRING_LIST_LIST);\n  }\n\n  public final CommandObject<List<Map<String, Double>>> vlinksWithScores(String key, String element) {\n    return new CommandObject<>(commandArguments(Command.VLINKS).key(key).add(element).add(Keyword.WITHSCORES), BuilderFactory.VLINKS_WITH_SCORES_RESULT);\n  }\n\n  public final CommandObject<List<List<byte[]>>> vlinks(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(Command.VLINKS).key(key).add(element), BuilderFactory.BINARY_LIST_LIST);\n  }\n\n  public final CommandObject<List<Map<byte[], Double>>> vlinksWithScores(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(Command.VLINKS).key(key).add(element).add(Keyword.WITHSCORES), BuilderFactory.VLINKS_WITH_SCORES_RESULT_BINARY);\n  }\n\n  public final CommandObject<String> vrandmember(String key) {\n    return new CommandObject<>(commandArguments(Command.VRANDMEMBER).key(key), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<List<String>> vrandmember(String key, int count) {\n    return new CommandObject<>(commandArguments(Command.VRANDMEMBER).key(key).add(count), BuilderFactory.STRING_LIST);\n  }\n\n  public final CommandObject<byte[]> vrandmember(byte[] key) {\n    return new CommandObject<>(commandArguments(Command.VRANDMEMBER).key(key), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<List<byte[]>> vrandmember(byte[] key, int count) {\n    return new CommandObject<>(commandArguments(Command.VRANDMEMBER).key(key).add(count), BuilderFactory.BINARY_LIST);\n  }\n\n  public final CommandObject<String> vgetattr(String key, String element) {\n    return new CommandObject<>(commandArguments(Command.VGETATTR).key(key).add(element), BuilderFactory.STRING);\n  }\n\n  public final CommandObject<byte[]> vgetattr(byte[] key, byte[] element) {\n    return new CommandObject<>(commandArguments(Command.VGETATTR).key(key).add(element), BuilderFactory.BINARY);\n  }\n\n  public final CommandObject<Boolean> vsetattr(String key, String element, String attributes) {\n    return new CommandObject<>(commandArguments(Command.VSETATTR).key(key).add(element).add(attributes), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<Boolean> vsetattr(byte[] key, byte[] element, byte[] attributes) {\n    return new CommandObject<>(commandArguments(Command.VSETATTR).key(key).add(element).add(attributes), BuilderFactory.BOOLEAN);\n  }\n\n  public final CommandObject<VectorInfo> vinfo(String key) {\n    return new CommandObject<>(commandArguments(Command.VINFO).key(key), BuilderFactory.VECTOR_INFO);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Connection.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.util.SafeEncoder.encode;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.net.Socket;\nimport java.net.SocketAddress;\nimport java.net.SocketException;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.args.ClientAttributeOption;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.authentication.AuthXManager;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.exceptions.JedisValidationException;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.util.RedisInputStream;\nimport redis.clients.jedis.util.RedisOutputStream;\n\npublic class Connection implements Closeable {\n\n  public static class Builder {\n    private JedisSocketFactory socketFactory;\n    private JedisClientConfig clientConfig;\n\n    public Builder socketFactory(JedisSocketFactory socketFactory) {\n      this.socketFactory = socketFactory;\n      return this;\n    }\n\n    public Builder clientConfig(JedisClientConfig clientConfig) {\n      this.clientConfig = clientConfig;\n      return this;\n    }\n\n    public JedisSocketFactory getSocketFactory() {\n      return socketFactory;\n    }\n\n    public JedisClientConfig getClientConfig() {\n      return clientConfig;\n    }\n\n    public Connection build() {\n      Connection conn = new Connection(this);\n      conn.initializeFromClientConfig();\n      return conn;\n    }\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private ConnectionPool memberOf;\n  protected RedisProtocol protocol;\n  private final JedisSocketFactory socketFactory;\n  private Socket socket;\n  private RedisOutputStream outputStream;\n  private RedisInputStream inputStream;\n  private int soTimeout = 0;\n  private int infiniteSoTimeout = 0;\n  private boolean broken = false;\n  private boolean strValActive;\n  private String strVal;\n  protected String server;\n  protected String version;\n  private AtomicReference<RedisCredentials> currentCredentials = new AtomicReference<>(null);\n  private AuthXManager authXManager;\n  private JedisClientConfig clientConfig;\n\n  public Connection() {\n    this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);\n  }\n\n  public Connection(final String host, final int port) {\n    this(new HostAndPort(host, port));\n  }\n\n  public Connection(final HostAndPort hostAndPort) {\n    this(new DefaultJedisSocketFactory(hostAndPort));\n  }\n\n  public Connection(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) {\n    this(new DefaultJedisSocketFactory(hostAndPort, clientConfig), clientConfig);\n  }\n\n  public Connection(final JedisSocketFactory socketFactory) {\n    this.socketFactory = socketFactory;\n  }\n\n  public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig) {\n    this.socketFactory = socketFactory;\n    this.clientConfig = clientConfig;\n    initializeFromClientConfig(clientConfig);\n  }\n\n  protected Connection(Builder builder) {\n    this.socketFactory = builder.getSocketFactory();\n    this.clientConfig = builder.getClientConfig();\n  }\n\n  @Override\n  public String toString() {\n    return getClass().getSimpleName() + \"{\" + socketFactory + \"}\";\n  }\n\n  @Experimental\n  public String toIdentityString() {\n    if (strValActive == broken && strVal != null) {\n      return strVal;\n    }\n\n    String className = getClass().getSimpleName();\n    int id = hashCode();\n\n    if (socket == null) {\n      return String.format(\"%s{id: 0x%X}\", className, id);\n    }\n\n    SocketAddress remoteAddr = socket.getRemoteSocketAddress();\n    SocketAddress localAddr = socket.getLocalSocketAddress();\n    if (remoteAddr != null) {\n      strVal = String.format(\"%s{id: 0x%X, L:%s %c R:%s}\", className, id, localAddr,\n        (broken ? '!' : '-'), remoteAddr);\n    } else if (localAddr != null) {\n      strVal = String.format(\"%s{id: 0x%X, L:%s}\", className, id, localAddr);\n    } else {\n      strVal = String.format(\"%s{id: 0x%X}\", className, id);\n    }\n\n    strValActive = broken;\n    return strVal;\n  }\n\n  public final RedisProtocol getRedisProtocol() {\n    return protocol;\n  }\n\n  public final void setHandlingPool(final ConnectionPool pool) {\n    this.memberOf = pool;\n  }\n\n  /**\n   * Returns the host and port of the Redis server this connection is connected to.\n   *\n   * @return the host and port, or null if not available\n   */\n  public final HostAndPort getHostAndPort() {\n    return ((DefaultJedisSocketFactory) socketFactory).getHostAndPort();\n  }\n\n  public int getSoTimeout() {\n    return soTimeout;\n  }\n\n  public void setSoTimeout(int soTimeout) {\n    this.soTimeout = soTimeout;\n    if (this.socket != null) {\n      try {\n        this.socket.setSoTimeout(soTimeout);\n      } catch (SocketException ex) {\n        setBroken();\n        throw new JedisConnectionException(ex);\n      }\n    }\n  }\n\n  public void setTimeoutInfinite() {\n    try {\n      if (!isConnected()) {\n        connect();\n      }\n      socket.setSoTimeout(infiniteSoTimeout);\n    } catch (SocketException ex) {\n      setBroken();\n      throw new JedisConnectionException(ex);\n    }\n  }\n\n  public void rollbackTimeout() {\n    try {\n      socket.setSoTimeout(this.soTimeout);\n    } catch (SocketException ex) {\n      setBroken();\n      throw new JedisConnectionException(ex);\n    }\n  }\n\n  public Object executeCommand(final ProtocolCommand cmd) {\n    return executeCommand(new CommandArguments(cmd));\n  }\n\n  public Object executeCommand(final CommandArguments args) {\n    sendCommand(args);\n    return getOne();\n  }\n\n  public <T> T executeCommand(final CommandObject<T> commandObject) {\n    final CommandArguments args = commandObject.getArguments();\n    sendCommand(args);\n    if (!args.isBlocking()) {\n      return commandObject.getBuilder().build(getOne());\n    } else {\n      try {\n        setTimeoutInfinite();\n        return commandObject.getBuilder().build(getOne());\n      } finally {\n        rollbackTimeout();\n      }\n    }\n  }\n\n  public void sendCommand(final ProtocolCommand cmd) {\n    sendCommand(new CommandArguments(cmd));\n  }\n\n  public void sendCommand(final ProtocolCommand cmd, Rawable keyword) {\n    sendCommand(new CommandArguments(cmd).add(keyword));\n  }\n\n  public void sendCommand(final ProtocolCommand cmd, final String... args) {\n    sendCommand(new CommandArguments(cmd).addObjects((Object[]) args));\n  }\n\n  public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {\n    sendCommand(new CommandArguments(cmd).addObjects((Object[]) args));\n  }\n\n  public void sendCommand(final CommandArguments args) {\n    try {\n      connect();\n      Protocol.sendCommand(outputStream, args);\n    } catch (JedisConnectionException ex) {\n      /*\n       * When client send request which formed by invalid protocol, Redis send back error message\n       * before close connection. We try to read it to provide reason of failure.\n       */\n      try {\n        String errorMessage = Protocol.readErrorLineIfPossible(inputStream);\n        if (errorMessage != null && errorMessage.length() > 0) {\n          ex = new JedisConnectionException(errorMessage, ex.getCause());\n        }\n      } catch (Exception e) {\n        /*\n         * Catch any IOException or JedisConnectionException occurred from InputStream#read and just\n         * ignore. This approach is safe because reading error message is optional and connection\n         * will eventually be closed.\n         */\n      }\n      // Any other exceptions related to connection?\n      setBroken();\n      throw ex;\n    }\n  }\n\n  public void connect() throws JedisConnectionException {\n    if (!isConnected()) {\n      try {\n        socket = socketFactory.createSocket();\n        soTimeout = socket.getSoTimeout(); // ?\n\n        outputStream = new RedisOutputStream(socket.getOutputStream());\n        inputStream = new RedisInputStream(socket.getInputStream());\n\n        broken = false; // unset broken status when connection is (re)initialized\n\n      } catch (JedisConnectionException jce) {\n\n        setBroken();\n        throw jce;\n\n      } catch (IOException ioe) {\n\n        setBroken();\n        throw new JedisConnectionException(\"Failed to create input/output stream\", ioe);\n\n      } finally {\n\n        if (broken) {\n          IOUtils.closeQuietly(socket);\n        }\n      }\n    }\n  }\n\n  @Override\n  public void close() {\n    if (this.memberOf != null) {\n      ConnectionPool pool = this.memberOf;\n      this.memberOf = null;\n      if (isBroken()) {\n        pool.returnBrokenResource(this);\n      } else {\n        pool.returnResource(this);\n      }\n    } else {\n      disconnect();\n    }\n  }\n\n  /**\n   * Close the socket and disconnect the server.\n   */\n  public void disconnect() {\n    if (isConnected()) {\n      try {\n        outputStream.flush();\n        socket.close();\n      } catch (IOException ex) {\n        throw new JedisConnectionException(ex);\n      } finally {\n        IOUtils.closeQuietly(socket);\n        setBroken();\n      }\n    }\n  }\n\n  public void forceDisconnect() throws IOException {\n    // setBroken() must be called first here,\n    // otherwise a concurrent close attempt would call 'returnResource' (instead of\n    // 'returnBrokenResource'),\n    // assuming it's an open/healthy connection whereas this individual socket is already closed.\n    setBroken();\n    IOUtils.closeQuietly(socket);\n  }\n\n  public boolean isConnected() {\n    return socket != null && socket.isBound() && !socket.isClosed() && socket.isConnected()\n        && !socket.isInputShutdown() && !socket.isOutputShutdown();\n  }\n\n  public boolean isBroken() {\n    return broken;\n  }\n\n  public void setBroken() {\n    broken = true;\n  }\n\n  public String getStatusCodeReply() {\n    flush();\n    final byte[] resp = (byte[]) readProtocolWithCheckingBroken();\n    if (null == resp) {\n      return null;\n    } else {\n      return encode(resp);\n    }\n  }\n\n  public String getBulkReply() {\n    final byte[] result = getBinaryBulkReply();\n    if (null != result) {\n      return encode(result);\n    } else {\n      return null;\n    }\n  }\n\n  public byte[] getBinaryBulkReply() {\n    flush();\n    return (byte[]) readProtocolWithCheckingBroken();\n  }\n\n  public Long getIntegerReply() {\n    flush();\n    return (Long) readProtocolWithCheckingBroken();\n  }\n\n  public List<String> getMultiBulkReply() {\n    return BuilderFactory.STRING_LIST.build(getBinaryMultiBulkReply());\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public List<byte[]> getBinaryMultiBulkReply() {\n    flush();\n    return (List<byte[]>) readProtocolWithCheckingBroken();\n  }\n\n  /**\n   * @deprecated Use {@link Connection#getUnflushedObject()}.\n   */\n  @Deprecated\n  @SuppressWarnings(\"unchecked\")\n  public List<Object> getUnflushedObjectMultiBulkReply() {\n    return (List<Object>) readProtocolWithCheckingBroken();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public Object getUnflushedObject() {\n    return readProtocolWithCheckingBroken();\n  }\n\n  public List<Object> getObjectMultiBulkReply() {\n    flush();\n    return (List<Object>) readProtocolWithCheckingBroken();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public List<Long> getIntegerMultiBulkReply() {\n    flush();\n    return (List<Long>) readProtocolWithCheckingBroken();\n  }\n\n  public Object getOne() {\n    flush();\n    return readProtocolWithCheckingBroken();\n  }\n\n  protected void flush() {\n    try {\n      outputStream.flush();\n    } catch (IOException ex) {\n      setBroken();\n      throw new JedisConnectionException(ex);\n    }\n  }\n\n  @Experimental\n  protected Object protocolRead(RedisInputStream is) {\n    return Protocol.read(is);\n  }\n\n  @Experimental\n  protected void protocolReadPushes(RedisInputStream is) {\n  }\n\n  protected Object readProtocolWithCheckingBroken() {\n    if (broken) {\n      throw new JedisConnectionException(\"Attempting to read from a broken connection.\");\n    }\n\n    try {\n      return protocolRead(inputStream);\n    } catch (JedisConnectionException exc) {\n      broken = true;\n      throw exc;\n    }\n  }\n\n  protected void readPushesWithCheckingBroken() {\n    if (broken) {\n      throw new JedisConnectionException(\"Attempting to read from a broken connection.\");\n    }\n\n    try {\n      if (inputStream.available() > 0) {\n        protocolReadPushes(inputStream);\n      }\n    } catch (IOException e) {\n      broken = true;\n      throw new JedisConnectionException(\"Failed to check buffer on connection.\", e);\n    } catch (JedisConnectionException exc) {\n      setBroken();\n      throw exc;\n    }\n  }\n\n  public List<Object> getMany(final int count) {\n    flush();\n    final List<Object> responses = new ArrayList<>(count);\n    for (int i = 0; i < count; i++) {\n      try {\n        responses.add(readProtocolWithCheckingBroken());\n      } catch (JedisDataException e) {\n        responses.add(e);\n      }\n    }\n    return responses;\n  }\n\n  /**\n   * Check if the client name libname, libver, characters are legal\n   * @param info the name\n   * @return Returns true if legal, false throws exception\n   * @throws JedisException if characters illegal\n   */\n  private static boolean validateClientInfo(String info) {\n    for (int i = 0; i < info.length(); i++) {\n      char c = info.charAt(i);\n      if (c < '!' || c > '~') {\n        throw new JedisValidationException(\n            \"client info cannot contain spaces, \" + \"newlines or special characters.\");\n      }\n    }\n    return true;\n  }\n\n  public void initializeFromClientConfig() {\n    this.initializeFromClientConfig(clientConfig);\n  }\n\n  protected void initializeFromClientConfig(final JedisClientConfig config) {\n    try {\n      this.soTimeout = config.getSocketTimeoutMillis();\n      this.infiniteSoTimeout = config.getBlockingSocketTimeoutMillis();\n\n      connect();\n\n      protocol = config.getRedisProtocol();\n\n      Supplier<RedisCredentials> credentialsProvider = config.getCredentialsProvider();\n\n      authXManager = config.getAuthXManager();\n      if (authXManager != null) {\n        credentialsProvider = authXManager;\n      }\n\n      if (credentialsProvider instanceof RedisCredentialsProvider) {\n        final RedisCredentialsProvider redisCredentialsProvider = (RedisCredentialsProvider) credentialsProvider;\n        try {\n          redisCredentialsProvider.prepare();\n          helloAndAuth(protocol, redisCredentialsProvider.get());\n        } finally {\n          redisCredentialsProvider.cleanUp();\n        }\n      } else {\n        helloAndAuth(protocol, credentialsProvider != null ? credentialsProvider.get()\n            : new DefaultRedisCredentials(config.getUser(), config.getPassword()));\n      }\n\n      List<CommandArguments> fireAndForgetMsg = new ArrayList<>();\n\n      String clientName = config.getClientName();\n      if (clientName != null && validateClientInfo(clientName)) {\n        fireAndForgetMsg\n            .add(new CommandArguments(Command.CLIENT).add(Keyword.SETNAME).add(clientName));\n      }\n\n      ClientSetInfoConfig setInfoConfig = config.getClientSetInfoConfig();\n      if (setInfoConfig == null) {\n        setInfoConfig = ClientSetInfoConfig.DEFAULT;\n      }\n\n      if (!setInfoConfig.isDisabled()) {\n        fireAndForgetMsg.add(\n                new CommandArguments(Command.CLIENT).add(Keyword.SETINFO).add(ClientAttributeOption.LIB_NAME.getRaw())\n                        .add(setInfoConfig.getDriverInfo().getFormattedName()));\n\n        String libVersion = JedisMetaInfo.getVersion();\n        if (libVersion != null && validateClientInfo(libVersion)) {\n          fireAndForgetMsg.add(\n                  new CommandArguments(Command.CLIENT).add(Keyword.SETINFO).add(ClientAttributeOption.LIB_VER.getRaw())\n                          .add(libVersion));\n        }\n      }\n\n      // set READONLY flag to ALL connections (including master nodes) when enable read from replica\n      if (config.isReadOnlyForRedisClusterReplicas()) {\n        fireAndForgetMsg.add(new CommandArguments(Command.READONLY));\n      }\n\n      for (CommandArguments arg : fireAndForgetMsg) {\n        sendCommand(arg);\n      }\n      getMany(fireAndForgetMsg.size());\n\n      int dbIndex = config.getDatabase();\n      if (dbIndex > 0) {\n        select(dbIndex);\n      }\n\n    } catch (JedisException je) {\n      try {\n        disconnect();\n      } catch (Exception e) {\n        // the first exception 'je' will be thrown\n      }\n      throw je;\n    }\n  }\n\n  private void helloAndAuth(final RedisProtocol protocol, final RedisCredentials credentials) {\n    Map<String, Object> helloResult = null;\n    if (protocol != null && credentials != null && credentials.getUser() != null) {\n      byte[] rawPass = encodeToBytes(credentials.getPassword());\n      try {\n        helloResult = hello(encode(protocol.version()), Keyword.AUTH.getRaw(),\n          encode(credentials.getUser()), rawPass);\n      } finally {\n        Arrays.fill(rawPass, (byte) 0); // clear sensitive data\n      }\n    } else {\n      authenticate(credentials);\n      helloResult = protocol == null ? null : hello(encode(protocol.version()));\n    }\n    if (helloResult != null) {\n      server = (String) helloResult.get(\"server\");\n      version = (String) helloResult.get(\"version\");\n    }\n\n    // clearing 'char[] credentials.getPassword()' should be\n    // handled in RedisCredentialsProvider.cleanUp()\n  }\n\n  public void setCredentials(RedisCredentials credentials) {\n    currentCredentials.set(credentials);\n  }\n\n  private String authenticate(RedisCredentials credentials) {\n    if (credentials == null || credentials.getPassword() == null) {\n      return null;\n    }\n    byte[] rawPass = encodeToBytes(credentials.getPassword());\n    try {\n      if (credentials.getUser() == null) {\n        sendCommand(Command.AUTH, rawPass);\n      } else {\n        sendCommand(Command.AUTH, encode(credentials.getUser()), rawPass);\n      }\n    } finally {\n      Arrays.fill(rawPass, (byte) 0); // clear sensitive data\n    }\n    return getStatusCodeReply();\n  }\n\n  public String reAuthenticate() {\n    return authenticate(currentCredentials.getAndSet(null));\n  }\n\n  protected Map<String, Object> hello(byte[]... args) {\n    sendCommand(Command.HELLO, args);\n    return BuilderFactory.ENCODED_OBJECT_MAP.build(getOne());\n  }\n\n  protected byte[] encodeToBytes(char[] chars) {\n    // Source: https://stackoverflow.com/a/9670279/4021802\n    ByteBuffer passBuf = Protocol.CHARSET.encode(CharBuffer.wrap(chars));\n    byte[] rawPass = Arrays.copyOfRange(passBuf.array(), passBuf.position(), passBuf.limit());\n    Arrays.fill(passBuf.array(), (byte) 0); // clear sensitive data\n    return rawPass;\n  }\n\n  public String select(final int index) {\n    sendCommand(Command.SELECT, Protocol.toByteArray(index));\n    return getStatusCodeReply();\n  }\n\n  public boolean ping() {\n    sendCommand(Command.PING);\n    String status = getStatusCodeReply();\n    if (!\"PONG\".equals(status)) {\n      throw new JedisException(status);\n    }\n    return true;\n  }\n\n  protected boolean isTokenBasedAuthenticationEnabled() {\n    return authXManager != null;\n  }\n\n  protected AuthXManager getAuthXManager() {\n    return authXManager;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ConnectionFactory.java",
    "content": "package redis.clients.jedis;\n\nimport org.apache.commons.pool2.PooledObject;\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.DefaultPooledObject;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.function.Supplier;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.authentication.AuthXManager;\nimport redis.clients.jedis.authentication.JedisAuthenticationException;\nimport redis.clients.jedis.authentication.AuthXEventListener;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConnection;\nimport redis.clients.jedis.exceptions.JedisException;\n\n/**\n * PoolableObjectFactory custom impl.\n */\npublic class ConnectionFactory implements PooledObjectFactory<Connection> {\n\n  public static class Builder {\n    private JedisClientConfig clientConfig;\n    private Connection.Builder connectionBuilder;\n    private JedisSocketFactory jedisSocketFactory;\n    private Cache cache;\n    private HostAndPort hostAndPort;\n\n    // Fluent API methods (preferred)\n    public Builder clientConfig(JedisClientConfig clientConfig) {\n      this.clientConfig = clientConfig;\n      return this;\n    }\n\n    public Builder connectionBuilder(Connection.Builder connectionBuilder) {\n      this.connectionBuilder = connectionBuilder;\n      return this;\n    }\n\n    public Builder socketFactory(JedisSocketFactory jedisSocketFactory) {\n      this.jedisSocketFactory = jedisSocketFactory;\n      return this;\n    }\n\n    public Builder cache(Cache cache) {\n      this.cache = cache;\n      return this;\n    }\n\n    public Builder hostAndPort(HostAndPort hostAndPort) {\n      this.hostAndPort = hostAndPort;\n      return this;\n    }\n\n    public Connection.Builder getConnectionBuilder() {\n      return connectionBuilder;\n    }\n\n    public JedisSocketFactory getJedisSocketFactory() {\n      return jedisSocketFactory;\n    }\n\n    public JedisClientConfig getClientConfig() {\n      return clientConfig;\n    }\n\n    public Cache getCache() {\n      return cache;\n    }\n\n    public ConnectionFactory build() {\n      withDefaults();\n      return new ConnectionFactory(this);\n    }\n\n    private Builder withDefaults() {\n      if (jedisSocketFactory == null) {\n        this.jedisSocketFactory = createDefaultSocketFactory();\n      }\n      if (connectionBuilder == null) {\n        this.connectionBuilder = createDefaultConnectionBuilder();\n      }\n      return this;\n    }\n\n    private JedisSocketFactory createDefaultSocketFactory() {\n      if (clientConfig == null) {\n        clientConfig = DefaultJedisClientConfig.builder().build();\n      }\n      if (hostAndPort == null) {\n        throw new IllegalStateException(\"HostAndPort is required when no socketFactory is provided\");\n      }\n      return new DefaultJedisSocketFactory(hostAndPort, clientConfig);\n    }\n\n    private Connection.Builder createDefaultConnectionBuilder() {\n      Connection.Builder connBuilder = cache == null ? Connection.builder() : CacheConnection.builder(cache);\n      connBuilder.socketFactory(jedisSocketFactory).clientConfig(clientConfig);\n      return connBuilder;\n    }\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private static final Logger logger = LoggerFactory.getLogger(ConnectionFactory.class);\n\n  private final JedisClientConfig clientConfig;\n  private Supplier<Connection> objectMaker;\n  private Connection.Builder connectionBuilder;\n\n  private AuthXEventListener authXEventListener;\n\n  public ConnectionFactory(final HostAndPort hostAndPort) {\n    this(builder().hostAndPort(hostAndPort).withDefaults());\n  }\n\n  public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) {\n    this(builder().hostAndPort(hostAndPort).clientConfig(clientConfig).withDefaults());\n  }\n\n  @Experimental\n  public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache csCache) {\n    this(builder().hostAndPort(hostAndPort).clientConfig(clientConfig).cache(csCache).withDefaults());\n  }\n\n  public ConnectionFactory(final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) {\n    this(builder().socketFactory(jedisSocketFactory).clientConfig(clientConfig).withDefaults());\n  }\n\n  public ConnectionFactory(Builder builder) {\n    this.clientConfig = builder.getClientConfig();\n    this.connectionBuilder = builder.getConnectionBuilder();\n\n    initAuthXManager();\n  }\n\n  private void initAuthXManager() {\n    AuthXManager authXManager = clientConfig.getAuthXManager();\n    if (authXManager == null) {\n      this.objectMaker = () -> build();\n      this.authXEventListener = AuthXEventListener.NOOP_LISTENER;\n    } else {\n      this.objectMaker = () -> (Connection) authXManager.addConnection(build());\n      this.authXEventListener = authXManager.getListener();\n      authXManager.start();\n    }\n  }\n\n  private Connection build() {\n    return connectionBuilder.build();\n  }\n\n  @Override\n  public void activateObject(PooledObject<Connection> pooledConnection) throws Exception {\n    // what to do ??\n  }\n\n  @Override\n  public void destroyObject(PooledObject<Connection> pooledConnection) throws Exception {\n    final Connection jedis = pooledConnection.getObject();\n    if (jedis.isConnected()) {\n      try {\n        jedis.close();\n      } catch (RuntimeException e) {\n        logger.debug(\"Error while close\", e);\n      }\n    }\n  }\n\n  @Override\n  public PooledObject<Connection> makeObject() throws Exception {\n    try {\n      Connection jedis = objectMaker.get();\n      return new DefaultPooledObject<>(jedis);\n    } catch (JedisException je) {\n      logger.debug(\"Error while makeObject\", je);\n      throw je;\n    }\n  }\n\n  @Override\n  public void passivateObject(PooledObject<Connection> pooledConnection) throws Exception {\n    // TODO maybe should select db 0? Not sure right now.\n    Connection jedis = pooledConnection.getObject();\n    reAuthenticate(jedis);\n  }\n\n  @Override\n  public boolean validateObject(PooledObject<Connection> pooledConnection) {\n    final Connection jedis = pooledConnection.getObject();\n    try {\n      // check HostAndPort ??\n      if (!jedis.isConnected()) {\n        return false;\n      }\n      reAuthenticate(jedis);\n      return jedis.ping();\n    } catch (final Exception e) {\n      logger.warn(\"Error while validating pooled Connection object.\", e);\n      return false;\n    }\n  }\n\n  private void reAuthenticate(Connection jedis) throws Exception {\n    try {\n      String result = jedis.reAuthenticate();\n      if (result != null && !result.equals(\"OK\")) {\n        String msg = \"Re-authentication failed with server response: \" + result;\n        Exception failedAuth = new JedisAuthenticationException(msg);\n        logger.error(failedAuth.getMessage(), failedAuth);\n        authXEventListener.onConnectionAuthenticationError(failedAuth);\n        return;\n      }\n    } catch (Exception e) {\n      logger.error(\"Error while re-authenticating connection\", e);\n      authXEventListener.onConnectionAuthenticationError(e);\n      throw e;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ConnectionPool.java",
    "content": "package redis.clients.jedis;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.authentication.core.Token;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.authentication.AuthXManager;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.Pool;\n\npublic class ConnectionPool extends Pool<Connection> {\n\n  private AuthXManager authXManager;\n\n  // Primary constructors using factory\n  public ConnectionPool(PooledObjectFactory<Connection> factory) {\n    super(factory);\n  }\n\n  public ConnectionPool(PooledObjectFactory<Connection> factory,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    super(factory, poolConfig);\n  }\n\n  // Convenience constructors\n  public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig) {\n    this(new ConnectionFactory(hostAndPort, clientConfig));\n    attachAuthenticationListener(clientConfig.getAuthXManager());\n  }\n\n  public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ConnectionFactory(hostAndPort, clientConfig), poolConfig);\n    attachAuthenticationListener(clientConfig.getAuthXManager());\n  }\n\n  @Experimental\n  public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig,\n      Cache clientSideCache) {\n    this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache));\n    attachAuthenticationListener(clientConfig.getAuthXManager());\n  }\n\n  @Experimental\n  public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig,\n      Cache clientSideCache, GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache), poolConfig);\n    attachAuthenticationListener(clientConfig.getAuthXManager());\n  }\n\n  @Override\n  public Connection getResource() {\n    Connection conn = super.getResource();\n    conn.setHandlingPool(this);\n    return conn;\n  }\n\n  @Override\n  public void close() {\n    try {\n      if (authXManager != null) {\n        authXManager.stop();\n      }\n    } finally {\n      super.close();\n    }\n  }\n\n  protected void attachAuthenticationListener(AuthXManager authXManager) {\n    this.authXManager = authXManager;\n    if (authXManager != null) {\n      authXManager.addPostAuthenticationHook(this::postAuthentication);\n    }\n  }\n\n  protected void detachAuthenticationListener() {\n    if (authXManager != null) {\n      authXManager.removePostAuthenticationHook(this::postAuthentication);\n    }\n  }\n\n  private void postAuthentication(Token token) {\n    try {\n      // this is to trigger validations on each connection via ConnectionFactory\n      evict();\n    } catch (Exception e) {\n      throw new JedisException(\"Failed to evict connections from pool\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ConnectionPoolConfig.java",
    "content": "package redis.clients.jedis;\n\nimport java.time.Duration;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\npublic class ConnectionPoolConfig extends GenericObjectPoolConfig<Connection> {\n\n  public ConnectionPoolConfig() {\n    // defaults to make your life with connection pool easier :)\n    setTestWhileIdle(true);\n    setMinEvictableIdleTime(Duration.ofMillis(60000));\n    setTimeBetweenEvictionRuns(Duration.ofMillis(30000));\n    setNumTestsPerEvictionRun(-1);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/DefaultJedisClientConfig.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\nimport java.util.function.Supplier;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport redis.clients.jedis.authentication.AuthXManager;\nimport redis.clients.jedis.util.JedisAsserts;\nimport redis.clients.jedis.util.JedisURIHelper;\n\npublic final class DefaultJedisClientConfig implements JedisClientConfig {\n\n  private final RedisProtocol redisProtocol;\n\n  private final int connectionTimeoutMillis;\n  private final int socketTimeoutMillis;\n  private final int blockingSocketTimeoutMillis;\n\n  private volatile Supplier<RedisCredentials> credentialsProvider;\n  private final int database;\n  private final String clientName;\n\n  private final boolean ssl;\n  private final SSLSocketFactory sslSocketFactory;\n  private final SSLParameters sslParameters;\n  private final SslOptions sslOptions;\n  private final HostnameVerifier hostnameVerifier;\n\n  private final HostAndPortMapper hostAndPortMapper;\n\n  private final ClientSetInfoConfig clientSetInfoConfig;\n\n  private final boolean readOnlyForRedisClusterReplicas;\n\n  private final AuthXManager authXManager;\n\n  private DefaultJedisClientConfig(DefaultJedisClientConfig.Builder builder) {\n    this.redisProtocol = builder.redisProtocol;\n    this.connectionTimeoutMillis = builder.connectionTimeoutMillis;\n    this.socketTimeoutMillis = builder.socketTimeoutMillis;\n    this.blockingSocketTimeoutMillis = builder.blockingSocketTimeoutMillis;\n    this.credentialsProvider = builder.credentialsProvider;\n    this.database = builder.database;\n    this.clientName = builder.clientName;\n    this.ssl = builder.ssl;\n    this.sslSocketFactory = builder.sslSocketFactory;\n    this.sslParameters = builder.sslParameters;\n    this.sslOptions = builder.sslOptions;\n    this.hostnameVerifier = builder.hostnameVerifier;\n    this.hostAndPortMapper = builder.hostAndPortMapper;\n    this.clientSetInfoConfig = builder.clientSetInfoConfig;\n    this.readOnlyForRedisClusterReplicas = builder.readOnlyForRedisClusterReplicas;\n    this.authXManager = builder.authXManager;\n  }\n\n  @Override\n  public RedisProtocol getRedisProtocol() {\n    return redisProtocol;\n  }\n\n  @Override\n  public int getConnectionTimeoutMillis() {\n    return connectionTimeoutMillis;\n  }\n\n  @Override\n  public int getSocketTimeoutMillis() {\n    return socketTimeoutMillis;\n  }\n\n  @Override\n  public int getBlockingSocketTimeoutMillis() {\n    return blockingSocketTimeoutMillis;\n  }\n\n  @Override\n  public String getUser() {\n    return credentialsProvider.get().getUser();\n  }\n\n  @Override\n  public String getPassword() {\n    char[] password = credentialsProvider.get().getPassword();\n    return password == null ? null : new String(password);\n  }\n\n  @Override\n  public Supplier<RedisCredentials> getCredentialsProvider() {\n    return credentialsProvider;\n  }\n\n  @Override\n  public AuthXManager getAuthXManager() {\n    return authXManager;\n  }\n\n  @Override\n  public int getDatabase() {\n    return database;\n  }\n\n  @Override\n  public String getClientName() {\n    return clientName;\n  }\n\n  @Override\n  public boolean isSsl() {\n    return ssl;\n  }\n\n  @Override\n  public SSLSocketFactory getSslSocketFactory() {\n    return sslSocketFactory;\n  }\n\n  @Override\n  public SSLParameters getSslParameters() {\n    return sslParameters;\n  }\n\n  @Override\n  public SslOptions getSslOptions() {\n    return sslOptions;\n  }\n\n  @Override\n  public HostnameVerifier getHostnameVerifier() {\n    return hostnameVerifier;\n  }\n\n  @Override\n  public HostAndPortMapper getHostAndPortMapper() {\n    return hostAndPortMapper;\n  }\n\n  @Override\n  public ClientSetInfoConfig getClientSetInfoConfig() {\n    return clientSetInfoConfig;\n  }\n\n  @Override\n  public boolean isReadOnlyForRedisClusterReplicas() {\n    return readOnlyForRedisClusterReplicas;\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Creates a new Builder pre-initialized with settings from the provided Redis URI.\n   * <p>\n   * The URI format is:\n   * {@code redis[s]://[username:password@]host:port[/database][?protocol=version]}\n   * </p>\n   * <p>\n   * Settings extracted from URI:\n   * <ul>\n   * <li>Credentials (username/password) if present in URI</li>\n   * <li>Database index if specified in path</li>\n   * <li>SSL enabled if scheme is \"rediss\"</li>\n   * <li>Protocol version if specified in query parameters</li>\n   * </ul>\n   * @param redisUri the Redis URI to extract settings from\n   * @return a new Builder pre-initialized from the URI\n   */\n  public static Builder builder(URI redisUri) {\n    JedisAsserts.notNull(redisUri, \"Redis URI must not be null\");\n    JedisAsserts.isTrue(JedisURIHelper.isValid(redisUri), \"Invalid Redis URI\");\n\n    Builder builder = new Builder();\n\n    // Extract and apply credentials if present\n    String uriUser = JedisURIHelper.getUser(redisUri);\n    String uriPassword = JedisURIHelper.getPassword(redisUri);\n\n    if (uriUser != null || uriPassword != null) {\n      builder.credentials(new DefaultRedisCredentials(uriUser, uriPassword));\n    }\n\n    if (JedisURIHelper.hasDbIndex(redisUri)) {\n      builder.database(JedisURIHelper.getDBIndex(redisUri));\n    }\n\n    // Apply protocol if specified\n    RedisProtocol uriProtocol = JedisURIHelper.getRedisProtocol(redisUri);\n    if (uriProtocol != null) {\n      builder.protocol(uriProtocol);\n    }\n\n    if (JedisURIHelper.isRedisSSLScheme(redisUri)) {\n      builder.ssl(true);\n    } else if (JedisURIHelper.isRedisScheme(redisUri)) {\n      builder.ssl(false);\n    }\n\n    return builder;\n  }\n\n  public static class Builder {\n\n    private RedisProtocol redisProtocol = null;\n\n    private int connectionTimeoutMillis = Protocol.DEFAULT_TIMEOUT;\n    private int socketTimeoutMillis = Protocol.DEFAULT_TIMEOUT;\n    private int blockingSocketTimeoutMillis = 0;\n\n    private String user = null;\n    private String password = null;\n    private Supplier<RedisCredentials> credentialsProvider;\n    private int database = Protocol.DEFAULT_DATABASE;\n    private String clientName = null;\n\n    private boolean ssl = false;\n    private SSLSocketFactory sslSocketFactory = null;\n    private SSLParameters sslParameters = null;\n    private SslOptions sslOptions = null;\n    private HostnameVerifier hostnameVerifier = null;\n\n    private HostAndPortMapper hostAndPortMapper = null;\n\n    private ClientSetInfoConfig clientSetInfoConfig = ClientSetInfoConfig.DEFAULT;\n\n    private boolean readOnlyForRedisClusterReplicas = false;\n\n    private AuthXManager authXManager = null;\n\n    private Builder() {\n    }\n\n    public DefaultJedisClientConfig build() {\n      if (credentialsProvider == null) {\n        credentialsProvider = new DefaultRedisCredentialsProvider(\n            new DefaultRedisCredentials(user, password));\n      }\n\n      return new DefaultJedisClientConfig(this);\n    }\n\n    /**\n     * Shortcut to\n     * {@link redis.clients.jedis.DefaultJedisClientConfig.Builder#protocol(RedisProtocol)} with\n     * {@link RedisProtocol#RESP3}.\n     * @return this\n     */\n    public Builder resp3() {\n      return protocol(RedisProtocol.RESP3);\n    }\n\n    public Builder protocol(RedisProtocol protocol) {\n      this.redisProtocol = protocol;\n      return this;\n    }\n\n    public Builder timeoutMillis(int timeoutMillis) {\n      this.connectionTimeoutMillis = timeoutMillis;\n      this.socketTimeoutMillis = timeoutMillis;\n      return this;\n    }\n\n    public Builder connectionTimeoutMillis(int connectionTimeoutMillis) {\n      this.connectionTimeoutMillis = connectionTimeoutMillis;\n      return this;\n    }\n\n    public Builder socketTimeoutMillis(int socketTimeoutMillis) {\n      this.socketTimeoutMillis = socketTimeoutMillis;\n      return this;\n    }\n\n    public Builder blockingSocketTimeoutMillis(int blockingSocketTimeoutMillis) {\n      this.blockingSocketTimeoutMillis = blockingSocketTimeoutMillis;\n      return this;\n    }\n\n    public Builder user(String user) {\n      this.user = user;\n      return this;\n    }\n\n    public Builder password(String password) {\n      this.password = password;\n      return this;\n    }\n\n    public Builder credentials(RedisCredentials credentials) {\n      this.credentialsProvider = new DefaultRedisCredentialsProvider(credentials);\n      return this;\n    }\n\n    public Builder credentialsProvider(Supplier<RedisCredentials> credentials) {\n      this.credentialsProvider = credentials;\n      return this;\n    }\n\n    public Builder database(int database) {\n      this.database = database;\n      return this;\n    }\n\n    public Builder clientName(String clientName) {\n      this.clientName = clientName;\n      return this;\n    }\n\n    public Builder ssl(boolean ssl) {\n      this.ssl = ssl;\n      return this;\n    }\n\n    public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) {\n      this.sslSocketFactory = sslSocketFactory;\n      return this;\n    }\n\n    public Builder sslParameters(SSLParameters sslParameters) {\n      this.sslParameters = sslParameters;\n      return this;\n    }\n\n    public Builder sslOptions(SslOptions sslOptions) {\n      this.sslOptions = sslOptions;\n      return this;\n    }\n\n    public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) {\n      this.hostnameVerifier = hostnameVerifier;\n      return this;\n    }\n\n    public Builder hostAndPortMapper(HostAndPortMapper hostAndPortMapper) {\n      this.hostAndPortMapper = hostAndPortMapper;\n      return this;\n    }\n\n    public Builder clientSetInfoConfig(ClientSetInfoConfig setInfoConfig) {\n      this.clientSetInfoConfig = setInfoConfig;\n      return this;\n    }\n\n    public Builder readOnlyForRedisClusterReplicas() {\n      this.readOnlyForRedisClusterReplicas = true;\n      return this;\n    }\n\n    public Builder authXManager(AuthXManager authXManager) {\n      this.authXManager = authXManager;\n      return this;\n    }\n\n    public Builder from(JedisClientConfig instance) {\n      this.redisProtocol = instance.getRedisProtocol();\n      this.connectionTimeoutMillis = instance.getConnectionTimeoutMillis();\n      this.socketTimeoutMillis = instance.getSocketTimeoutMillis();\n      this.blockingSocketTimeoutMillis = instance.getBlockingSocketTimeoutMillis();\n      this.credentialsProvider = instance.getCredentialsProvider();\n      this.database = instance.getDatabase();\n      this.clientName = instance.getClientName();\n      this.ssl = instance.isSsl();\n      this.sslSocketFactory = instance.getSslSocketFactory();\n      this.sslParameters = instance.getSslParameters();\n      this.sslOptions = instance.getSslOptions();\n      this.hostnameVerifier = instance.getHostnameVerifier();\n      this.hostAndPortMapper = instance.getHostAndPortMapper();\n      this.clientSetInfoConfig = instance.getClientSetInfoConfig();\n      this.readOnlyForRedisClusterReplicas = instance.isReadOnlyForRedisClusterReplicas();\n      this.authXManager = instance.getAuthXManager();\n      return this;\n    }\n  }\n\n  /**\n   * @deprecated Use {@link redis.clients.jedis.DefaultJedisClientConfig.Builder}.\n   */\n  @Deprecated\n  public static DefaultJedisClientConfig create(int connectionTimeoutMillis, int soTimeoutMillis,\n      int blockingSocketTimeoutMillis, String user, String password, int database,\n      String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,\n      SSLParameters sslParameters, HostnameVerifier hostnameVerifier,\n      HostAndPortMapper hostAndPortMapper) {\n    Builder builder = builder();\n    builder.connectionTimeoutMillis(connectionTimeoutMillis).socketTimeoutMillis(soTimeoutMillis)\n        .blockingSocketTimeoutMillis(blockingSocketTimeoutMillis);\n    if (user != null || password != null) {\n      // deliberately not handling 'user != null && password == null' here\n      builder.credentials(new DefaultRedisCredentials(user, password));\n    }\n    builder.database(database).clientName(clientName);\n    builder.ssl(ssl).sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier);\n    builder.hostAndPortMapper(hostAndPortMapper);\n    return builder.build();\n  }\n\n  /**\n   * @deprecated Use\n   *             {@link redis.clients.jedis.DefaultJedisClientConfig.Builder#from(redis.clients.jedis.JedisClientConfig)}.\n   */\n  @Deprecated\n  public static DefaultJedisClientConfig copyConfig(JedisClientConfig copy) {\n    Builder builder = builder();\n    builder.protocol(copy.getRedisProtocol());\n    builder.connectionTimeoutMillis(copy.getConnectionTimeoutMillis());\n    builder.socketTimeoutMillis(copy.getSocketTimeoutMillis());\n    builder.blockingSocketTimeoutMillis(copy.getBlockingSocketTimeoutMillis());\n\n    Supplier<RedisCredentials> credentialsProvider = copy.getCredentialsProvider();\n    if (credentialsProvider != null) {\n      builder.credentialsProvider(credentialsProvider);\n    } else {\n      builder.user(copy.getUser());\n      builder.password(copy.getPassword());\n    }\n\n    builder.database(copy.getDatabase());\n    builder.clientName(copy.getClientName());\n\n    builder.ssl(copy.isSsl());\n    builder.sslSocketFactory(copy.getSslSocketFactory());\n    builder.sslParameters(copy.getSslParameters());\n    builder.hostnameVerifier(copy.getHostnameVerifier());\n    builder.sslOptions(copy.getSslOptions());\n    builder.hostAndPortMapper(copy.getHostAndPortMapper());\n\n    builder.clientSetInfoConfig(copy.getClientSetInfoConfig());\n    if (copy.isReadOnlyForRedisClusterReplicas()) {\n      builder.readOnlyForRedisClusterReplicas();\n    }\n\n    builder.authXManager(copy.getAuthXManager());\n\n    return builder.build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.security.GeneralSecurityException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.util.IOUtils;\n\npublic class DefaultJedisSocketFactory implements JedisSocketFactory {\n\n  protected static final HostAndPort DEFAULT_HOST_AND_PORT = new HostAndPort(Protocol.DEFAULT_HOST,\n      Protocol.DEFAULT_PORT);\n\n  private volatile HostAndPort hostAndPort = DEFAULT_HOST_AND_PORT;\n  private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;\n  private int socketTimeout = Protocol.DEFAULT_TIMEOUT;\n  private boolean ssl = false;\n  private SSLSocketFactory sslSocketFactory = null;\n  private SslOptions sslOptions = null;\n  private SSLParameters sslParameters = null;\n  private HostnameVerifier hostnameVerifier = null;\n  private HostAndPortMapper hostAndPortMapper = null;\n\n  public DefaultJedisSocketFactory() {\n  }\n\n  public DefaultJedisSocketFactory(HostAndPort hostAndPort) {\n    this(hostAndPort, null);\n  }\n\n  public DefaultJedisSocketFactory(JedisClientConfig config) {\n    this(null, config);\n  }\n\n  public DefaultJedisSocketFactory(HostAndPort hostAndPort, JedisClientConfig config) {\n    if (hostAndPort != null) {\n      this.hostAndPort = hostAndPort;\n    }\n    if (config != null) {\n      this.connectionTimeout = config.getConnectionTimeoutMillis();\n      this.socketTimeout = config.getSocketTimeoutMillis();\n      this.ssl = config.isSsl();\n      this.sslSocketFactory = config.getSslSocketFactory();\n      this.sslParameters = config.getSslParameters();\n      this.sslOptions = config.getSslOptions();\n      this.hostnameVerifier = config.getHostnameVerifier();\n      this.hostAndPortMapper = config.getHostAndPortMapper();\n    }\n  }\n\n  private Socket connectToFirstSuccessfulHost(HostAndPort hostAndPort) throws Exception {\n    List<InetAddress> hosts = Arrays.asList(InetAddress.getAllByName(hostAndPort.getHost()));\n    if (hosts.size() > 1) {\n      Collections.shuffle(hosts);\n    }\n\n    JedisConnectionException jce = new JedisConnectionException(\"Failed to connect to \" + hostAndPort + \".\");\n    for (InetAddress host : hosts) {\n      try {\n        Socket socket = new Socket();\n\n        socket.setReuseAddress(true);\n        socket.setKeepAlive(true); // Will monitor the TCP connection is valid\n        socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to ensure timely delivery of data\n        socket.setSoLinger(true, 0); // Control calls close () method, the underlying socket is closed immediately\n\n        // Passing 'host' directly will avoid another call to InetAddress.getByName() inside the InetSocketAddress constructor.\n        // For machines with ipv4 and ipv6, but the startNode uses ipv4 to connect, the ipv6 connection may fail.\n        socket.connect(new InetSocketAddress(host, hostAndPort.getPort()), connectionTimeout);\n        return socket;\n      } catch (Exception e) {\n        jce.addSuppressed(e);\n      }\n    }\n    throw jce;\n  }\n\n  @Override\n  public Socket createSocket() throws JedisConnectionException {\n    Socket socket = null;\n    try {\n      HostAndPort _hostAndPort = getSocketHostAndPort();\n      socket = connectToFirstSuccessfulHost(_hostAndPort);\n      socket.setSoTimeout(socketTimeout);\n\n      if (ssl || sslOptions != null) {\n        socket = createSslSocket(_hostAndPort, socket);\n      }\n\n      return socket;\n\n    } catch (Exception ex) {\n      IOUtils.closeQuietly(socket);\n      if (ex instanceof JedisConnectionException) {\n        throw (JedisConnectionException) ex;\n      } else {\n        throw new JedisConnectionException(\"Failed to create socket.\", ex);\n      }\n    }\n  }\n\n  /**\n   * ssl enable check is done before this.\n   */\n  private Socket createSslSocket(HostAndPort _hostAndPort, Socket socket) throws IOException, GeneralSecurityException {\n\n    Socket plainSocket = socket;\n\n    SSLSocketFactory _sslSocketFactory;\n    SSLParameters _sslParameters;\n\n    if (sslOptions != null) {\n\n      SSLContext _sslContext = sslOptions.createSslContext();\n      _sslSocketFactory = _sslContext.getSocketFactory();\n\n      _sslParameters = sslOptions.getSslParameters();\n\n    } else {\n\n      _sslSocketFactory = this.sslSocketFactory;\n      _sslParameters = this.sslParameters;\n    }\n\n    if (_sslSocketFactory == null) {\n      _sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();\n    }\n\n    SSLSocket sslSocket = (SSLSocket) _sslSocketFactory.createSocket(socket,\n        _hostAndPort.getHost(), _hostAndPort.getPort(), true);\n\n    if (_sslParameters != null) {\n      sslSocket.setSSLParameters(_sslParameters);\n    }\n\n    // allowing HostnameVerifier for both SslOptions and legacy ssl config\n    if (hostnameVerifier != null && !hostnameVerifier.verify(_hostAndPort.getHost(), sslSocket.getSession())) {\n      String message = String.format(\"The connection to '%s' failed ssl/tls hostname verification.\",\n          _hostAndPort.getHost());\n      throw new JedisConnectionException(message);\n    }\n\n    return new SSLSocketWrapper(sslSocket, plainSocket);\n  }\n\n  public void updateHostAndPort(HostAndPort hostAndPort) {\n    this.hostAndPort = hostAndPort;\n  }\n\n  public HostAndPort getHostAndPort() {\n    return this.hostAndPort;\n  }\n\n  protected HostAndPort getSocketHostAndPort() {\n    HostAndPortMapper mapper = hostAndPortMapper;\n    HostAndPort hap = this.hostAndPort;\n    if (mapper != null) {\n      HostAndPort mapped = mapper.getHostAndPort(hap);\n      if (mapped != null) {\n        return mapped;\n      }\n    }\n    return hap;\n  }\n\n  @Override\n  public String toString() {\n    return \"DefaultJedisSocketFactory{\" + hostAndPort.toString() + \"}\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/DefaultRedisCredentials.java",
    "content": "package redis.clients.jedis;\n\npublic final class DefaultRedisCredentials implements RedisCredentials {\n\n  private final String user;\n  private final char[] password;\n\n  public DefaultRedisCredentials(String user, char[] password) {\n    this.user = user;\n    this.password = password;\n  }\n\n  public DefaultRedisCredentials(String user, CharSequence password) {\n    this.user = user;\n    this.password = password == null ? null\n        : password instanceof String ? ((String) password).toCharArray()\n            : toCharArray(password);\n  }\n\n  @Override\n  public String getUser() {\n    return user;\n  }\n\n  @Override\n  public char[] getPassword() {\n    return password;\n  }\n\n  private static char[] toCharArray(CharSequence seq) {\n    final int len = seq.length();\n    char[] arr = new char[len];\n    for (int i = 0; i < len; i++) {\n      arr[i] = seq.charAt(i);\n    }\n    return arr;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/DefaultRedisCredentialsProvider.java",
    "content": "package redis.clients.jedis;\n\npublic final class DefaultRedisCredentialsProvider implements RedisCredentialsProvider {\n\n  private volatile RedisCredentials credentials;\n\n  public DefaultRedisCredentialsProvider(RedisCredentials credentials) {\n    this.credentials = credentials;\n  }\n\n  public void setCredentials(RedisCredentials credentials) {\n    this.credentials = credentials;\n  }\n\n  @Override\n  public RedisCredentials get() {\n    return this.credentials;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/DriverInfo.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport redis.clients.jedis.exceptions.JedisValidationException;\n\n/**\n * Immutable class representing driver information for Redis client identification.\n * <p>\n * This class is used to identify the client library and any upstream drivers (such as Spring Data\n * Redis or Spring Session) when connecting to Redis. The information is sent via the\n * {@code CLIENT SETINFO} command.\n * <p>\n * The formatted name follows the pattern: {@code name(driver1_vVersion1;driver2_vVersion2)}\n * @see ClientSetInfoConfig\n * @see <a href=\"https://redis.io/docs/latest/commands/client-setinfo/\">CLIENT SETINFO</a>\n */\npublic final class DriverInfo {\n\n  /**\n   * Set of brace characters that are not allowed in driver names or versions. These characters are\n   * used to delimit the driver information in the formatted output and would break parsing.\n   */\n  private static final Set<Character> BRACES = Collections\n      .unmodifiableSet(new HashSet<>(Arrays.asList('(', ')', '[', ']', '{', '}')));\n\n  private final String name;\n\n  private final List<String> upstreamDrivers;\n\n  private DriverInfo(String name, List<String> upstreamDrivers) {\n    this.name = name;\n    this.upstreamDrivers = Collections.unmodifiableList(upstreamDrivers);\n  }\n\n  /**\n   * Creates a new {@link Builder} with default values.\n   * <p>\n   * The default name is \"Jedis\" (from {@link JedisMetaInfo#getArtifactId()}).\n   * @return a new builder instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Creates a new {@link Builder} initialized with values from an existing {@link DriverInfo}.\n   * @param driverInfo the existing driver info to copy from, must not be {@code null}\n   * @return a new builder instance initialized with the existing values\n   * @throws JedisValidationException if driverInfo is {@code null}\n   */\n  public static Builder builder(DriverInfo driverInfo) {\n    if (driverInfo == null) {\n      throw new JedisValidationException(\"DriverInfo must not be null\");\n    }\n    return new Builder(driverInfo);\n  }\n\n  /**\n   * Returns the formatted name including upstream drivers or legacy suffix.\n   * <p>\n   * If a legacy suffix is set, returns the name followed by the suffix in parentheses. Otherwise,\n   * if upstream drivers are present, returns the name followed by upstream drivers in parentheses,\n   * separated by semicolons. If neither is set, returns just the name.\n   * <p>\n   * Examples:\n   * <ul>\n   * <li>{@code \"jedis\"} - no upstream drivers or suffix</li>\n   * <li>{@code \"jedis(my-suffix)\"} - legacy suffix mode</li>\n   * <li>{@code \"jedis(spring-data-redis_v3.2.0)\"} - one upstream driver</li>\n   * <li>{@code \"jedis(spring-session_v3.3.0;spring-data-redis_v3.2.0)\"} - multiple upstream\n   * drivers</li>\n   * </ul>\n   * @return the formatted name for use in CLIENT SETINFO\n   */\n  public String getFormattedName() {\n    if (upstreamDrivers.isEmpty()) {\n      return name;\n    }\n    return String.format(\"%s(%s)\", name, String.join(\";\", upstreamDrivers));\n  }\n\n  /**\n   * Returns the base library name without upstream driver information.\n   * @return the library name\n   */\n  public String getName() {\n    return name;\n  }\n\n  /**\n   * Returns the formatted upstream drivers string (without the base library name).\n   * <p>\n   * Multiple drivers are separated by semicolons, with the most recently added driver appearing\n   * first.\n   * <p>\n   * Examples:\n   * <ul>\n   * <li>{@code \"spring-data-redis_v3.2.0\"} - single upstream driver</li>\n   * <li>{@code \"spring-session_v3.3.0;spring-data-redis_v3.2.0\"} - multiple upstream drivers</li>\n   * </ul>\n   * @return the formatted upstream drivers string, or {@code null} if no upstream drivers are set\n   */\n  public String getUpstreamDrivers() {\n    if (upstreamDrivers.isEmpty()) {\n      return \"\";\n    }\n    return String.join(\";\", upstreamDrivers);\n  }\n\n  @Override\n  public String toString() {\n    return getFormattedName();\n  }\n\n  /**\n   * Builder for creating {@link DriverInfo} instances.\n   */\n  public static class Builder {\n\n    private String name;\n\n    private final List<String> upstreamDrivers;\n\n    private Builder() {\n      this.name = JedisMetaInfo.getArtifactId();\n      this.upstreamDrivers = new ArrayList<>();\n    }\n\n    private Builder(DriverInfo driverInfo) {\n      this.name = driverInfo.name;\n      this.upstreamDrivers = new ArrayList<>(driverInfo.upstreamDrivers);\n    }\n\n    /**\n     * Sets the base library name.\n     * <p>\n     * This overrides the default name (\"Jedis\"). Use this when you want to completely customize the\n     * library identification.\n     * @param name the library name, must not be {@code null}\n     * @return this builder\n     * @throws JedisValidationException if name is {@code null}\n     */\n    public Builder name(String name) {\n      if (name == null) {\n        throw new JedisValidationException(\"Name must not be null\");\n      }\n      this.name = name;\n      return this;\n    }\n\n    /**\n     * Adds an upstream driver to the driver information.\n     * <p>\n     * Upstream drivers are prepended to the list, so the most recently added driver appears first\n     * in the formatted output.\n     * <p>\n     * The driver name must follow Maven artifactId naming conventions: lowercase letters, digits,\n     * hyphens, and underscores only, starting with a lowercase letter. Dots are only allowed after\n     * digits (for Scala cross-version naming like akka-redis_2.13).\n     * <p>\n     * Both values must not contain spaces, newlines, non-printable characters, or brace characters\n     * as these would violate the format of the Redis CLIENT LIST reply.\n     * @param driverName the name of the upstream driver (e.g., \"spring-data-redis\"), must not be\n     *          {@code null}\n     * @param driverVersion the version of the upstream driver (e.g., \"3.2.0\"), must not be\n     *          {@code null}\n     * @return this builder\n     * @throws JedisValidationException if the driver name or version is {@code null} or has invalid\n     *           format\n     * @see <a href=\"https://maven.apache.org/guides/mini/guide-naming-conventions.html\">Maven\n     *      Naming Conventions</a>\n     * @see <a href=\"https://redis.io/docs/latest/commands/client-setinfo/\">CLIENT SETINFO</a>\n     */\n    public Builder addUpstreamDriver(String driverName, String driverVersion) {\n      if (driverName == null) {\n        throw new JedisValidationException(\"Driver name must not be null\");\n      }\n      if (driverVersion == null) {\n        throw new JedisValidationException(\"Driver version must not be null\");\n      }\n      validateDriverField(driverName, \"Driver name\");\n      validateDriverField(driverVersion, \"Driver version\");\n      String formattedDriverInfo = formatDriverInfo(driverName, driverVersion);\n      this.upstreamDrivers.add(0, formattedDriverInfo);\n      return this;\n    }\n\n    public Builder addUpstreamDriver(String driverName) {\n      if (driverName == null) {\n        throw new JedisValidationException(\"Driver name must not be null\");\n      }\n      validateDriverField(driverName, \"Driver name\");\n      this.upstreamDrivers.add(0, driverName);\n      return this;\n    }\n\n    /**\n     * Builds and returns a new immutable {@link DriverInfo} instance.\n     * @return a new DriverInfo instance\n     */\n    public DriverInfo build() {\n      return new DriverInfo(name, upstreamDrivers);\n    }\n  }\n\n  /**\n   * Validates that the value does not contain characters that would violate the format of the Redis\n   * CLIENT LIST reply.\n   * <p>\n   * Only printable ASCII characters (0x21-0x7E, i.e., '!' to '~') are allowed, excluding braces.\n   * @param value the value to validate\n   * @param fieldName the name of the field for error messages (e.g., \"Driver name\", \"Driver\n   *          version\")\n   * @throws JedisValidationException if the value is empty or contains invalid characters\n   * @see <a href=\"https://redis.io/docs/latest/commands/client-setinfo/\">CLIENT SETINFO</a>\n   */\n  private static void validateDriverField(String value, String fieldName) {\n    if (value.trim().isEmpty()) {\n      throw new JedisValidationException(fieldName + \" must not be empty\");\n    }\n\n    validateNoInvalidCharacters(value, fieldName);\n  }\n\n  /**\n   * Validates that the value does not contain characters that would violate the format of the Redis\n   * CLIENT LIST reply: non-printable characters, spaces, or brace characters.\n   * <p>\n   * Only printable ASCII characters (0x21-0x7E, i.e., '!' to '~') are allowed, excluding braces.\n   * @param value the value to validate\n   * @param fieldName the name of the field for error messages\n   * @throws JedisValidationException if the value contains invalid characters\n   * @see <a href=\"https://redis.io/docs/latest/commands/client-setinfo/\">CLIENT SETINFO</a>\n   */\n  private static void validateNoInvalidCharacters(String value, String fieldName) {\n    for (int i = 0; i < value.length(); i++) {\n      char c = value.charAt(i);\n      if (c < '!' || c > '~' || BRACES.contains(c)) {\n        throw new JedisValidationException(\n            fieldName + \" must not contain spaces, newlines, non-printable characters, or braces\");\n      }\n    }\n  }\n\n  private static String formatDriverInfo(String driverName, String driverVersion) {\n    return driverName + \"_v\" + driverVersion;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Endpoint.java",
    "content": "package redis.clients.jedis;\n\npublic interface Endpoint {\n\n  String getHost();\n\n  int getPort();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/GeoCoordinate.java",
    "content": "package redis.clients.jedis;\n\npublic class GeoCoordinate {\n\n  private double longitude;\n  private double latitude;\n\n  public GeoCoordinate(double longitude, double latitude) {\n    this.longitude = longitude;\n    this.latitude = latitude;\n  }\n\n  public double getLongitude() {\n    return longitude;\n  }\n\n  public double getLatitude() {\n    return latitude;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == null) return false;\n    if (o == this) return true;\n    if (!(o instanceof GeoCoordinate)) return false;\n\n    GeoCoordinate that = (GeoCoordinate) o;\n\n    if (Double.compare(that.longitude, longitude) != 0) return false;\n    return Double.compare(that.latitude, latitude) == 0;\n  }\n\n  @Override\n  public int hashCode() {\n    // follows IntelliJ default hashCode implementation\n    int result;\n    long temp;\n    temp = Double.doubleToLongBits(longitude);\n    result = (int) (temp ^ (temp >>> 32));\n    temp = Double.doubleToLongBits(latitude);\n    result = 31 * result + (int) (temp ^ (temp >>> 32));\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"(\" + longitude + \",\" + latitude + \")\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/HostAndPort.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.Serializable;\n\npublic class HostAndPort implements Serializable, Endpoint {\n\n  private static final long serialVersionUID = -519876229978427751L;\n\n  private final String host;\n  private final int port;\n\n  public HostAndPort(String host, int port) {\n    this.host = host;\n    this.port = port;\n  }\n\n  @Override\n  public String getHost() {\n    return host;\n  }\n\n  @Override\n  public int getPort() {\n    return port;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null) return false;\n    if (obj == this) return true;\n    if (!(obj instanceof HostAndPort)) return false;\n    HostAndPort other = (HostAndPort) obj;\n    return this.port == other.port && this.host.equals(other.host);\n  }\n\n  @Override\n  public int hashCode() {\n    return 31 * host.hashCode() + port;\n  }\n\n  @Override\n  public String toString() {\n    return host + \":\" + port;\n  }\n\n  /**\n   * Creates HostAndPort with <i>unconverted</i> host.\n   * @param string String to parse. Must be in <b>\"host:port\"</b> format. Port is mandatory.\n   * @return parsed HostAndPort\n   */\n  public static HostAndPort from(String string) {\n    int lastColon = string.lastIndexOf(\":\");\n    String host = string.substring(0, lastColon);\n    int port = Integer.parseInt(string.substring(lastColon + 1));\n    return new HostAndPort(host, port);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/HostAndPortMapper.java",
    "content": "package redis.clients.jedis;\n\n/**\n * An interface for mapping Redis node addresses.\n * <p>\n * It is used to translate an advertised server address to one that is reachable by the client,\n * especially in network topologies involving NAT or containerization.\n */\n@FunctionalInterface\npublic interface HostAndPortMapper {\n\n  /**\n   * @param hap The original address from the server.\n   * @return The translated, reachable address.\n   */\n  HostAndPort getHostAndPort(HostAndPort hap);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Jedis.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.Command.*;\nimport static redis.clients.jedis.Protocol.Keyword.*;\nimport static redis.clients.jedis.Protocol.SentinelKeyword.*;\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.util.SafeEncoder.encode;\n\nimport java.io.Closeable;\nimport java.net.URI;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.Arrays;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport redis.clients.jedis.Protocol.*;\nimport redis.clients.jedis.args.*;\nimport redis.clients.jedis.commands.*;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.exceptions.InvalidURIException;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.util.JedisURIHelper;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.Pool;\n\n/**\n * Jedis is a lightweight Redis client that uses a single, non-pooled connection to Redis.\n * <p>\n * <b>Important:</b> For most production use cases, {@link RedisClient} is the recommended and\n * preferred option. {@code RedisClient} provides connection pooling, better resource management,\n * and improved performance for typical applications.\n * </p>\n * <p>\n * <b>When to use Jedis:</b>\n * </p>\n * <ul>\n * <li><b>Short-lived scripts or utilities:</b> When you need a simple, lightweight client for\n * one-off operations or command-line tools.</li>\n * <li><b>Testing and development:</b> For unit tests or local development where connection pooling\n * overhead is unnecessary.</li>\n * <li><b>Fine-grained connection control:</b> Advanced scenarios requiring explicit control over\n * individual connections, such as managing connection lifecycle manually or implementing custom\n * connection strategies.</li>\n * <li><b>Single-threaded applications:</b> Applications that execute Redis commands sequentially\n * from a single thread and don't benefit from connection pooling.</li>\n * </ul>\n * <p>\n * <b>When to use RedisClient instead:</b>\n * </p>\n * <ul>\n * <li><b>Production applications:</b> Any multi-threaded or high-throughput application should use\n * {@link RedisClient} for its connection pooling capabilities.</li>\n * <li><b>Web applications:</b> Server applications handling concurrent requests benefit from\n * connection pooling to avoid connection overhead.</li>\n * <li><b>Long-running services:</b> Applications that maintain persistent connections to Redis\n * should use {@link RedisClient} for better resource management.</li>\n * <li><b>Default choice:</b> If you're unsure which to use, choose {@link RedisClient}.</li>\n * </ul>\n * <p>\n * <b>Usage example:</b>\n * </p>\n *\n * <pre>\n * {\n *   &#64;code\n *   // Simple usage for a short-lived operation\n *   try (Jedis jedis = new Jedis(\"localhost\", 6379)) {\n *     jedis.set(\"key\", \"value\");\n *     String value = jedis.get(\"key\");\n *   }\n * }\n * </pre>\n * <p>\n * <b>Note:</b> Each {@code Jedis} instance maintains a single connection. For concurrent access\n * from multiple threads, either use {@link RedisClient} with connection pooling, or create\n * separate {@code Jedis} instances per thread (not recommended for production).\n * </p>\n *\n * @see RedisClient for the recommended pooled client for production use\n * @see JedisPool for legacy pooled connections (deprecated, use RedisClient instead)\n */\npublic class Jedis implements ServerCommands, DatabaseCommands, JedisCommands, JedisBinaryCommands,\n    ControlCommands, ControlBinaryCommands, ClusterCommands, ModuleCommands, GenericControlCommands,\n    SentinelCommands, CommandCommands,  Closeable {\n\n  protected final Connection connection;\n  private final CommandObjects commandObjects = new CommandObjects();\n  private int db = 0;\n  private Transaction transaction = null;\n  private boolean isInMulti = false;\n  private boolean isInWatch = false;\n  private Pipeline pipeline = null;\n  protected static final byte[][] DUMMY_ARRAY = new byte[0][];\n\n  private Pool<Jedis> dataSource = null;\n\n  public Jedis() {\n    connection = new Connection();\n  }\n\n  /**\n   * This constructor only accepts a URI string. {@link JedisURIHelper#isValid(java.net.URI)} can be\n   * used before this.\n   * @param url\n   */\n  public Jedis(final String url) {\n    this(URI.create(url));\n  }\n\n  public Jedis(final HostAndPort hp) {\n    connection = new Connection(hp);\n  }\n\n  public Jedis(final String host, final int port) {\n    connection = new Connection(host, port);\n  }\n\n  public Jedis(final String host, final int port, final JedisClientConfig config) {\n    this(new HostAndPort(host, port), config);\n  }\n\n  public Jedis(final HostAndPort hostPort, final JedisClientConfig config) {\n    connection = new Connection(hostPort, config);\n    RedisProtocol proto = config.getRedisProtocol();\n    if (proto != null) commandObjects.setProtocol(proto);\n  }\n\n  public Jedis(final String host, final int port, final boolean ssl) {\n    this(host, port, DefaultJedisClientConfig.builder().ssl(ssl).build());\n  }\n\n  public Jedis(final String host, final int port, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(host, port, DefaultJedisClientConfig.builder().ssl(ssl)\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public Jedis(final String host, final int port, final int timeout) {\n    this(host, port, timeout, timeout);\n  }\n\n  public Jedis(final String host, final int port, final int timeout, final boolean ssl) {\n    this(host, port, timeout, timeout, ssl);\n  }\n\n  public Jedis(final String host, final int port, final int timeout, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(host, port, timeout, timeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public Jedis(final String host, final int port, final int connectionTimeout,\n      final int soTimeout) {\n    this(host, port, DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout).build());\n  }\n\n  public Jedis(final String host, final int port, final int connectionTimeout,\n      final int soTimeout, final int infiniteSoTimeout) {\n    this(host, port, DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)\n        .blockingSocketTimeoutMillis(infiniteSoTimeout).build());\n  }\n\n  public Jedis(final String host, final int port, final int connectionTimeout,\n      final int soTimeout, final boolean ssl) {\n    this(host, port, DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout).ssl(ssl)\n        .build());\n  }\n\n  public Jedis(final String host, final int port, final int connectionTimeout,\n      final int soTimeout, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(host, port, DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout).ssl(ssl)\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public Jedis(final String host, final int port, final int connectionTimeout,\n      final int soTimeout, final int infiniteSoTimeout, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(host, port, DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)\n        .blockingSocketTimeoutMillis(infiniteSoTimeout).ssl(ssl)\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public Jedis(URI uri) {\n    if (!JedisURIHelper.isValid(uri)) {\n      throw new InvalidURIException(String.format(\n        \"Cannot open Redis connection due invalid URI \\\"%s\\\".\", uri.toString()));\n    }\n    connection = new Connection(new HostAndPort(uri.getHost(), uri.getPort()),\n        DefaultJedisClientConfig.builder().user(JedisURIHelper.getUser(uri))\n            .password(JedisURIHelper.getPassword(uri)).database(JedisURIHelper.getDBIndex(uri))\n            .protocol(JedisURIHelper.getRedisProtocol(uri))\n            .ssl(JedisURIHelper.isRedisSSLScheme(uri)).build());\n  }\n\n  public Jedis(URI uri, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(uri, DefaultJedisClientConfig.builder().sslSocketFactory(sslSocketFactory)\n        .sslParameters(sslParameters).hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public Jedis(final URI uri, final int timeout) {\n    this(uri, timeout, timeout);\n  }\n\n  public Jedis(final URI uri, final int timeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(uri, timeout, timeout, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public Jedis(final URI uri, final int connectionTimeout, final int soTimeout) {\n    this(uri, DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).build());\n  }\n\n  public Jedis(final URI uri, final int connectionTimeout, final int soTimeout,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(uri, DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).sslSocketFactory(sslSocketFactory)\n        .sslParameters(sslParameters).hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public Jedis(final URI uri, final int connectionTimeout, final int soTimeout,\n      final int infiniteSoTimeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(uri, DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout)\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build());\n  }\n\n  /**\n   * Create a new Jedis with the provided URI and JedisClientConfig object. Note that all fields\n   * that can be parsed from the URI will be used instead of the corresponding configuration values. This includes\n   * the following fields: user, password, database, protocol version, and whether to use SSL.\n   *\n   * For example, if the URI is \"redis://user:password@localhost:6379/1\", the user and password fields will be set\n   * to \"user\" and \"password\" respectively, the database field will be set to 1. Those fields will be ignored\n   * from the JedisClientConfig object.\n   *\n   * @param uri The URI to connect to\n   * @param config The JedisClientConfig object to use\n   */\n  public Jedis(final URI uri, JedisClientConfig config) {\n    if (!JedisURIHelper.isValid(uri)) {\n      throw new InvalidURIException(String.format(\n        \"Cannot open Redis connection due invalid URI \\\"%s\\\".\", uri.toString()));\n    }\n    connection = new Connection(new HostAndPort(uri.getHost(), uri.getPort()),\n        DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(config.getConnectionTimeoutMillis())\n            .socketTimeoutMillis(config.getSocketTimeoutMillis())\n            .blockingSocketTimeoutMillis(config.getBlockingSocketTimeoutMillis())\n            .user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri))\n            .database(JedisURIHelper.getDBIndex(uri)).clientName(config.getClientName())\n            .protocol(JedisURIHelper.getRedisProtocol(uri))\n            .ssl(JedisURIHelper.isRedisSSLScheme(uri)).sslSocketFactory(config.getSslSocketFactory())\n            .sslParameters(config.getSslParameters()).hostnameVerifier(config.getHostnameVerifier())\n            .build());\n    RedisProtocol proto = config.getRedisProtocol();\n    if (proto != null) commandObjects.setProtocol(proto);\n  }\n\n  public Jedis(final JedisSocketFactory jedisSocketFactory) {\n    connection = new Connection(jedisSocketFactory);\n  }\n\n  public Jedis(final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) {\n    connection = new Connection(jedisSocketFactory, clientConfig);\n    RedisProtocol proto = clientConfig.getRedisProtocol();\n    if (proto != null) commandObjects.setProtocol(proto);\n  }\n\n  public Jedis(final Connection connection) {\n    this.connection = connection;\n  }\n\n  @Override\n  public String toString() {\n    return \"Jedis{\" + connection + '}';\n  }\n\n  // Legacy\n  public Connection getClient() {\n    return getConnection();\n  }\n\n  public Connection getConnection() {\n    return connection;\n  }\n\n  // Legacy\n  public void connect() {\n    connection.connect();\n  }\n\n  /**\n   * Closing the socket will disconnect the server connection.\n   */\n  public void disconnect() {\n    connection.disconnect();\n  }\n\n  public boolean isConnected() {\n    return connection.isConnected();\n  }\n\n  public boolean isBroken() {\n    return connection.isBroken();\n  }\n\n  public void resetState() {\n    if (isConnected()) {\n      if (transaction != null) {\n        transaction.close();\n      }\n\n      if (pipeline != null) {\n        pipeline.close();\n      }\n\n//      connection.resetState();\n      if (isInWatch) {\n        connection.sendCommand(UNWATCH);\n        connection.getStatusCodeReply();\n        isInWatch = false;\n      }\n    }\n\n    transaction = null;\n    pipeline = null;\n  }\n\n  protected void setDataSource(Pool<Jedis> jedisPool) {\n    this.dataSource = jedisPool;\n  }\n\n  @Override\n  public void close() {\n    if (dataSource != null) {\n      Pool<Jedis> pool = this.dataSource;\n      this.dataSource = null;\n      if (isBroken()) {\n        pool.returnBrokenResource(this);\n      } else {\n        pool.returnResource(this);\n      }\n    } else {\n      connection.close();\n    }\n  }\n\n  // Legacy\n  public Transaction multi() {\n    transaction = new Transaction(getConnection()) {\n      @Override\n      protected void onAfterExec() {\n        resetState();\n      }\n\n      @Override\n      protected void onAfterDiscard() {\n        resetState();\n      }\n    };\n    return transaction;\n  }\n\n  // Legacy\n  public Pipeline pipelined() {\n    pipeline = new Pipeline(this);\n    return pipeline;\n  }\n\n  // Legacy\n  protected void checkIsInMultiOrPipeline() {\n//    if (connection.isInMulti()) {\n    if (transaction != null) {\n      throw new IllegalStateException(\n          \"Cannot use Jedis when in Multi. Please use Transaction or reset jedis state.\");\n    } else if (pipeline != null && pipeline.hasPipelinedResponse()) {\n      throw new IllegalStateException(\n          \"Cannot use Jedis when in Pipeline. Please use Pipeline or reset jedis state.\");\n    }\n  }\n\n  public int getDB() {\n    return this.db;\n  }\n\n  /**\n   * @return <code>PONG</code>\n   */\n  @Override\n  public String ping() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.PING);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Works same as {@link Jedis#ping()} but returns argument message instead of <code>PONG</code>.\n   * @param message\n   * @return message\n   */\n  public byte[] ping(final byte[] message) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.PING, message);\n    return connection.getBinaryBulkReply();\n  }\n\n  /**\n   * Select the DB with having the specified zero-based numeric index. For default every new\n   * connection is automatically selected to DB 0.\n   * @param index\n   * @return OK\n   */\n  @Override\n  public String select(final int index) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(SELECT, toByteArray(index));\n    String statusCodeReply = connection.getStatusCodeReply();\n    this.db = index;\n    return statusCodeReply;\n  }\n\n  @Override\n  public String swapDB(final int index1, final int index2) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(SWAPDB, toByteArray(index1), toByteArray(index2));\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Delete all the keys of the currently selected DB. This command never fails.\n   * @return OK\n   */\n  @Override\n  public String flushDB() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.flushDB());\n  }\n\n  /**\n   * Delete all the keys of the currently selected DB. This command never fails.\n   * @param flushMode\n   * @return OK\n   */\n  @Override\n  public String flushDB(FlushMode flushMode) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(FLUSHDB, flushMode.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Delete all the keys of all the existing databases, not just the currently selected one. This\n   * command never fails.\n   * @return OK\n   */\n  @Override\n  public String flushAll() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.flushAll());\n  }\n\n  /**\n   * Delete all the keys of all the existing databases, not just the currently selected one. This\n   * command never fails.\n   * @param flushMode\n   * @return OK\n   */\n  @Override\n  public String flushAll(FlushMode flushMode) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(FLUSHALL, flushMode.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * COPY source destination [DB destination-db] [REPLACE]\n   *\n   * @param srcKey the source key.\n   * @param dstKey the destination key.\n   * @param db\n   * @param replace\n   */\n  @Override\n  public boolean copy(byte[] srcKey, byte[] dstKey, int db, boolean replace) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.copy(srcKey, dstKey, db, replace));\n  }\n\n  /**\n   * COPY source destination [DB destination-db] [REPLACE]\n   *\n   * @param srcKey the source key.\n   * @param dstKey the destination key.\n   * @param replace\n   */\n  @Override\n  public boolean copy(byte[] srcKey, byte[] dstKey, boolean replace) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.copy(srcKey, dstKey, replace));\n  }\n\n  /**\n   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1\n   * GB).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return OK\n   */\n  @Override\n  public String set(final byte[] key, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.set(key, value));\n  }\n\n  /**\n   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1\n   * GB).\n   * @param key\n   * @param value\n   * @param params NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the\n   *          key if it already exists. EX|PX, expire time units: EX = seconds; PX = milliseconds\n   * @return simple-string-reply {@code OK} if {@code SET} was executed correctly, or {@code null}\n   * if the {@code SET} operation was not performed because the user specified the NX or XX option\n   * but the condition was not met.\n   */\n  @Override\n  public String set(final byte[] key, final byte[] value, final SetParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.set(key, value, params));\n  }\n\n  /**\n   * Get the value of the specified key. If the key does not exist the special value 'nil' is\n   * returned. If the value stored at key is not a string an error is returned because GET can only\n   * handle string values.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public byte[] get(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.get(key));\n  }\n\n  @Override\n  public byte[] digestKey(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.digestKey(key));\n  }\n\n  @Override\n  public byte[] setGet(final byte[] key, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setGet(key, value));\n  }\n\n  @Override\n  public byte[] setGet(final byte[] key, final byte[] value, final SetParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setGet(key, value, params));\n  }\n\n  /**\n   * Get the value of key and delete the key. This command is similar to GET, except for the fact\n   * that it also deletes the key on success (if and only if the key's value type is a string).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The value of key\n   */\n  @Override\n  public byte[] getDel(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getDel(key));\n  }\n\n  @Override\n  public byte[] getEx(final byte[] key, final GetExParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getEx(key, params));\n  }\n\n  /**\n   * Test if the specified keys exist. The command returns the number of keys exist.\n   * Time complexity: O(N)\n   * @param keys\n   * @return An integer greater than 0 if one or more keys exist, 0 if none of the specified keys exist\n   */\n  @Override\n  public long exists(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.exists(keys));\n  }\n\n  /**\n   * Test if the specified key exists. The command returns true if the key exists, otherwise false is\n   * returned. Note that even keys set with an empty string as value will return true. Time\n   * complexity: O(1)\n   * @param key\n   * @return {@code true} if the key exists, otherwise {@code false}\n   */\n  @Override\n  public boolean exists(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.exists(key));\n  }\n\n  /**\n   * Remove the specified keys. If a given key does not exist no operation is performed for this\n   * key. The command returns the number of keys removed. Time complexity: O(1)\n   * @param keys\n   * @return The number of keys that were removed\n   */\n  @Override\n  public long del(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.del(keys));\n  }\n  @Override\n  public long delex(final byte[] key, final CompareCondition condition) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.delex(key, condition));\n  }\n\n\n  @Override\n  public long del(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.del(key));\n  }\n\n  /**\n   * This command is very similar to DEL: it removes the specified keys. Just like DEL a key is\n   * ignored if it does not exist. However, the command performs the actual memory reclaiming in a\n   * different thread, so it is not blocking, while DEL is. This is where the command name comes\n   * from: the command just unlinks the keys from the keyspace. The actual removal will happen later\n   * asynchronously.\n   * <p>\n   * Time complexity: O(1) for each key removed regardless of its size. Then the command does O(N)\n   * work in a different thread in order to reclaim memory, where N is the number of allocations the\n   * deleted objects where composed of.\n   * @param keys\n   * @return The number of keys that were unlinked\n   */\n  @Override\n  public long unlink(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.unlink(keys));\n  }\n\n  @Override\n  public long unlink(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.unlink(key));\n  }\n\n  /**\n   * Return the type of the value stored at key in form of a string. The type can be one of \"none\",\n   * \"string\", \"list\", \"set\". \"none\" is returned if the key does not exist. Time complexity: O(1)\n   * @param key\n   * @return \"none\" if the key does not exist, \"string\" if the key contains a String value, \"list\"\n   * if the key contains a List value, \"set\" if the key contains a Set value, \"zset\" if the key\n   * contains a Sorted Set value, \"hash\" if the key contains a Hash value\n   */\n  @Override\n  public String type(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.type(key));\n  }\n\n  /**\n   * Returns all the keys matching the glob-style pattern as space separated strings. For example if\n   * you have in the database the keys \"foo\" and \"foobar\" the command \"KEYS foo*\" will return\n   * \"foo foobar\".\n   * <p>\n   * Note that while the time complexity for this operation is O(n) the constant times are pretty\n   * low. For example Redis running on an entry level laptop can scan a 1 million keys database in\n   * 40 milliseconds. <b>Still it's better to consider this one of the slow commands that may ruin\n   * the DB performance if not used with care.</b>\n   * <p>\n   * In other words this command is intended only for debugging and special operations like creating\n   * a script to change the DB schema. Don't use it in your normal code. Use Redis Sets in order to\n   * group together a subset of objects.\n   * <p>\n   * Glob style patterns examples:\n   * <ul>\n   * <li>h?llo will match hello hallo hhllo\n   * <li>h*llo will match hllo heeeello\n   * <li>h[ae]llo will match hello and hallo, but not hillo\n   * </ul>\n   * <p>\n   * Use \\ to escape special chars if you want to match them verbatim.\n   * <p>\n   * Time complexity: O(n) (with n being the number of keys in the DB, and assuming keys and pattern\n   * of limited length)\n   * @param pattern\n   * @return Multi bulk reply\n   */\n  @Override\n  public Set<byte[]> keys(final byte[] pattern) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.keys(pattern));\n  }\n\n  /**\n   * Return a randomly selected key from the currently selected DB.\n   * <p>\n   * Time complexity: O(1)\n   * @return The randomly selected key or an empty string is the database is empty\n   */\n  @Override\n  public byte[] randomBinaryKey() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.randomBinaryKey());\n  }\n\n  /**\n   * Atomically renames the key oldkey to newkey. If the source and destination name are the same an\n   * error is returned. If newkey already exists it is overwritten.\n   * <p>\n   * Time complexity: O(1)\n   * @param oldkey\n   * @param newkey\n   * @return OK\n   */\n  @Override\n  public String rename(final byte[] oldkey, final byte[] newkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rename(oldkey, newkey));\n  }\n\n  /**\n   * Rename oldkey into newkey but fails if the destination key newkey already exists.\n   * <p>\n   * Time complexity: O(1)\n   * @param oldkey\n   * @param newkey\n   * @return 1 if the key was renamed 0 if the target key already exist\n   */\n  @Override\n  public long renamenx(final byte[] oldkey, final byte[] newkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.renamenx(oldkey, newkey));\n  }\n\n  /**\n   * Return the number of keys in the currently selected database.\n   * @return The number of keys\n   */\n  @Override\n  public long dbSize() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(DBSIZE);\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * Set a timeout on the specified key. After the timeout the key will be automatically deleted by\n   * the server. A key with an associated timeout is said to be volatile in Redis terminology.\n   * <p>\n   * Volatile keys are stored on disk like the other keys, the timeout is persistent too like all\n   * the other aspects of the dataset. Saving a dataset containing expires and stopping the server\n   * does not stop the flow of time as Redis stores on disk the time when the key will no longer be\n   * available as Unix time, and not the remaining seconds.\n   * <p>\n   * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire\n   * set. It is also possible to undo the expire at all turning the key into a normal key using the\n   * {@link Jedis#persist(byte[]) PERSIST} command.\n   * <p>\n   * Time complexity: O(1)\n   * @see <a href=\"http://redis.io/commands/expire\">Expire Command</a>\n   * @param key\n   * @param seconds\n   * @return 1: the timeout was set. 0: the timeout was not set.\n   */\n  @Override\n  public long expire(final byte[] key, final long seconds) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expire(key, seconds));\n  }\n\n  @Override\n  public long expire(final byte[] key, final long seconds, final ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand((commandObjects.expire(key, seconds, expiryOption)));\n  }\n\n  /**\n   * Set a timeout on the specified key. After the timeout the key will be automatically deleted by\n   * the server. A key with an associated timeout is said to be volatile in Redis terminology.\n   * <p>\n   * Volatile keys are stored on disk like the other keys, the timeout is persistent too like all\n   * the other aspects of the dataset. Saving a dataset containing expires and stopping the server\n   * does not stop the flow of time as Redis stores on disk the time when the key will no longer be\n   * available as Unix time, and not the remaining milliseconds.\n   * <p>\n   * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire\n   * set. It is also possible to undo the expire at all turning the key into a normal key using the\n   * {@link Jedis#persist(byte[]) PERSIST} command.\n   * <p>\n   * Time complexity: O(1)\n   * @see <a href=\"http://redis.io/commands/pexpire\">PEXPIRE Command</a>\n   * @param key\n   * @param milliseconds\n   * @return 1: the timeout was set. 0: the timeout was not set.\n   */\n  @Override\n  public long pexpire(final byte[] key, final long milliseconds) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpire(key, milliseconds));\n  }\n\n  @Override\n  public long pexpire(final byte[] key, final long milliseconds, final ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpire(key, milliseconds, expiryOption));\n  }\n\n  @Override\n  public long expireTime(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand((commandObjects.expireTime(key)));\n  }\n\n  @Override\n  public long pexpireTime(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpireTime(key));\n  }\n\n  /**\n   * EXPIREAT works exactly like {@link Jedis#expire(byte[], long) EXPIRE} but instead to get the\n   * number of seconds representing the Time To Live of the key as a second argument (that is a\n   * relative way of specifying the TTL), it takes an absolute one in the form of a UNIX timestamp\n   * (Number of seconds elapsed since 1 Gen 1970).\n   * <p>\n   * EXPIREAT was introduced in order to implement the Append Only File persistence mode so that\n   * EXPIRE commands are automatically translated into EXPIREAT commands for the append only file.\n   * Of course EXPIREAT can also used by programmers that need a way to simply specify that a given\n   * key should expire at a given time in the future.\n   * <p>\n   * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire\n   * set. It is also possible to undo the expire at all turning the key into a normal key using the\n   * {@link Jedis#persist(byte[]) PERSIST} command.\n   * <p>\n   * Time complexity: O(1)\n   * @see <a href=\"http://redis.io/commands/expire\">Expire Command</a>\n   * @param key\n   * @param unixTime\n   * @return 1: the timeout was set. 0: the timeout was not set since\n   *         the key already has an associated timeout (this may happen only in Redis versions &lt;\n   *         2.1.3, Redis &gt;= 2.1.3 will happily update the timeout), or the key does not exist.\n   */\n  @Override\n  public long expireAt(final byte[] key, final long unixTime) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expireAt(key, unixTime));\n  }\n\n  @Override\n  public long expireAt(byte[] key, long unixTime, ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expireAt(key, unixTime, expiryOption));\n  }\n\n  @Override\n  public long pexpireAt(final byte[] key, final long millisecondsTimestamp) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp));\n  }\n\n  @Override\n  public long pexpireAt(byte[] key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption));\n  }\n\n  /**\n   * The TTL command returns the remaining time to live in seconds of a key that has an\n   * {@link Jedis#expire(byte[], long) EXPIRE} set. This introspection capability allows a Redis\n   * connection to check how many seconds a given key will continue to be part of the dataset.\n   * @param key\n   * @return TTL in seconds, or a negative value in order to signal an error\n   */\n  @Override\n  public long ttl(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.ttl(key));\n  }\n\n  /**\n   * Alters the last access time of a key(s). A key is ignored if it does not exist.\n   * Time complexity: O(N) where N is the number of keys that will be touched.\n   * @param keys\n   * @return The number of keys that were touched.\n   */\n  @Override\n  public long touch(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.touch(keys));\n  }\n\n  @Override\n  public long touch(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.touch(key));\n  }\n\n  /**\n   * Move the specified key from the currently selected DB to the specified destination DB. Note\n   * that this command returns 1 only if the key was successfully moved, and 0 if the target key was\n   * already there or if the source key was not found at all, so it is possible to use MOVE as a\n   * locking primitive.\n   * @param key\n   * @param dbIndex\n   * @return 1 if the key was moved 0 if the key was not moved because\n   *         already present on the target DB or was not found in the current DB.\n   */\n  @Override\n  public long move(final byte[] key, final int dbIndex) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MOVE, key, toByteArray(dbIndex));\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * GETSET is an atomic set this value and return the old value command. Set key to the string\n   * value and return the old value stored at key. The string can't be longer than 1073741824 bytes\n   * (1 GB).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return Bulk reply\n   * @deprecated Use {@link Jedis#setGet(byte[], byte[])}.\n   */\n  @Deprecated\n  @Override\n  public byte[] getSet(final byte[] key, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getSet(key, value));\n  }\n\n  /**\n   * Get the values of all the specified keys. If one or more keys don't exist or is not of type\n   * String, a 'nil' value is returned instead of the value of the specified key, but the operation\n   * never fails.\n   * <p>\n   * Time complexity: O(1) for every key\n   * @param keys\n   * @return Multi bulk reply\n   */\n  @Override\n  public List<byte[]> mget(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.mget(keys));\n  }\n\n  /**\n   * SETNX works exactly like {@link Jedis#set(byte[], byte[]) SET} with the only difference that if\n   * the key already exists no operation is performed. SETNX actually means \"SET if Not eXists\".\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return 1 if the key was set 0 if the key was not set\n   * @deprecated Use {@link Jedis#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public long setnx(final byte[] key, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setnx(key, value));\n  }\n\n  /**\n   * The command is exactly equivalent to the following group of commands:\n   * {@link Jedis#set(byte[], byte[]) SET} + {@link Jedis#expire(byte[], long) EXPIRE}. The\n   * operation is atomic.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param seconds\n   * @param value\n   * @return OK\n   * @deprecated Use {@link Jedis#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String setex(final byte[] key, final long seconds, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setex(key, seconds, value));\n  }\n\n  /**\n   * Set the respective keys to the respective values. MSET will replace old values with new\n   * values, while {@link Jedis#msetnx(byte[][]) MSETNX} will not perform any operation at all even\n   * if just a single key already exists.\n   * <p>\n   * Because of this semantic MSETNX can be used in order to set different keys representing\n   * different fields of an unique logic object in a way that ensures that either all the fields or\n   * none at all are set.\n   * <p>\n   * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B\n   * are modified, another connection talking to Redis can either see the changes to both A and B at\n   * once, or no modification at all.\n   * @see Jedis#msetnx(byte[][])\n   * @param keysvalues\n   * @return OK\n   */\n  @Override\n  public String mset(final byte[]... keysvalues) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.mset(keysvalues));\n  }\n\n  /**\n   * Set the respective keys to the respective values. {@link Jedis#mset(byte[][]) MSET} will\n   * replace old values with new values, while MSETNX will not perform any operation at all even if\n   * just a single key already exists.\n   * <p>\n   * Because of this semantic MSETNX can be used in order to set different keys representing\n   * different fields of an unique logic object in a way that ensures that either all the fields or\n   * none at all are set.\n   * <p>\n   * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B\n   * are modified, another connection talking to Redis can either see the changes to both A and B at\n   * once, or no modification at all.\n   * @see Jedis#mset(byte[][])\n   * @param keysvalues\n   * @return 1 if the all the keys were set 0 if no key was set (at\n   *         least one key already existed)\n   */\n  @Override\n  public long msetnx(final byte[]... keysvalues) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.msetnx(keysvalues));\n  }\n\n  @Override\n  public boolean msetex(final MSetExParams params, final byte[]... keysvalues) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.msetex(params, keysvalues));\n  }\n\n  /**\n   * DECRBY work just like {@link Jedis#decr(byte[]) DECR} but instead to decrement by 1 the\n   * decrement is integer.\n   * <p>\n   * DECR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(byte[])\n   * @see Jedis#decr(byte[])\n   * @see Jedis#incrBy(byte[], long)\n   * @param key\n   * @param decrement\n   * @return The value of key after the decrement\n   */\n  @Override\n  public long decrBy(final byte[] key, final long decrement) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.decrBy(key, decrement));\n  }\n\n  /**\n   * Decrement the number stored at key by one. If the key does not exist or contains a value of a\n   * wrong type, set the key to the value of \"0\" before to perform the decrement operation.\n   * <p>\n   * DECR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(byte[])\n   * @see Jedis#incrBy(byte[], long)\n   * @see Jedis#decrBy(byte[], long)\n   * @param key\n   * @return The value of key after the decrement\n   */\n  @Override\n  public long decr(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.decr(key));\n  }\n\n  /**\n   * INCRBY work just like {@link Jedis#incr(byte[]) INCR} but instead to increment by 1 the\n   * increment is integer.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(byte[])\n   * @see Jedis#decr(byte[])\n   * @see Jedis#decrBy(byte[], long)\n   * @param key\n   * @param increment\n   * @return The value of key after the increment\n   */\n  @Override\n  public long incrBy(final byte[] key, final long increment) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.incrBy(key, increment));\n  }\n\n  /**\n   * INCRBYFLOAT work just like {@link Jedis#incrBy(byte[], long)} INCRBY} but increments by floats\n   * instead of integers.\n   * <p>\n   * INCRBYFLOAT commands are limited to double precision floating point values.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"double\" types.\n   * Simply the string stored at the key is parsed as a base double precision floating point value,\n   * incremented, and then converted back as a string. There is no DECRYBYFLOAT but providing a\n   * negative value will work as expected.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(byte[])\n   * @see Jedis#decr(byte[])\n   * @see Jedis#decrBy(byte[], long)\n   * @param key the key to increment\n   * @param increment the value to increment by\n   * @return The value of key after the increment\n   */\n  @Override\n  public double incrByFloat(final byte[] key, final double increment) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.incrByFloat(key, increment));\n  }\n\n  /**\n   * Increment the number stored at key by one. If the key does not exist or contains a value of a\n   * wrong type, set the key to the value of \"0\" before to perform the increment operation.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incrBy(byte[], long)\n   * @see Jedis#decr(byte[])\n   * @see Jedis#decrBy(byte[], long)\n   * @param key\n   * @return The value of key after the increment\n   */\n  @Override\n  public long incr(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.incr(key));\n  }\n\n  /**\n   * If the key already exists and is a string, this command appends the provided value at the end\n   * of the string. If the key does not exist it is created and set as an empty string, so APPEND\n   * will be very similar to SET in this special case.\n   * <p>\n   * Time complexity: O(1). The amortized time complexity is O(1) assuming the appended value is\n   * small and the already present value is of any size, since the dynamic string library used by\n   * Redis will double the free space available on every reallocation.\n   * @param key\n   * @param value\n   * @return The total length of the string after the append operation\n   */\n  @Override\n  public long append(final byte[] key, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.append(key, value));\n  }\n\n  /**\n   * Return a subset of the string from offset start to offset end (both offsets are inclusive).\n   * Negative offsets can be used in order to provide an offset starting from the end of the string.\n   * So -1 means the last char, -2 the penultimate and so forth.\n   * <p>\n   * The function handles out of range requests without raising an error, but just limiting the\n   * resulting range to the actual length of the string.\n   * <p>\n   * Time complexity: O(start+n) (with start being the start index and n the total length of the\n   * requested range). Note that the lookup part of this command is O(1) so for small strings this\n   * is actually an O(1) command.\n   * @param key\n   * @param start\n   * @param end\n   * @return Bulk reply\n   * @deprecated Use {@link Jedis#getrange(byte[], long, long)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  @Override\n  public byte[] substr(final byte[] key, final int start, final int end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.substr(key, start, end));\n  }\n\n  /**\n   * Set the specified hash field to the specified value.\n   * <p>\n   * If key does not exist, a new key holding a hash is created.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return If the field already exists, and the HSET just produced an update of the value, 0 is\n   *         returned, otherwise if a new field is created 1 is returned.\n   */\n  @Override\n  public long hset(final byte[] key, final byte[] field, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hset(key, field, value));\n  }\n\n  @Override\n  public long hset(final byte[] key, final Map<byte[], byte[]> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hset(key, hash));\n  }\n\n  @Override\n  public long hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hsetex(key, params, field, value));\n  }\n\n  @Override\n  public long hsetex(byte[] key, HSetExParams params, Map<byte[], byte[]> hash){\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hsetex(key, params, hash));\n  }\n\n  /**\n   * If key holds a hash, retrieve the value associated to the specified field.\n   * <p>\n   * If the field is not found or the key does not exist, a special 'nil' value is returned.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @return Bulk reply\n   */\n  @Override\n  public byte[] hget(final byte[] key, final byte[] field) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hget(key, field));\n  }\n\n  @Override\n  public List<byte[]> hgetex(byte[] key, HGetExParams params, byte[]... fields){\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hgetex(key, params, fields));\n  }\n\n  @Override\n  public List<byte[]> hgetdel(byte[] key, byte[]... fields){\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hgetdel(key, fields));\n  }\n\n  /**\n   * Set the specified hash field to the specified value if the field not exists. <b>Time\n   * complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return If the field already exists, 0 is returned, otherwise if a new field is created 1 is\n   *         returned.\n   */\n  @Override\n  public long hsetnx(final byte[] key, final byte[] field, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hsetnx(key, field, value));\n  }\n\n  /**\n   * Set the respective fields to the respective values. HMSET replaces old values with new values.\n   * <p>\n   * If key does not exist, a new key holding a hash is created.\n   * <p>\n   * <b>Time complexity:</b> O(N) (with N being the number of fields)\n   * @param key\n   * @param hash\n   * @return OK\n   * @deprecated Use {@link Jedis#hset(byte[], Map)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  @Override\n  public String hmset(final byte[] key, final Map<byte[], byte[]> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hmset(key, hash));\n  }\n\n  /**\n   * Retrieve the values associated to the specified fields.\n   * <p>\n   * If some of the specified fields do not exist, nil values are returned. Non existing keys are\n   * considered like empty hashes.\n   * <p>\n   * <b>Time complexity:</b> O(N) (with N being the number of fields)\n   * @param key\n   * @param fields\n   * @return A list of all the values associated with the specified fields, in the same order of the request\n   */\n  @Override\n  public List<byte[]> hmget(final byte[] key, final byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hmget(key, fields));\n  }\n\n  /**\n   * Increment the number stored at field in the hash at key by value. If key does not exist, a new\n   * key holding a hash is created. If field does not exist or holds a string, the value is set to 0\n   * before applying the operation. Since the value argument is signed you can use this command to\n   * perform both increments and decrements.\n   * <p>\n   * The range of values supported by HINCRBY is limited to 64-bit signed integers.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return The value of key after the increment\n   */\n  @Override\n  public long hincrBy(final byte[] key, final byte[] field, final long value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hincrBy(key, field, value));\n  }\n\n  /**\n   * Increment the number stored at field in the hash at key by a double precision floating point\n   * value. If key does not exist, a new key holding a hash is created. If field does not exist or\n   * holds a string, the value is set to 0 before applying the operation. Since the value argument\n   * is signed you can use this command to perform both increments and decrements.\n   * <p>\n   * The range of values supported by HINCRBYFLOAT is limited to double precision floating point\n   * values.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return The new value at field after the increment operation\n   */\n  @Override\n  public double hincrByFloat(final byte[] key, final byte[] field, final double value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hincrByFloat(key, field, value));\n  }\n\n  /**\n   * Test for existence of a specified field in a hash. <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @return {@code true} if the hash stored at key contains the specified field, {@code false} if the key is\n   *         not found or the field is not present.\n   */\n  @Override\n  public boolean hexists(final byte[] key, final byte[] field) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexists(key, field));\n  }\n\n  /**\n   * Remove the specified field from an hash stored at key.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param fields\n   * @return If the field was present in the hash it is deleted and 1 is returned, otherwise 0 is\n   *         returned and no operation is performed.\n   */\n  @Override\n  public long hdel(final byte[] key, final byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hdel(key, fields));\n  }\n\n  /**\n   * Return the number of items in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @return The number of entries (fields) contained in the hash stored at key. If the specified\n   *         key does not exist, 0 is returned assuming an empty hash.\n   */\n  @Override\n  public long hlen(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hlen(key));\n  }\n\n  /**\n   * Return all the fields in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the total number of entries\n   * @param key\n   * @return All the fields names contained into a hash.\n   */\n  @Override\n  public Set<byte[]> hkeys(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hkeys(key));\n  }\n\n  /**\n   * Return all the values in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the total number of entries\n   * @param key\n   * @return All the fields values contained into a hash.\n   */\n  @Override\n  public List<byte[]> hvals(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hvals(key));\n  }\n\n  /**\n   * Return all the fields and associated values in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the total number of entries\n   * @param key\n   * @return All the fields and values contained into a hash.\n   */\n  @Override\n  public Map<byte[], byte[]> hgetAll(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hgetAll(key));\n  }\n\n  /**\n   * Get one random field from a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the number of fields returned\n   * @param key\n   * @return one random field from a hash.\n   */\n  @Override\n  public byte[] hrandfield(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hrandfield(key));\n  }\n\n  /**\n   * Get multiple random fields from a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the number of fields returned\n   * @param key\n   * @return Multiple random fields from a hash.\n   */\n  @Override\n  public List<byte[]> hrandfield(final byte[] key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hrandfield(key, count));\n  }\n\n  /**\n   * Get one or multiple random fields with values from a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the number of fields returned\n   * @param key\n   * @return One or multiple random fields with values from a hash.\n   */\n  @Override\n  public List<Map.Entry<byte[], byte[]>> hrandfieldWithValues(final byte[] key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hrandfieldWithValues(key, count));\n  }\n\n  /**\n   * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key\n   * does not exist an empty list is created just before the append operation. If the key exists but\n   * is not a List an error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param strings\n   * @return The number of elements inside the list after the push operation\n   */\n  @Override\n  public long rpush(final byte[] key, final byte[]... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpush(key, strings));\n  }\n\n  /**\n   * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key\n   * does not exist an empty list is created just before the append operation. If the key exists but\n   * is not a List an error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param strings\n   * @return The number of elements inside the list after the push operation\n   */\n  @Override\n  public long lpush(final byte[] key, final byte[]... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpush(key, strings));\n  }\n\n  /**\n   * Return the length of the list stored at the specified key. If the key does not exist zero is\n   * returned (the same behaviour as for empty lists). If the value stored at key is not a list an\n   * error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The length of the list\n   */\n  @Override\n  public long llen(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.llen(key));\n  }\n\n  /**\n   * Return the specified elements of the list stored at the specified key. Start and end are\n   * zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and\n   * so on.\n   * <p>\n   * For example LRANGE foobar 0 2 will return the first three elements of the list.\n   * <p>\n   * start and end can also be negative numbers indicating offsets from the end of the list. For\n   * example -1 is the last element of the list, -2 the penultimate element and so on.\n   * <p>\n   * <b>Consistency with range functions in various programming languages</b>\n   * <p>\n   * Note that if you have a list of numbers from 0 to 100, LRANGE 0 10 will return 11 elements,\n   * that is, rightmost item is included. This may or may not be consistent with behavior of\n   * range-related functions in your programming language of choice (think Ruby's Range.new,\n   * Array#slice or Python's range() function).\n   * <p>\n   * LRANGE behavior is consistent with one of Tcl.\n   * <p>\n   * <b>Out-of-range indexes</b>\n   * <p>\n   * Indexes out of range will not produce an error: if start is over the end of the list, or start\n   * &gt; end, an empty list is returned. If end is over the end of the list Redis will threat it\n   * just like the last element of the list.\n   * <p>\n   * Time complexity: O(start+n) (with n being the length of the range and start being the start\n   * offset)\n   * @param key\n   * @param start\n   * @param stop\n   * @return A list of elements in the specified range\n   */\n  @Override\n  public List<byte[]> lrange(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lrange(key, start, stop));\n  }\n\n  /**\n   * Trim an existing list so that it will contain only the specified range of elements specified.\n   * Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the\n   * next element and so on.\n   * <p>\n   * For example LTRIM foobar 0 2 will modify the list stored at foobar key so that only the first\n   * three elements of the list will remain.\n   * <p>\n   * start and end can also be negative numbers indicating offsets from the end of the list. For\n   * example -1 is the last element of the list, -2 the penultimate element and so on.\n   * <p>\n   * Indexes out of range will not produce an error: if start is over the end of the list, or start\n   * &gt; end, an empty list is left as value. If end over the end of the list Redis will threat it\n   * just like the last element of the list.\n   * <p>\n   * Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:\n   * <p>\n   * {@code lpush(\"mylist\", \"someelement\"); ltrim(\"mylist\", 0, 99); * }\n   * <p>\n   * The above two commands will push elements in the list taking care that the list will not grow\n   * without limits. This is very useful when using Redis to store logs for example. It is important\n   * to note that when used in this way LTRIM is an O(1) operation because in the average case just\n   * one element is removed from the tail of the list.\n   * <p>\n   * Time complexity: O(n) (with n being len of list - len of range)\n   * @param key\n   * @param start\n   * @param stop\n   * @return OK\n   */\n  @Override\n  public String ltrim(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.ltrim(key, start, stop));\n  }\n\n  /**\n   * Return the specified element of the list stored at the specified key. 0 is the first element, 1\n   * the second and so on. Negative indexes are supported, for example -1 is the last element, -2\n   * the penultimate and so on.\n   * <p>\n   * If the value stored at key is not of list type an error is returned. If the index is out of\n   * range a 'nil' reply is returned.\n   * <p>\n   * Note that even if the average time complexity is O(n) asking for the first or the last element\n   * of the list is O(1).\n   * <p>\n   * Time complexity: O(n) (with n being the length of the list)\n   * @param key\n   * @param index\n   * @return The requested element\n   */\n  @Override\n  public byte[] lindex(final byte[] key, final long index) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lindex(key, index));\n  }\n\n  /**\n   * Set a new value as the element at index position of the List at key.\n   * <p>\n   * Out of range indexes will generate an error.\n   * <p>\n   * Similarly to other list commands accepting indexes, the index can be negative to access\n   * elements starting from the end of the list. So -1 is the last element, -2 is the penultimate,\n   * and so forth.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(N) (with N being the length of the list), setting the first or last elements of the list is\n   * O(1).\n   * @see Jedis#lindex(byte[], long)\n   * @param key\n   * @param index\n   * @param value\n   * @return OK\n   */\n  @Override\n  public String lset(final byte[] key, final long index, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lset(key, index, value));\n  }\n\n  /**\n   * Remove the first count occurrences of the value element from the list. If count is zero all the\n   * elements are removed. If count is negative elements are removed from tail to head, instead to\n   * go from head to tail that is the normal behaviour. So for example LREM with count -2 and hello\n   * as value to remove against the list (a,b,c,hello,x,hello,hello) will leave the list\n   * (a,b,c,hello,x). The number of removed elements is returned as an integer, see below for more\n   * information about the returned value. Note that non existing keys are considered like empty\n   * lists by LREM, so LREM against non existing keys will always return 0.\n   * <p>\n   * Time complexity: O(N) (with N being the length of the list)\n   * @param key\n   * @param count\n   * @param value\n   * @return The number of removed elements if the operation succeeded\n   */\n  @Override\n  public long lrem(final byte[] key, final long count, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lrem(key, count, value));\n  }\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned.\n   * @see Jedis#rpop(byte[])\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public byte[] lpop(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpop(key));\n  }\n\n  @Override\n  public List<byte[]> lpop(final byte[] key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpop(key, count));\n  }\n\n  /**\n   * Returns the index of the first matching element inside a redis list. If the element is found,\n   * its index (the zero-based position in the list) is returned. Otherwise, if no match is found,\n   * 'nil' is returned.\n   * <p>\n   * Time complexity: O(N) where N is the number of elements in the list\n   * @see Jedis#lpos(byte[], byte[])\n   * @param key\n   * @param element\n   * @return The index of first matching element in the list. Value will\n   * be 'nil' when the element is not present in the list.\n   */\n  @Override\n  public Long lpos(final byte[] key, final byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpos(key, element));\n  }\n\n  /**\n   * In case there are multiple matches Rank option specifies the \"rank\" of the element to return.\n   * A rank of 1 returns the first match, 2 to return the second match, and so forth.\n   * If list `foo` has elements (\"a\",\"b\",\"c\",\"1\",\"2\",\"3\",\"c\",\"c\"), The function call to get the\n   * index of second occurrence of \"c\" will be as follows lpos(\"foo\",\"c\", LPosParams.lPosParams().rank(2)).\n   * <p>\n   * Maxlen option compares the element provided only with a given maximum number of list items.\n   * A value of 1000 will make sure that the command performs only 1000 comparisons. The\n   * comparison is made for the first part or the last part depending on the fact we use a positive or\n   * negative rank.\n   * Following is how we could use the Maxlen option lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(1).maxlen(2)).\n   * @see Jedis#lpos(byte[], byte[], LPosParams)\n   * @param key\n   * @param element\n   * @param params\n   * @return The index of first matching element in the list. Value will be 'nil' when the element\n   * is not present in the list\n   */\n  @Override\n  public Long lpos(final byte[] key, final byte[] element, final LPosParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpos(key, element, params));\n  }\n\n  /**\n   * Count will return list of position of all the first N matching elements. It is possible to\n   * specify 0 as the number of matches, as a way to tell the command we want all the matches\n   * found returned as an array of indexes. When count is used and no match is found, an empty list\n   * is returned.\n   * <p>\n   * Time complexity: O(N) where N is the number of elements in the list\n   * @see Jedis#lpos(byte[], byte[], LPosParams, long)\n   * @param key\n   * @param element\n   * @param params\n   * @param count\n   * @return A list containing position of the matching elements inside the list\n   */\n  @Override\n  public List<Long> lpos(final byte[] key, final byte[] element, final LPosParams params,\n      final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpos(key, element, params, count));\n  }\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned.\n   * @see Jedis#lpop(byte[])\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public byte[] rpop(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpop(key));\n  }\n\n  @Override\n  public List<byte[]> rpop(final byte[] key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpop(key, count));\n  }\n\n  /**\n   * Atomically return and remove the last (tail) element of the srckey list, and push the element\n   * as the first (head) element of the dstkey list. For example if the source list contains the\n   * elements \"a\",\"b\",\"c\" and the destination list contains the elements \"foo\",\"bar\" after an\n   * RPOPLPUSH command the content of the two lists will be \"a\",\"b\" and \"c\",\"foo\",\"bar\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned. If\n   * the srckey and dstkey are the same the operation is equivalent to removing the last element\n   * from the list and pushing it as first element of the list, so it's a \"list rotation\" command.\n   * <p>\n   * Time complexity: O(1)\n   * @param srckey\n   * @param dstkey\n   * @return Bulk reply\n   * @deprecated Use {@link Jedis#lmove(byte[], byte[], ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public byte[] rpoplpush(final byte[] srckey, final byte[] dstkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpoplpush(srckey, dstkey));\n  }\n\n  /**\n   * Add the specified member to the set value stored at key. If member is already a member of the\n   * set no operation is performed. If key does not exist a new set with the specified member as\n   * sole member is created. If the key exists but does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param members\n   * @return The number of elements that were added to the set, not including all the elements already\n   * present in the set\n   */\n  @Override\n  public long sadd(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sadd(key, members));\n  }\n\n  /**\n   * Return all the members (elements) of the set value stored at key. This is just syntax glue for\n   * {@link Jedis#sinter(byte[][])} SINTER}.\n   * <p>\n   * Time complexity O(N)\n   * @param key the key of the set\n   * @return All elements of the set\n   */\n  @Override\n  public Set<byte[]> smembers(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.smembers(key));\n  }\n\n  /**\n   * Remove the specified member from the set value stored at key. If member was not a member of the\n   * set no operation is performed. If key does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key the key of the set\n   * @param members the set member to remove\n   * @return The number of members that were removed from the set, not including non-existing members\n   */\n  @Override\n  public long srem(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.srem(key, members));\n  }\n\n  /**\n   * Remove a random element from a Set returning it as return value. If the Set is empty or the key\n   * does not exist, a nil object is returned.\n   * <p>\n   * The {@link Jedis#srandmember(byte[])} command does a similar work but the returned element is\n   * not removed from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The removed member, or nil when key does not exist\n   */\n  @Override\n  public byte[] spop(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.spop(key));\n  }\n\n  @Override\n  public Set<byte[]> spop(final byte[] key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.spop(key, count));\n  }\n\n  /**\n   * Move the specified member from the set at srckey to the set at dstkey. This operation is\n   * atomic, in every given moment the element will appear to be in the source or destination set\n   * for accessing clients.\n   * <p>\n   * If the source set does not exist or does not contain the specified element no operation is\n   * performed and zero is returned, otherwise the element is removed from the source set and added\n   * to the destination set. On success one is returned, even if the element was already present in\n   * the destination set.\n   * <p>\n   * An error is raised if the source or destination keys contain a non Set value.\n   * <p>\n   * Time complexity O(1)\n   * @param srckey\n   * @param dstkey\n   * @param member\n   * @return 1 if the element was moved, 0 if no operation was performed\n   */\n  @Override\n  public long smove(final byte[] srckey, final byte[] dstkey, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.smove(srckey, dstkey, member));\n  }\n\n  /**\n   * Return the set cardinality (number of elements). If the key does not exist 0 is returned, like\n   * for empty sets.\n   * @param key\n   * @return The cardinality (number of elements) of the set\n   */\n  @Override\n  public long scard(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.scard(key));\n  }\n\n  /**\n   * Return true if member is a member of the set stored at key, otherwise false is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param member\n   * @return {@code true} if the element is a member of the set, {@code false} otherwise\n   */\n  @Override\n  public boolean sismember(final byte[] key, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sismember(key, member));\n  }\n\n  /**\n   * Returns whether each member is a member of the set stored at key.\n   * <p>\n   * Time complexity O(N) where N is the number of elements being checked for membership\n   * @param key\n   * @param members\n   * @return List representing the membership of the given elements, in the same order as they are requested\n   */\n  @Override\n  public List<Boolean> smismember(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.smismember(key, members));\n  }\n\n  /**\n   * Return the members of a set resulting from the intersection of all the sets hold at the\n   * specified keys. Like in {@link Jedis#lrange(byte[], long, long)} LRANGE} the result is sent to\n   * the connection as a multi-bulk reply (see the protocol specification for more information). If\n   * just a single key is specified, then this command produces the same result as\n   * {@link Jedis#smembers(byte[]) SMEMBERS}. Actually SMEMBERS is just syntax sugar for SINTER.\n   * <p>\n   * Non existing keys are considered like empty sets, so if one of the keys is missing an empty set\n   * is returned (since the intersection with an empty set always is an empty set).\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the\n   * number of sets\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public Set<byte[]> sinter(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sinter(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sinter(byte[][]) SINTER} but instead of being\n   * returned the resulting set is stored as dstkey.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the\n   * number of sets\n   * @param dstkey\n   * @param keys\n   * @return The number of elements in the resulting set\n   */\n  @Override\n  public long sinterstore(final byte[] dstkey, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sinterstore(dstkey, keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sinter(byte[][]) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result. LIMIT defaults to 0 and means unlimited\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param keys\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  @Override\n  public long sintercard(byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sintercard(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sinter(byte[][]) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param limit If the intersection cardinality reaches limit partway through the computation,\n   *              the algorithm will exit and yield limit as the cardinality.\n   * @param keys\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  @Override\n  public long sintercard(int limit, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sintercard(limit, keys));\n  }\n\n  /**\n   * Return the members of a set resulting from the union of all the sets hold at the specified\n   * keys. Like in {@link Jedis#lrange(byte[], long, long)} LRANGE} the result is sent to the\n   * connection as a multi-bulk reply (see the protocol specification for more information). If just\n   * a single key is specified, then this command produces the same result as\n   * {@link Jedis#smembers(byte[]) SMEMBERS}.\n   * <p>\n   * Non existing keys are considered like empty sets.\n   * <p>\n   * Time complexity O(N) where N is the total number of elements in all the provided sets\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public Set<byte[]> sunion(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sunion(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sunion(byte[][]) SUNION} but instead of being\n   * returned the resulting set is stored as dstkey. Any existing value in dstkey will be\n   * over-written.\n   * <p>\n   * Time complexity O(N) where N is the total number of elements in all the provided sets\n   * @param dstkey\n   * @param keys\n   * @return The number of elements in the resulting set\n   */\n  @Override\n  public long sunionstore(final byte[] dstkey, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sunionstore(dstkey, keys));\n  }\n\n  /**\n   * Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN\n   * <p>\n   * <b>Example:</b>\n   *\n   * <pre>\n   * key1 = [x, a, b, c]\n   * key2 = [c]\n   * key3 = [a, d]\n   * SDIFF key1,key2,key3 =&gt; [x, b]\n   * </pre>\n   *\n   * Non existing keys are considered like empty sets.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(N) with N being the total number of elements of all the sets\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public Set<byte[]> sdiff(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sdiff(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sdiff(byte[][]) SDIFF} but instead of being returned\n   * the resulting set is stored in dstkey.\n   * @param dstkey\n   * @param keys\n   * @return The number of elements in the resulting set\n   */\n  @Override\n  public long sdiffstore(final byte[] dstkey, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sdiffstore(dstkey, keys));\n  }\n\n  /**\n   * Return a random element from a Set, without removing the element. If the Set is empty or the\n   * key does not exist, a nil object is returned.\n   * <p>\n   * The SPOP command does a similar work but the returned element is popped (removed) from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The randomly selected element\n   */\n  @Override\n  public byte[] srandmember(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.srandmember(key));\n  }\n\n  @Override\n  public List<byte[]> srandmember(final byte[] key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.srandmember(key, count));\n  }\n\n  /**\n   * Add the specified member having the specified score to the sorted set stored at key. If member\n   * is already a member of the sorted set the score is updated, and the element reinserted in the\n   * right position to ensure sorting. If key does not exist a new sorted set with the specified\n   * member as sole member is created. If the key exists but does not hold a sorted set value an\n   * error is returned.\n   * <p>\n   * The score value can be the string representation of a double precision floating point number.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param score\n   * @param member\n   * @return 1 if the new element was added, 0 if the element was already a member of the sorted\n   * set and the score was updated\n   */\n  @Override\n  public long zadd(final byte[] key, final double score, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, score, member));\n  }\n\n  @Override\n  public long zadd(final byte[] key, final double score, final byte[] member,\n      final ZAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, score, member, params));\n  }\n\n  @Override\n  public long zadd(final byte[] key, final Map<byte[], Double> scoreMembers) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, scoreMembers));\n  }\n\n  @Override\n  public long zadd(final byte[] key, final Map<byte[], Double> scoreMembers, final ZAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, scoreMembers, params));\n  }\n\n  @Override\n  public Double zaddIncr(final byte[] key, final double score, final byte[] member, final ZAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zaddIncr(key, score, member, params));\n  }\n\n  @Override\n  public List<byte[]> zrange(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrange(key, start, stop));\n  }\n\n  /**\n   * Remove the specified member from the sorted set value stored at key. If member was not a member\n   * of the set no operation is performed. If key does not not hold a set value an error is\n   * returned.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param members\n   * @return 1 if the new element was removed, 0 if the new element was not a member of the set\n   */\n  @Override\n  public long zrem(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrem(key, members));\n  }\n\n  /**\n   * If member already exists in the sorted set adds the increment to its score and updates the\n   * position of the element in the sorted set accordingly. If member does not already exist in the\n   * sorted set it is added with increment as score (that is, like if the previous score was\n   * virtually zero). If key does not exist a new sorted set with the specified member as sole\n   * member is created. If the key exists but does not hold a sorted set value an error is returned.\n   * <p>\n   * The score value can be the string representation of a double precision floating point number.\n   * It's possible to provide a negative value to perform a decrement.\n   * <p>\n   * For an introduction to sorted sets check the Introduction to Redis data types page.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param increment\n   * @param member\n   * @return The new score\n   */\n  @Override\n  public double zincrby(final byte[] key, final double increment, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zincrby(key, increment, member));\n  }\n\n  @Override\n  public Double zincrby(final byte[] key, final double increment, final byte[] member,\n      final ZIncrByParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zincrby(key, increment, member, params));\n  }\n\n  /**\n   * Return the rank (or index) or member in the sorted set at key, with scores being ordered from\n   * low to high.\n   * <p>\n   * When the given member does not exist in the sorted set, the special value 'nil' is returned.\n   * The returned rank (or index) of the member is 0-based for both commands.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))\n   * @see Jedis#zrevrank(byte[], byte[])\n   * @param key\n   * @param member\n   * @return The element as an integer if the element exists. A 'nil' bulk reply if there is no such element\n   */\n  @Override\n  public Long zrank(final byte[] key, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrank(key, member));\n  }\n\n  /**\n   * Return the rank (or index) or member in the sorted set at key, with scores being ordered from\n   * high to low.\n   * <p>\n   * When the given member does not exist in the sorted set, the special value 'nil' is returned.\n   * The returned rank (or index) of the member is 0-based for both commands.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))\n   * @see Jedis#zrank(byte[], byte[])\n   * @param key\n   * @param member\n   * @return The element as an integer if the element exists. A 'nil' bulk reply if there is no such element.\n   */\n  @Override\n  public Long zrevrank(final byte[] key, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrank(key, member));\n  }\n\n  /**\n   * Returns the rank and the score of member in the sorted set stored at key, with the scores\n   * ordered from low to high.\n   * @param key the key\n   * @param member the member\n   * @return the KeyValue contains rank and score.\n   */\n  @Override\n  public KeyValue<Long, Double> zrankWithScore(byte[] key, byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrankWithScore(key, member));\n  }\n\n  /**\n   * Returns the rank and the score of member in the sorted set stored at key, with the scores\n   * ordered from high to low.\n   * @param key the key\n   * @param member the member\n   * @return the KeyValue contains rank and score.\n   */\n  @Override\n  public KeyValue<Long, Double> zrevrankWithScore(byte[] key, byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrankWithScore(key, member));\n  }\n\n  @Override\n  public List<byte[]> zrevrange(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrange(key, start, stop));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public List<Tuple> zrevrangeWithScores(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public List<byte[]> zrange(byte[] key, ZRangeParams zRangeParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrange(key, zRangeParams));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(byte[] key, ZRangeParams zRangeParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeWithScores(key, zRangeParams));\n  }\n\n  @Override\n  public long zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangestore(dest, src, zRangeParams));\n  }\n\n  @Override\n  public byte[] zrandmember(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrandmember(key));\n  }\n\n  @Override\n  public List<byte[]> zrandmember(final byte[] key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrandmember(key, count));\n  }\n\n  @Override\n  public List<Tuple> zrandmemberWithScores(final byte[] key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrandmemberWithScores(key, count));\n  }\n\n  /**\n   * Return the sorted set cardinality (number of elements). If the key does not exist 0 is\n   * returned, like for empty sorted sets.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The cardinality (number of elements) of the set as an integer.\n   */\n  @Override\n  public long zcard(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zcard(key));\n  }\n\n  /**\n   * Return the score of the specified element of the sorted set at key. If the specified element\n   * does not exist in the sorted set, or the key does not exist at all, a special 'nil' value is\n   * returned.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param member\n   * @return The score\n   */\n  @Override\n  public Double zscore(final byte[] key, final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zscore(key, member));\n  }\n\n  /**\n   * Returns the scores associated with the specified members in the sorted set stored at key.\n   * For every member that does not exist in the sorted set, a nil value is returned.\n   * <p>\n   * <b>Time complexity:</b> O(N) where N is the number of members being requested.\n   * @param key\n   * @param members\n   * @return The scores\n   */\n  @Override\n  public List<Double> zmscore(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zmscore(key, members));\n  }\n\n  @Override\n  public Tuple zpopmax(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmax(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmax(final byte[] key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmax(key, count));\n  }\n\n  @Override\n  public Tuple zpopmin(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmin(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmin(final byte[] key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmin(key, count));\n  }\n\n  public String watch(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(WATCH, keys);\n//    return connection.getStatusCodeReply();\n    String status = connection.getStatusCodeReply();\n    isInWatch = true;\n    return status;\n  }\n\n  public String unwatch() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(UNWATCH);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Sort a Set or a List.\n   * <p>\n   * Sort the elements contained in the List, Set, or Sorted Set value at key. By default sorting is\n   * numeric with elements being compared as double precision floating point numbers. This is the\n   * simplest form of SORT.\n   * @see Jedis#sort(byte[], byte[])\n   * @see Jedis#sort(byte[], SortingParams)\n   * @see Jedis#sort(byte[], SortingParams, byte[])\n   * @param key\n   * @return Assuming the Set/List at key contains a list of numbers, the return value will be the\n   *         list of numbers ordered from the smallest to the biggest number.\n   */\n  @Override\n  public List<byte[]> sort(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key));\n  }\n\n  /**\n   * Sort a Set or a List accordingly to the specified parameters.\n   * <p>\n   * <b>examples:</b>\n   * <p>\n   * Given are the following sets and key/values:\n   *\n   * <pre>\n   * x = [1, 2, 3]\n   * y = [a, b, c]\n   *\n   * k1 = z\n   * k2 = y\n   * k3 = x\n   *\n   * w1 = 9\n   * w2 = 8\n   * w3 = 7\n   * </pre>\n   *\n   * Sort Order:\n   *\n   * <pre>\n   * sort(x) or sort(x, sp.asc())\n   * -&gt; [1, 2, 3]\n   *\n   * sort(x, sp.desc())\n   * -&gt; [3, 2, 1]\n   *\n   * sort(y)\n   * -&gt; [c, a, b]\n   *\n   * sort(y, sp.alpha())\n   * -&gt; [a, b, c]\n   *\n   * sort(y, sp.alpha().desc())\n   * -&gt; [c, a, b]\n   * </pre>\n   *\n   * Limit (e.g. for Pagination):\n   *\n   * <pre>\n   * sort(x, sp.limit(0, 2))\n   * -&gt; [1, 2]\n   *\n   * sort(y, sp.alpha().desc().limit(1, 2))\n   * -&gt; [b, a]\n   * </pre>\n   *\n   * Sorting by external keys:\n   *\n   * <pre>\n   * sort(x, sb.by(w*))\n   * -&gt; [3, 2, 1]\n   *\n   * sort(x, sb.by(w*).desc())\n   * -&gt; [1, 2, 3]\n   * </pre>\n   *\n   * Getting external keys:\n   *\n   * <pre>\n   * sort(x, sp.by(w*).get(k*))\n   * -&gt; [x, y, z]\n   *\n   * sort(x, sp.by(w*).get(#).get(k*))\n   * -&gt; [3, x, 2, y, 1, z]\n   * </pre>\n   * @see Jedis#sort(byte[])\n   * @see Jedis#sort(byte[], SortingParams, byte[])\n   * @param key\n   * @param sortingParams\n   * @return a list of sorted elements.\n   */\n  @Override\n  public List<byte[]> sort(final byte[] key, final SortingParams sortingParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key, sortingParams));\n  }\n\n  /**\n   * Sort a Set or a List accordingly to the specified parameters and store the result at dstkey.\n   * @see Jedis#sort(byte[], SortingParams)\n   * @see Jedis#sort(byte[])\n   * @see Jedis#sort(byte[], byte[])\n   * @param key\n   * @param sortingParams\n   * @param dstkey\n   * @return The number of elements of the list at dstkey.\n   */\n  @Override\n  public long sort(final byte[] key, final SortingParams sortingParams, final byte[] dstkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key, sortingParams, dstkey));\n  }\n\n  /**\n   * Sort a Set or a List and Store the Result at dstkey.\n   * <p>\n   * Sort the elements contained in the List, Set, or Sorted Set value at key and store the result\n   * at dstkey. By default sorting is numeric with elements being compared as double precision\n   * floating point numbers. This is the simplest form of SORT.\n   * @see Jedis#sort(byte[])\n   * @see Jedis#sort(byte[], SortingParams)\n   * @see Jedis#sort(byte[], SortingParams, byte[])\n   * @param key\n   * @param dstkey\n   * @return The number of elements of the list at dstkey.\n   */\n  @Override\n  public long sort(final byte[] key, final byte[] dstkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key, dstkey));\n  }\n\n  @Override\n  public List<byte[]> sortReadonly(byte[] key, SortingParams sortingParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sortReadonly(key, sortingParams));\n  }\n\n  /**\n   * Pop an element from a list, push it to another list and return it\n   * @param srcKey\n   * @param dstKey\n   * @param from\n   * @param to\n   * @return The element being popped and pushed\n   */\n  @Override\n  public byte[] lmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lmove(srcKey, dstKey, from, to));\n  }\n\n  /**\n   * Pop an element from a list, push it to another list and return it; or block until one is available\n   * @param srcKey\n   * @param dstKey\n   * @param from\n   * @param to\n   * @param timeout\n   * @return The element being popped and pushed\n   */\n  @Override\n  public byte[] blmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to, double timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blmove(srcKey, dstKey, from, to, timeout));\n  }\n\n  /**\n   * BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commands as blocking\n   * versions of LPOP and RPOP able to block if the specified keys don't exist or contain empty\n   * lists.\n   * <p>\n   * The following is a description of the exact semantic. We describe BLPOP but the two commands\n   * are identical, the only difference is that BLPOP pops the element from the left (head) of the\n   * list, and BRPOP pops from the right (tail).\n   * <p>\n   * <b>Non blocking behavior</b>\n   * <p>\n   * When BLPOP is called, if at least one of the specified keys contain a non empty list, an\n   * element is popped from the head of the list and returned to the caller together with the name\n   * of the key (BLPOP returns a two elements array, the first element is the key, the second the\n   * popped value).\n   * <p>\n   * Keys are scanned from left to right, so for instance if you issue BLPOP list1 list2 list3 0\n   * against a dataset where list1 does not exist but list2 and list3 contain non empty lists, BLPOP\n   * guarantees to return an element from the list stored at list2 (since it is the first non empty\n   * list starting from the left).\n   * <p>\n   * <b>Blocking behavior</b>\n   * <p>\n   * If none of the specified keys exist or contain non empty lists, BLPOP blocks until some other\n   * connection performs a LPUSH or an RPUSH operation against one of the lists.\n   * <p>\n   * Once new data is present on one of the lists, the connection finally returns with the name of the\n   * key unblocking it and the popped value.\n   * <p>\n   * When blocking, if a non-zero timeout is specified, the connection will unblock returning a nil\n   * special value if the specified amount of seconds passed without a push operation against at\n   * least one of the specified keys.\n   * <p>\n   * The timeout argument is interpreted as an integer value. A timeout of zero means instead to\n   * block forever.\n   * <p>\n   * <b>Multiple clients blocking for the same keys</b>\n   * <p>\n   * Multiple clients can block for the same key. They are put into a queue, so the first to be\n   * served will be the one that started to wait earlier, in a first-blpopping first-served fashion.\n   * <p>\n   * <b>blocking POP inside a MULTI/EXEC transaction</b>\n   * <p>\n   * BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies\n   * in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis\n   * transaction).\n   * <p>\n   * The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil\n   * reply, exactly what happens when the timeout is reached. If you like science fiction, think at\n   * it like if inside MULTI/EXEC the time will flow at infinite speed :)\n   * <p>\n   * Time complexity: O(1)\n   * @param timeout\n   * @param keys\n   * @return BLPOP returns a two-elements array via a multi bulk reply in order to return both the\n   *         unblocking key and the popped value.\n   *         <p>\n         When a non-zero timeout is specified, and the BLPOP operation timed out, the return\n         value is a nil multi bulk reply. Most connection values will return false or nil\n         accordingly to the programming language used.\n   */\n  @Override\n  public List<byte[]> blpop(final int timeout, final byte[]... keys) {\n    return connection.executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], byte[]> blpop(final double timeout, final byte[]... keys) {\n    return connection.executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  /**\n   * BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commands as blocking\n   * versions of LPOP and RPOP able to block if the specified keys don't exist or contain empty\n   * lists.\n   * <p>\n   * The following is a description of the exact semantic. We describe BLPOP but the two commands\n   * are identical, the only difference is that BLPOP pops the element from the left (head) of the\n   * list, and BRPOP pops from the right (tail).\n   * <p>\n   * <b>Non blocking behavior</b>\n   * <p>\n   * When BLPOP is called, if at least one of the specified keys contain a non empty list, an\n   * element is popped from the head of the list and returned to the caller together with the name\n   * of the key (BLPOP returns a two elements array, the first element is the key, the second the\n   * popped value).\n   * <p>\n   * Keys are scanned from left to right, so for instance if you issue BLPOP list1 list2 list3 0\n   * against a dataset where list1 does not exist but list2 and list3 contain non empty lists, BLPOP\n   * guarantees to return an element from the list stored at list2 (since it is the first non empty\n   * list starting from the left).\n   * <p>\n   * <b>Blocking behavior</b>\n   * <p>\n   * If none of the specified keys exist or contain non empty lists, BLPOP blocks until some other\n   * connection performs a LPUSH or an RPUSH operation against one of the lists.\n   * <p>\n   * Once new data is present on one of the lists, the connection finally returns with the name of the\n   * key unblocking it and the popped value.\n   * <p>\n   * When blocking, if a non-zero timeout is specified, the connection will unblock returning a nil\n   * special value if the specified amount of seconds passed without a push operation against at\n   * least one of the specified keys.\n   * <p>\n   * The timeout argument is interpreted as an integer value. A timeout of zero means instead to\n   * block forever.\n   * <p>\n   * <b>Multiple clients blocking for the same keys</b>\n   * <p>\n   * Multiple clients can block for the same key. They are put into a queue, so the first to be\n   * served will be the one that started to wait earlier, in a first-blpopping first-served fashion.\n   * <p>\n   * <b>blocking POP inside a MULTI/EXEC transaction</b>\n   * <p>\n   * BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies\n   * in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis\n   * transaction).\n   * <p>\n   * The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil\n   * reply, exactly what happens when the timeout is reached. If you like science fiction, think at\n   * it like if inside MULTI/EXEC the time will flow at infinite speed :)\n   * <p>\n   * Time complexity: O(1)\n   * @param timeout\n   * @param keys\n   * @return BLPOP returns a two-elements array via a multi bulk reply in order to return both the\n   *         unblocking key and the popped value.\n   *         <p>\n         When a non-zero timeout is specified, and the BLPOP operation timed out, the return\n         value is a nil multi bulk reply. Most connection values will return false or nil\n         accordingly to the programming language used.\n   */\n  @Override\n  public List<byte[]> brpop(final int timeout, final byte[]... keys) {\n    return connection.executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], byte[]> brpop(final double timeout, final byte[]... keys) {\n    return connection.executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> lmpop(ListDirection direction, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lmpop(direction, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> lmpop(ListDirection direction, int count, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lmpop(direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> blmpop(double timeout, ListDirection direction, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blmpop(timeout, direction, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> blmpop(double timeout, ListDirection direction, int count, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blmpop(timeout, direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], Tuple> bzpopmax(final double timeout, final byte[]... keys) {\n    return connection.executeCommand(commandObjects.bzpopmax(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], Tuple> bzpopmin(final double timeout, final byte[]... keys) {\n    return connection.executeCommand(commandObjects.bzpopmin(timeout, keys));\n  }\n\n  /**\n   * Request for authentication in a password protected Redis server. A Redis server can be\n   * instructed to require a password before to allow clients to issue commands. This is done using\n   * the requirepass directive in the Redis configuration file. If the password given by the connection\n   * is correct the server replies with an OK status code reply and starts accepting commands from\n   * the connection. Otherwise, an error is returned and the clients needs to try a new password. Note\n   * that for the high performance nature of Redis it is possible to try a lot of passwords in\n   * parallel in very short time, so make sure to generate a strong and very long password so that\n   * this attack is infeasible.\n   * @param password\n   * @return OK\n   */\n  @Override\n  public String auth(final String password) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.AUTH, password);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Request for authentication with a Redis Server that is using ACL where user are authenticated with\n   * username and password.\n   * See https://redis.io/topics/acl\n   * @param user\n   * @param password\n   * @return OK\n   */\n  @Override\n  public String auth(final String user, final String password) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.AUTH, user, password);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public long zcount(final byte[] key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public long zcount(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public List<byte[]> zdiff(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiff(keys));\n  }\n\n  @Override\n  public List<Tuple> zdiffWithScores(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiffWithScores(keys));\n  }\n\n  @Override\n  @Deprecated\n  public long zdiffStore(final byte[] dstkey, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiffStore(dstkey, keys));\n  }\n\n  @Override\n  public long zdiffstore(final byte[] dstkey, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiffstore(dstkey, keys));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(byte[], double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if the\n   * offset is large the commands needs to traverse the list for offset elements and this adds up to\n   * the O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(byte[], double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(byte[], double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(byte[], double, double)\n   * @see Jedis#zrangeByScore(byte[], double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double, int, int)\n   * @see Jedis#zcount(byte[], double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @return A list of elements in the specified score range\n   */\n  @Override\n  public List<byte[]> zrangeByScore(final byte[] key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  @Override\n  public List<byte[]> zrangeByScore(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(byte[], double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(byte[], double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(byte[], double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(byte[], double, double)\n   * @see Jedis#zrangeByScore(byte[], double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double, int, int)\n   * @see Jedis#zcount(byte[], double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @param offset\n   * @param count\n   * @return A list of elements in the specified score range\n   */\n  @Override\n  public List<byte[]> zrangeByScore(final byte[] key, final double min, final double max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  @Override\n  public List<byte[]> zrangeByScore(final byte[] key, final byte[] min, final byte[] max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(byte[], double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(byte[], double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(byte[], double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(byte[], double, double)\n   * @see Jedis#zrangeByScore(byte[], double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double, int, int)\n   * @see Jedis#zcount(byte[], double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @return A list of elements in the specified score range\n   */\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final byte[] key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(byte[], double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(byte[], double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(byte[], double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(byte[], double, double)\n   * @see Jedis#zrangeByScore(byte[], double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double)\n   * @see Jedis#zrangeByScoreWithScores(byte[], double, double, int, int)\n   * @see Jedis#zcount(byte[], double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @param offset\n   * @param count\n   * @return A list of elements in the specified score range\n   */\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final byte[] key, final double min, final double max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final byte[] key, final byte[] min, final byte[] max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  @Override\n  public List<byte[]> zrevrangeByScore(final byte[] key, final double max, final double min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  @Override\n  public List<byte[]> zrevrangeByScore(final byte[] key, final byte[] max, final byte[] min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  @Override\n  public List<byte[]> zrevrangeByScore(final byte[] key, final double max, final double min,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  @Override\n  public List<byte[]> zrevrangeByScore(final byte[] key, final byte[] max, final byte[] min,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final byte[] key, final double max, final double min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final byte[] key, final double max,\n      final double min, final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final byte[] key, final byte[] max, final byte[] min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final byte[] key, final byte[] max,\n      final byte[] min, final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  /**\n   * Remove all elements in the sorted set at key with rank between start and end. Start and end are\n   * 0-based with rank 0 being the element with the lowest score. Both start and end can be negative\n   * numbers, where they indicate offsets starting at the element with the highest rank. For\n   * example: -1 is the element with the highest score, -2 the element with the second highest score\n   * and so forth.\n   * <p>\n   * <b>Time complexity:</b> O(log(N))+O(M) with N being the number of elements in the sorted set\n   * and M the number of elements removed by the operation\n   * @param key\n   * @param start\n   * @param stop\n   * @return The number of elements removed\n   */\n  @Override\n  public long zremrangeByRank(final byte[] key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByRank(key, start, stop));\n  }\n\n  /**\n   * Remove all the elements in the sorted set at key with a score between min and max (including\n   * elements with score equal to min or max).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements removed by the operation\n   * @param key\n   * @param min\n   * @param max\n   * @return The number of elements removed\n   */\n  @Override\n  public long zremrangeByScore(final byte[] key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public long zremrangeByScore(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  /**\n   * Add multiple sorted sets, This command is similar to ZUNIONSTORE, but instead of storing the\n   * resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return The result of the union\n   */\n  @Override\n  public List<byte[]> zunion(final ZParams params, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunion(params, keys));\n  }\n\n  /**\n   * Add multiple sorted sets with scores, This command is similar to ZUNIONSTORE, but instead of\n   * storing the resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return The result of the union with their scores\n   */\n  @Override\n  public List<Tuple> zunionWithScores(final ZParams params, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunionWithScores(params, keys));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(byte[], byte[][])} ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(byte[], byte[][])} command inserts all elements across all inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @param dstkey\n   * @param sets\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zunionstore(final byte[] dstkey, final byte[]... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunionstore(dstkey, sets));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(byte[], byte[][]) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(byte[], byte[][]) ZUNIONSTORE} command inserts all elements across\n   * all inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @param dstkey\n   * @param sets\n   * @param params\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zunionstore(final byte[] dstkey, final ZParams params, final byte[]... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunionstore(dstkey, params, sets));\n  }\n\n  /**\n   * Intersect multiple sorted sets, This command is similar to ZINTERSTORE, but instead of storing\n   * the resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return The result of the intersection\n   */\n  @Override\n  public List<byte[]> zinter(final ZParams params, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinter(params, keys));\n  }\n\n  /**\n   * Intersect multiple sorted sets, This command is similar to ZINTERSTORE, but instead of storing\n   * the resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return The result of the intersection with scores\n   */\n  @Override\n  public List<Tuple> zinterWithScores(final ZParams params, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinterWithScores(params, keys));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(byte[], byte[][]) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(byte[], byte[][]) ZUNIONSTORE} command inserts all elements across all\n   * inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @param dstkey\n   * @param sets\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zinterstore(final byte[] dstkey, final byte[]... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinterstore(dstkey, sets));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(byte[], byte[][]) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(byte[], byte[][]) ZUNIONSTORE} command inserts all elements across all\n   * inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @param dstkey\n   * @param sets\n   * @param params\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zinterstore(final byte[] dstkey, final ZParams params, final byte[]... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinterstore(dstkey, params, sets));\n  }\n\n  @Override\n  public long zintercard(byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zintercard(keys));\n  }\n\n  @Override\n  public long zintercard(long limit, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zintercard(limit, keys));\n  }\n\n  @Override\n  public long zlexcount(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zlexcount(key, min, max));\n  }\n\n  @Override\n  public List<byte[]> zrangeByLex(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByLex(key, min, max));\n  }\n\n  @Override\n  public List<byte[]> zrangeByLex(final byte[] key, final byte[] min, final byte[] max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count));\n  }\n\n  @Override\n  public List<byte[]> zrevrangeByLex(final byte[] key, final byte[] max, final byte[] min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByLex(key, max, min));\n  }\n\n  @Override\n  public List<byte[]> zrevrangeByLex(final byte[] key, final byte[] max, final byte[] min,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n  }\n\n  @Override\n  public long zremrangeByLex(final byte[] key, final byte[] min, final byte[] max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByLex(key, min, max));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> zmpop(SortedSetOption option, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zmpop(option, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> zmpop(SortedSetOption option, int count, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zmpop(option, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> bzmpop(double timeout, SortedSetOption option, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bzmpop(timeout, option, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> bzmpop(double timeout, SortedSetOption option, int count, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bzmpop(timeout, option, count, keys));\n  }\n\n  /**\n   * Synchronously save the DB on disk.\n   * <p>\n   * Save the whole dataset on disk (this means that all the databases are saved, as well as keys\n   * with an EXPIRE set (the expire is preserved). The server hangs while the saving is not\n   * completed, no connection is served in the meanwhile. An OK code is returned when the DB was\n   * fully stored in disk.\n   * <p>\n   * The background variant of this command is {@link Jedis#bgsave() BGSAVE} that is able to perform\n   * the saving in the background while the server continues serving other clients.\n   * <p>\n   * @return OK\n   */\n  @Override\n  public String save() {\n    connection.sendCommand(Command.SAVE);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Asynchronously save the DB on disk.\n   * <p>\n   * Save the DB in background. The OK code is immediately returned. Redis forks, the parent\n   * continues to server the clients, the child saves the DB on disk then exit. A connection my be able\n   * to check if the operation succeeded using the LASTSAVE command.\n   * @return OK\n   */\n  @Override\n  public String bgsave() {\n    connection.sendCommand(BGSAVE);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String bgsaveSchedule() {\n    connection.sendCommand(BGSAVE, SCHEDULE);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Rewrite the append only file in background when it gets too big. Please for detailed\n   * information about the Redis Append Only File check the <a\n   * href=\"http://redis.io/topics/persistence#append-only-file\">Append Only File Howto</a>.\n   * <p>\n   * BGREWRITEAOF rewrites the Append Only File in background when it gets too big. The Redis Append\n   * Only File is a Journal, so every operation modifying the dataset is logged in the Append Only\n   * File (and replayed at startup). This means that the Append Only File always grows. In order to\n   * rebuild its content the BGREWRITEAOF creates a new version of the append only file starting\n   * directly form the dataset in memory in order to guarantee the generation of the minimal number\n   * of commands needed to rebuild the database.\n   * <p>\n   * @return OK\n   */\n  @Override\n  public String bgrewriteaof() {\n    connection.sendCommand(BGREWRITEAOF);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Return the UNIX time stamp of the last successfully saving of the dataset on disk.\n   * <p>\n   * Return the UNIX TIME of the last DB save executed with success. A connection may check if a\n   * {@link Jedis#bgsave() BGSAVE} command succeeded reading the LASTSAVE value, then issuing a\n   * BGSAVE command and checking at regular intervals every N seconds if LASTSAVE changed.\n   * @return An UNIX time stamp\n   */\n  @Override\n  public long lastsave() {\n    connection.sendCommand(LASTSAVE);\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * Synchronously save the DB on disk, then shutdown the server.\n   * <p>\n   * Stop all the clients, save the DB, then quit the server. This commands makes sure that the DB\n   * is switched off without the lost of any data.\n   * @throws JedisException with the status code reply on error. On success nothing is thrown since\n   *         the server quits and the connection is closed.\n   */\n  @Override\n  public void shutdown() throws JedisException {\n    connection.sendCommand(SHUTDOWN);\n    try {\n      throw new JedisException(connection.getStatusCodeReply());\n    } catch (JedisConnectionException jce) {\n      // expected\n      connection.setBroken();\n    }\n  }\n\n  @Override\n  public void shutdown(ShutdownParams shutdownParams) throws JedisException {\n    connection.sendCommand(new CommandArguments(SHUTDOWN).addParams(shutdownParams));\n    try {\n      throw new JedisException(connection.getStatusCodeReply());\n    } catch (JedisConnectionException jce) {\n      // expected\n      connection.setBroken();\n    }\n  }\n\n  @Override\n  public String shutdownAbort() {\n    connection.sendCommand(SHUTDOWN, ABORT);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Provide information and statistics about the server.\n   * <p>\n   * The info command returns different information and statistics about the server in an format\n   * that's simple to parse by computers and easy to read by humans.\n   * <p>\n   * <b>Format of the returned String:</b>\n   * <p>\n   * All the fields are in the form field:value\n   *\n   * <pre>\n   * redis_version:0.07\n   * connected_clients:1\n   * connected_slaves:0\n   * used_memory:3187\n   * changes_since_last_save:0\n   * last_save_time:1237655729\n   * total_connections_received:1\n   * total_commands_processed:1\n   * uptime_in_seconds:25\n   * uptime_in_days:0\n   * </pre>\n   *\n   * <b>Notes</b>\n   * <p>\n   * used_memory is returned in bytes, and is the total number of bytes allocated by the program\n   * using malloc.\n   * <p>\n   * uptime_in_days is redundant since the uptime in seconds contains already the full uptime\n   * information, this field is only mainly present for humans.\n   * <p>\n   * changes_since_last_save does not refer to the number of key changes, but to the number of\n   * operations that produced some kind of change in the dataset.\n   * <p>\n   * @return Bulk reply\n   */\n  @Override\n  public String info() {\n    connection.sendCommand(Command.INFO);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String info(final String section) {\n    connection.sendCommand(Command.INFO, section);\n    return connection.getBulkReply();\n  }\n\n  /**\n   * Dump all the received requests in real time.\n   * <p>\n   * MONITOR is a debugging command that outputs the whole sequence of commands received by the\n   * Redis server. is very handy in order to understand what is happening into the database. This\n   * command is used directly via telnet.\n   * @param jedisMonitor\n   */\n  public void monitor(final JedisMonitor jedisMonitor) {\n//    connection.monitor();\n    connection.sendCommand(Command.MONITOR);\n    connection.getStatusCodeReply();\n    jedisMonitor.proceed(connection);\n  }\n\n  /**\n   * Change the replication settings.\n   * <p>\n   * The SLAVEOF command can change the replication settings of a slave on the fly. If a Redis\n   * server is already acting as slave, the command SLAVEOF NO ONE will turn off the replication\n   * turning the Redis server into a MASTER. In the proper form SLAVEOF hostname port will make the\n   * server a slave of the specific server listening at the specified hostname and port.\n   * <p>\n   * If a server is already a slave of some master, SLAVEOF hostname port will stop the replication\n   * against the old server and start the synchronization against the new one discarding the old\n   * dataset.\n   * <p>\n   * The form SLAVEOF no one will stop replication turning the server into a MASTER but will not\n   * discard the replication. So if the old master stop working it is possible to turn the slave\n   * into a master and set the application to use the new master in read/write. Later when the other\n   * Redis server will be fixed it can be configured in order to work as slave.\n   * @param host\n   * @param port\n   * @return OK\n   * @deprecated Use {@link Jedis#replicaof(java.lang.String, int)}.\n   */\n  @Override\n  @Deprecated\n  public String slaveof(final String host, final int port) {\n    connection.sendCommand(SLAVEOF, encode(host), toByteArray(port));\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#replicaofNoOne()}.\n   */\n  @Override\n  @Deprecated\n  public String slaveofNoOne() {\n    connection.sendCommand(SLAVEOF, NO.getRaw(), ONE.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String replicaof(final String host, final int port) {\n    connection.sendCommand(REPLICAOF, encode(host), toByteArray(port));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String replicaofNoOne() {\n    connection.sendCommand(REPLICAOF, NO.getRaw(), ONE.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public List<Object> roleBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ROLE);\n    return BuilderFactory.RAW_OBJECT_LIST.build(connection.getOne());\n  }\n\n  /**\n   * Retrieve the configuration of a running Redis server. Not all the configuration parameters are\n   * supported.\n   * <p>\n   * CONFIG GET returns the current configuration parameters. This sub command only accepts a single\n   * argument, that is glob style pattern. All the configuration parameters matching this parameter\n   * are reported as a list of key-value pairs.\n   * <p>\n   * <b>Example:</b>\n   *\n   * <pre>\n   * $ redis-cli config get '*'\n   * 1. \"dbfilename\"\n   * 2. \"dump.rdb\"\n   * 3. \"requirepass\"\n   * 4. (nil)\n   * 5. \"masterauth\"\n   * 6. (nil)\n   * 7. \"maxmemory\"\n   * 8. \"0\\n\"\n   * 9. \"appendfsync\"\n   * 10. \"everysec\"\n   * 11. \"save\"\n   * 12. \"3600 1 300 100 60 10000\"\n   *\n   * $ redis-cli config get 'm*'\n   * 1. \"masterauth\"\n   * 2. (nil)\n   * 3. \"maxmemory\"\n   * 4. \"0\\n\"\n   * </pre>\n   * @param pattern\n   * @return Bulk reply.\n   */\n  @Override\n  public Map<byte[], byte[]> configGet(final byte[] pattern) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, Keyword.GET.getRaw(), pattern);\n    return BuilderFactory.BINARY_MAP.build(connection.getOne());\n  }\n\n  @Override\n  public Map<byte[], byte[]> configGet(byte[]... patterns) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, joinParameters(Keyword.GET.getRaw(), patterns));\n    return BuilderFactory.BINARY_MAP.build(connection.getOne());\n  }\n\n  /**\n   * Reset the stats returned by INFO\n   */\n  @Override\n  public String configResetStat() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, Keyword.RESETSTAT);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * The CONFIG REWRITE command rewrites the redis.conf file the server was started with, applying\n   * the minimal changes needed to make it reflect the configuration currently used by the server,\n   * which may be different compared to the original one because of the use of the CONFIG SET\n   * command.\n   * <p>\n   * The rewrite is performed in a very conservative way:\n   * <ul>\n   * <li>Comments and the overall structure of the original redis.conf are preserved as much as\n   * possible.</li>\n   * <li>If an option already exists in the old redis.conf file, it will be rewritten at the same\n   * position (line number).</li>\n   * <li>If an option was not already present, but it is set to its default value, it is not added\n   * by the rewrite process.</li>\n   * <li>If an option was not already present, but it is set to a non-default value, it is appended\n   * at the end of the file.</li>\n   * <li>Non used lines are blanked. For instance if you used to have multiple save directives, but\n   * the current configuration has fewer or none as you disabled RDB persistence, all the lines will\n   * be blanked.</li>\n   * </ul>\n   * <p>\n   * CONFIG REWRITE is also able to rewrite the configuration file from scratch if the original one\n   * no longer exists for some reason. However, if the server was started without a configuration\n   * file at all, the CONFIG REWRITE will just return an error.\n   * @return OK when the configuration was rewritten properly. Otherwise, an error is returned.\n   */\n  @Override\n  public String configRewrite() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, Keyword.REWRITE);\n    return connection.getStatusCodeReply();\n  }\n\n  /**\n   * Alter the configuration of a running Redis server. Not all the configuration parameters are\n   * supported.\n   * <p>\n   * The list of configuration parameters supported by CONFIG SET can be obtained issuing a\n   * {@link Jedis#configGet(byte[]) CONFIG GET *} command.\n   * <p>\n   * The configuration set using CONFIG SET is immediately loaded by the Redis server that will\n   * start acting as specified starting from the next command.\n   * <p>\n   * <b>Parameters value format</b>\n   * <p>\n   * The value of the configuration parameter is the same as the one of the same parameter in the\n   * Redis configuration file, with the following exceptions:\n   * <p>\n   * <ul>\n   * <li>The save parameter is a list of space-separated integers. Every pair of integers specify\n   * the time and number of changes limit to trigger a save. For instance the command CONFIG SET\n   * save \"3600 10 60 10000\" will configure the server to issue a background saving of the RDB file\n   * every 3600 seconds if there are at least 10 changes in the dataset, and every 60 seconds if\n   * there are at least 10000 changes. To completely disable automatic snapshots just set the\n   * parameter as an empty string.\n   * <li>All the integer parameters representing memory are returned and accepted only using bytes\n   * as unit.\n   * </ul>\n   * @param parameter\n   * @param value\n   * @return OK\n   */\n  @Override\n  public String configSet(final byte[] parameter, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, Keyword.SET.getRaw(), parameter, value);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String configSet(final byte[]... parameterValues) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, joinParameters(Keyword.SET.getRaw(), parameterValues));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String configSetBinary(Map<byte[], byte[]> parameterValues) {\n    checkIsInMultiOrPipeline();\n    CommandArguments args = new CommandArguments(Command.CONFIG).add(Keyword.SET);\n    parameterValues.forEach((k, v) -> args.add(k).add(v));\n    connection.sendCommand(args);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public long strlen(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.strlen(key));\n  }\n\n  @Override\n  public LCSMatchResult lcs(final byte[] keyA, final byte[] keyB, final LCSParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lcs(keyA, keyB, params));\n  }\n\n  @Override\n  public long lpushx(final byte[] key, final byte[]... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpushx(key, strings));\n  }\n\n  /**\n   * Undo a {@link Jedis#expire(byte[], long) expire} at turning the expire key into a normal key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return 1 if the key is now persist, 0 if the key is not persist (only happens when key not set)\n   */\n  @Override\n  public long persist(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.persist(key));\n  }\n\n  @Override\n  public long rpushx(final byte[] key, final byte[]... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpushx(key, strings));\n  }\n\n  @Override\n  public byte[] echo(final byte[] string) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ECHO, string);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public long linsert(final byte[] key, final ListPosition where, final byte[] pivot,\n      final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.linsert(key, where, pivot, value));\n  }\n\n  /**\n   * Pop a value from a list, push it to another list and return it; or block until one is available\n   * @deprecated Use {@link Jedis#blmove(byte[], byte[], ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public byte[] brpoplpush(final byte[] source, final byte[] destination, final int timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.brpoplpush(source, destination, timeout));\n  }\n\n  /**\n   * Sets or clears the bit at offset in the string value stored at key\n   */\n  @Override\n  public boolean setbit(final byte[] key, final long offset, final boolean value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setbit(key, offset, value));\n  }\n\n  /**\n   * Returns the bit value at offset in the string value stored at key\n   */\n  @Override\n  public boolean getbit(final byte[] key, final long offset) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getbit(key, offset));\n  }\n\n  @Override\n  public long bitpos(final byte[] key, final boolean value) {\n    return bitpos(key, value, new BitPosParams());\n  }\n\n  @Override\n  public long bitpos(final byte[] key, final boolean value, final BitPosParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitpos(key, value, params));\n  }\n\n  @Override\n  public long setrange(final byte[] key, final long offset, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setrange(key, offset, value));\n  }\n\n  @Override\n  public byte[] getrange(final byte[] key, final long startOffset, final long endOffset) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getrange(key, startOffset, endOffset));\n  }\n\n  public long publish(final byte[] channel, final byte[] message) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.publish(channel, message));\n  }\n\n  public void subscribe(BinaryJedisPubSub jedisPubSub, final byte[]... channels) {\n    jedisPubSub.proceed(connection, channels);\n  }\n\n  public void psubscribe(BinaryJedisPubSub jedisPubSub, final byte[]... patterns) {\n    jedisPubSub.proceedWithPatterns(connection, patterns);\n  }\n\n  /**\n   * Evaluates scripts using the Lua interpreter built into Redis starting from version 2.6.0.\n   * @param script\n   * @param keys\n   * @param args\n   * @return Script result\n   */\n  @Override\n  public Object eval(final byte[] script, final List<byte[]> keys, final List<byte[]> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.eval(script, keys, args));\n  }\n\n  @Override\n  public Object evalReadonly(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalReadonly(script, keys, args));\n  }\n\n  protected static byte[][] getParamsWithBinary(List<byte[]> keys, List<byte[]> args) {\n    final int keyCount = keys.size();\n    final int argCount = args.size();\n    byte[][] params = new byte[keyCount + argCount][];\n\n    for (int i = 0; i < keyCount; i++)\n      params[i] = keys.get(i);\n\n    for (int i = 0; i < argCount; i++)\n      params[keyCount + i] = args.get(i);\n\n    return params;\n  }\n\n  @Override\n  public Object eval(final byte[] script, final int keyCount, final byte[]... params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.eval(script, keyCount, params));\n  }\n\n  @Override\n  public Object eval(final byte[] script) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.eval(script));\n  }\n\n  @Override\n  public Object evalsha(final byte[] sha1) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalsha(sha1));\n  }\n\n  @Override\n  public Object evalsha(final byte[] sha1, final List<byte[]> keys, final List<byte[]> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalsha(sha1, keys, args));\n  }\n\n  @Override\n  public Object evalshaReadonly(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalshaReadonly(sha1, keys, args));\n  }\n\n  @Override\n  public Object evalsha(final byte[] sha1, final int keyCount, final byte[]... params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalsha(sha1, keyCount, params));\n  }\n\n  @Override\n  public String scriptFlush() {\n    connection.sendCommand(SCRIPT, FLUSH);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String scriptFlush(final FlushMode flushMode) {\n    connection.sendCommand(SCRIPT, FLUSH.getRaw(), flushMode.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public Boolean scriptExists(final byte[] sha1) {\n    byte[][] a = new byte[1][];\n    a[0] = sha1;\n    return scriptExists(a).get(0);\n  }\n\n  @Override\n  public List<Boolean> scriptExists(final byte[]... sha1) {\n    connection.sendCommand(SCRIPT, joinParameters(Keyword.EXISTS.getRaw(), sha1));\n    return BuilderFactory.BOOLEAN_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public byte[] scriptLoad(final byte[] script) {\n    connection.sendCommand(SCRIPT, LOAD.getRaw(), script);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public String scriptKill() {\n    return connection.executeCommand(commandObjects.scriptKill());\n  }\n\n  @Override\n  public String slowlogReset() {\n    return connection.executeCommand(commandObjects.slowlogReset());\n  }\n\n  @Override\n  public long slowlogLen() {\n    connection.sendCommand(SLOWLOG, LEN);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public List<Object> slowlogGetBinary() {\n    connection.sendCommand(SLOWLOG, Keyword.GET);\n    return connection.getObjectMultiBulkReply();\n  }\n\n  @Override\n  public List<Object> slowlogGetBinary(final long entries) {\n    connection.sendCommand(SLOWLOG, Keyword.GET.getRaw(), toByteArray(entries));\n    return connection.getObjectMultiBulkReply();\n  }\n\n  @Override\n  public Long objectRefcount(final byte[] key) {\n    connection.sendCommand(OBJECT, REFCOUNT.getRaw(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public byte[] objectEncoding(final byte[] key) {\n    connection.sendCommand(OBJECT, ENCODING.getRaw(), key);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public Long objectIdletime(final byte[] key) {\n    connection.sendCommand(OBJECT, IDLETIME.getRaw(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public List<byte[]> objectHelpBinary() {\n    connection.sendCommand(OBJECT, HELP);\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public Long objectFreq(final byte[] key) {\n    connection.sendCommand(OBJECT, FREQ.getRaw(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public long bitcount(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitcount(key));\n  }\n\n  @Override\n  public long bitcount(final byte[] key, final long start, final long end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitcount(key, start, end));\n  }\n\n  @Override\n  public long bitcount(final byte[] key, final long start, final long end, final BitCountOption option) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitcount(key, start, end, option));\n  }\n\n  @Override\n  public long bitop(final BitOP op, final byte[] destKey, final byte[]... srcKeys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitop(op, destKey, srcKeys));\n  }\n\n  @Override\n  public byte[] dump(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.dump(key));\n  }\n\n  @Override\n  public String restore(final byte[] key, final long ttl, final byte[] serializedValue) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.restore(key, ttl, serializedValue));\n  }\n\n  @Override\n  public String restore(final byte[] key, final long ttl, final byte[] serializedValue,\n      final RestoreParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.restore(key, ttl, serializedValue, params));\n  }\n\n  @Override\n  public long pttl(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pttl(key));\n  }\n\n  /**\n   * PSETEX works exactly like {@link Jedis#setex(byte[], long, byte[])} with the sole difference\n   * that the expire time is specified in milliseconds instead of seconds. Time complexity: O(1)\n   * @param key\n   * @param milliseconds\n   * @param value\n   * @return OK\n   * @deprecated Use {@link Jedis#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String psetex(final byte[] key, final long milliseconds, final byte[] value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.psetex(key, milliseconds, value));\n  }\n\n  @Override\n  public byte[] memoryDoctorBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, DOCTOR);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public Long memoryUsage(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, USAGE.getRaw(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public Long memoryUsage(final byte[] key, final int samples) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, USAGE.getRaw(), key, SAMPLES.getRaw(), toByteArray(samples));\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public String failover() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.FAILOVER);\n    connection.setTimeoutInfinite();\n    try {\n      return connection.getStatusCodeReply();\n    } finally {\n      connection.rollbackTimeout();\n    }\n  }\n\n  @Override\n  public String failover(FailoverParams failoverParams) {\n    checkIsInMultiOrPipeline();\n    CommandArguments args = new CommandArguments(Command.FAILOVER).addParams(failoverParams);\n    connection.sendCommand(args);\n    connection.setTimeoutInfinite();\n    try {\n      return connection.getStatusCodeReply();\n    } finally {\n      connection.rollbackTimeout();\n    }\n  }\n\n  @Override\n  public String failoverAbort() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.FAILOVER, ABORT);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public byte[] aclWhoAmIBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, WHOAMI);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public byte[] aclGenPassBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, GENPASS);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public byte[] aclGenPassBinary(int bits) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, GENPASS.getRaw(), toByteArray(bits));\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public List<byte[]> aclListBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LIST);\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public List<byte[]> aclUsersBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, USERS);\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public AccessControlUser aclGetUser(byte[] name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, GETUSER.getRaw(), name);\n    return BuilderFactory.ACCESS_CONTROL_USER.build(connection.getObjectMultiBulkReply());\n  }\n\n  @Override\n  public String aclSetUser(byte[] name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, SETUSER.getRaw(), name);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String aclSetUser(byte[] name, byte[]... rules) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, joinParameters(SETUSER.getRaw(), name, rules));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public long aclDelUser(byte[]... names) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, joinParameters(DELUSER.getRaw(), names));\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public List<byte[]> aclCatBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, CAT);\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public List<byte[]> aclCat(byte[] category) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, CAT.getRaw(), category);\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public List<byte[]> aclLogBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LOG);\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public List<byte[]> aclLogBinary(int limit) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LOG.getRaw(), toByteArray(limit));\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public String aclLogReset() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LOG.getRaw(), Keyword.RESET.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clientKill(final byte[] ipPort) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, KILL.getRaw(), ipPort);\n    return this.connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clientKill(final String ip, final int port) {\n    return clientKill(ip + ':' + port);\n  }\n\n  @Override\n  public long clientKill(ClientKillParams params) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(new CommandArguments(CLIENT).add(KILL).addParams(params));\n    return this.connection.getIntegerReply();\n  }\n\n  @Override\n  public byte[] clientGetnameBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, GETNAME);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public byte[] clientListBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, LIST);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public byte[] clientListBinary(ClientType type) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, LIST.getRaw(), type.getRaw());\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public byte[] clientListBinary(final long... clientIds) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, clientListParams(clientIds));\n    return connection.getBinaryBulkReply();\n  }\n\n  private byte[][] clientListParams(final long... clientIds) {\n    final byte[][] params = new byte[2 + clientIds.length][];\n    int index = 0;\n    params[index++] = Keyword.LIST.getRaw();\n    params[index++] = ID.getRaw();\n    for (final long clientId : clientIds) {\n      params[index++] = toByteArray(clientId);\n    }\n    return params;\n  }\n\n  @Override\n  public byte[] clientInfoBinary() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, Keyword.INFO);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public String clientSetInfo(ClientAttributeOption attr, byte[] value) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, SETINFO.getRaw(), attr.getRaw(), value);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clientSetname(final byte[] name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, SETNAME.getRaw(), name);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public long clientId() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, ID);\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * Unblock a connection blocked in a blocking command from a different connection.\n   * @param clientId\n   */\n  @Override\n  public long clientUnblock(final long clientId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, UNBLOCK.getRaw(), toByteArray(clientId));\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * Unblock a connection blocked in a blocking command from a different connection.\n   * @param clientId\n   * @param unblockType\n   */\n  @Override\n  public long clientUnblock(final long clientId, final UnblockType unblockType) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, UNBLOCK.getRaw(), toByteArray(clientId), unblockType.getRaw());\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public String clientPause(final long timeout) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, PAUSE.getRaw(), toByteArray(timeout));\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientPause(final long timeout, final ClientPauseMode mode) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, PAUSE.getRaw(), toByteArray(timeout), mode.getRaw());\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientUnpause() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, UNPAUSE);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientNoEvictOn() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, \"NO-EVICT\", \"ON\");\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientNoEvictOff() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, \"NO-EVICT\", \"OFF\");\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientNoTouchOn() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, \"NO-TOUCH\", \"ON\");\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clientNoTouchOff() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, \"NO-TOUCH\", \"OFF\");\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public TrackingInfo clientTrackingInfo() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, \"TRACKINGINFO\");\n    return TrackingInfo.TRACKING_INFO_BUILDER.build(connection.getOne());\n  }\n\n  public List<String> time() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.TIME);\n    return connection.getMultiBulkReply();\n  }\n\n  @Override\n  public String migrate(final String host, final int port, final byte[] key,\n      final int destinationDb, final int timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, key, destinationDb, timeout));\n  }\n\n  @Override\n  public String migrate(final String host, final int port, final int destinationDB,\n      final int timeout, final MigrateParams params, final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, destinationDB, timeout, params, keys));\n  }\n\n  @Override\n  public String migrate(String host, int port, byte[] key, int timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, key, timeout));\n  }\n\n  @Override\n  public String migrate(String host, int port, int timeout, MigrateParams params, byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, timeout, params, keys));\n  }\n\n  @Override\n  public long waitReplicas(final int replicas, final long timeout) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(WAIT, toByteArray(replicas), toByteArray(timeout));\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public KeyValue<Long, Long> waitAOF(long numLocal, long numReplicas, long timeout) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(WAITAOF, toByteArray(numLocal), toByteArray(numReplicas), toByteArray(timeout));\n    return BuilderFactory.LONG_LONG_PAIR.build(connection.getOne());\n  }\n\n  @Override\n  public long pfadd(final byte[] key, final byte[]... elements) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfadd(key, elements));\n  }\n\n  @Override\n  public long pfcount(final byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfcount(key));\n  }\n\n  @Override\n  public String pfmerge(final byte[] destkey, final byte[]... sourcekeys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfmerge(destkey, sourcekeys));\n  }\n\n  @Override\n  public long pfcount(final byte[]... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfcount(keys));\n  }\n\n  @Override\n  public ScanResult<byte[]> scan(final byte[] cursor) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.scan(cursor));\n  }\n\n  @Override\n  public ScanResult<byte[]> scan(final byte[] cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.scan(cursor, params));\n  }\n\n  @Override\n  public ScanResult<byte[]> scan(final byte[] cursor, final ScanParams params, final byte[] type) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.scan(cursor, params, type));\n  }\n\n  @Override\n  public ScanResult<Map.Entry<byte[], byte[]>> hscan(final byte[] key, final byte[] cursor,\n      final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<byte[]> hscanNoValues(final byte[] key, final byte[] cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hscanNoValues(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<byte[]> sscan(final byte[] key, final byte[] cursor) {\n    return sscan(key, cursor, new ScanParams());\n  }\n\n  @Override\n  public ScanResult<byte[]> sscan(final byte[] key, final byte[] cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<Tuple> zscan(final byte[] key, final byte[] cursor) {\n    return zscan(key, cursor, new ScanParams());\n  }\n\n  @Override\n  public ScanResult<Tuple> zscan(final byte[] key, final byte[] cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zscan(key, cursor, params));\n  }\n\n  @Override\n  public long geoadd(final byte[] key, final double longitude, final double latitude,\n      final byte[] member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geoadd(key, longitude, latitude, member));\n  }\n\n  @Override\n  public long geoadd(final byte[] key, final Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geoadd(key, memberCoordinateMap));\n  }\n\n  @Override\n  public long geoadd(final byte[] key, final GeoAddParams params, final Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geoadd(key, params, memberCoordinateMap));\n  }\n\n  @Override\n  public Double geodist(final byte[] key, final byte[] member1, final byte[] member2) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geodist(key, member1, member2));\n  }\n\n  @Override\n  public Double geodist(final byte[] key, final byte[] member1, final byte[] member2,\n      final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geodist(key, member1, member2, unit));\n  }\n\n  @Override\n  public List<byte[]> geohash(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geohash(key, members));\n  }\n\n  @Override\n  public List<GeoCoordinate> geopos(final byte[] key, final byte[]... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geopos(key, members));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadius(final byte[] key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(final byte[] key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadius(final byte[] key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param));\n  }\n\n  @Override\n  public long georadiusStore(final byte[] key, final double longitude, final double latitude,\n      final double radius, final GeoUnit unit, final GeoRadiusParam param,\n      final GeoRadiusStoreParam storeParam) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(final byte[] key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(final byte[] key, final byte[] member,\n      final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMember(key, member, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(final byte[] key, final byte[] member,\n      final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(final byte[] key, final byte[] member,\n      final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param));\n  }\n\n  @Override\n  public long georadiusByMemberStore(final byte[] key, final byte[] member, final double radius,\n      final GeoUnit unit, final GeoRadiusParam param, final GeoRadiusStoreParam storeParam) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, member, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, coord, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, member, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, coord, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, GeoSearchParam params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, params));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, byte[] member, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, member, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, coord, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, byte[] member, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, member, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, coord, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, GeoSearchParam params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, params));\n  }\n\n  @Override\n  public long geosearchStoreStoreDist(byte[] dest, byte[] src, GeoSearchParam params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStoreStoreDist(dest, src, params));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(final byte[] key, final byte[] member,\n      final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));\n  }\n\n  @Override\n  public List<Long> bitfield(final byte[] key, final byte[]... arguments) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitfield(key, arguments));\n  }\n\n  @Override\n  public List<Long> bitfieldReadonly(byte[] key, final byte[]... arguments) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitfieldReadonly(key, arguments));\n  }\n\n  @Override\n  public long hstrlen(final byte[] key, final byte[] field) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hstrlen(key, field));\n  }\n\n  @Override\n  public List<Long> hexpire(byte[] key, long seconds, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpire(key, seconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpire(key, seconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(byte[] key, long milliseconds, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireTime(byte[] key, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireTime(byte[] key, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> httl(byte[] key, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.httl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpttl(byte[] key, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpttl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpersist(byte[] key, byte[]... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpersist(key, fields));\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use\n   *     {@link #xreadBinary(XReadParams, Map)} or\n   *     {@link #xreadBinaryAsMap(XReadParams, Map)} for type safety and better stream entry\n   *     parsing.\n   */\n  @Deprecated\n  @Override\n  public List<Object> xread(XReadParams xReadParams, Entry<byte[], byte[]>... streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xread(xReadParams, streams));\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use\n   *     {@link #xreadGroupBinary(byte[], byte[], XReadGroupParams, Map)} or\n   *     {@link #xreadGroupBinaryAsMap(byte[], byte[], XReadGroupParams, Map)} instead.\n   */\n  @Deprecated\n  @Override\n  public List<Object> xreadGroup(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Entry<byte[], byte[]>... streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public List<Map.Entry<byte[], List<StreamEntryBinary>>> xreadBinary(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadBinary(xReadParams, streams));\n  }\n\n  @Override\n  public Map<byte[], List<StreamEntryBinary>> xreadBinaryAsMap(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadBinaryAsMap(xReadParams, streams));\n  }\n\n  @Override\n  public List<Map.Entry<byte[], List<StreamEntryBinary>>> xreadGroupBinary(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadGroupBinary(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Map<byte[], List<StreamEntryBinary>> xreadGroupBinaryAsMap(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadGroupBinaryAsMap(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public byte[] xadd(final byte[] key, final XAddParams params, final Map<byte[], byte[]> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xadd(key, params, hash));\n  }\n\n  @Override\n  public long xlen(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xlen(key));\n  }\n\n  @Override\n  public List<Object> xrange(byte[] key, byte[] start, byte[] end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public List<Object> xrange(byte[] key, byte[] start, byte[] end, int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public List<Object> xrevrange(byte[] key, byte[] end, byte[] start) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public List<Object> xrevrange(byte[] key, byte[] end, byte[] start, int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public long xack(byte[] key, byte[] group, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xack(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(byte[] key, byte[] group, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xackdel(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xackdel(key, group, trimMode, ids));\n  }\n\n  @Override\n  public String xgroupCreate(byte[] key, byte[] consumer, byte[] id, boolean makeStream) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupCreate(key, consumer, id, makeStream));\n  }\n\n  @Override\n  public String xgroupSetID(byte[] key, byte[] consumer, byte[] id) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupSetID(key, consumer, id));\n  }\n\n  @Override\n  public long xgroupDestroy(byte[] key, byte[] consumer) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupDestroy(key, consumer));\n  }\n\n  @Override\n  public boolean xgroupCreateConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xgroupDelConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xdel(byte[] key, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xdel(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(byte[] key, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xdelex(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xdelex(key, trimMode, ids));\n  }\n\n  @Override\n  public long xtrim(byte[] key, long maxLen, boolean approximateLength) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xtrim(key, maxLen, approximateLength));\n  }\n\n  @Override\n  public long xtrim(byte[] key, XTrimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xtrim(key, params));\n  }\n\n  @Override\n  public Object xpending(final byte[] key, final byte[] groupName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xpending(key, groupName));\n  }\n\n  @Override\n  public List<Object> xpending(final byte[] key, final byte[] groupName, final XPendingParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xpending(key, groupName, params));\n  }\n\n  @Override\n  public List<byte[]> xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime,\n      XClaimParams params, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public List<byte[]> xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime,\n      XClaimParams params, byte[]... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public List<Object> xautoclaim(byte[] key, byte[] groupName, byte[] consumerName,\n      long minIdleTime, byte[] start, XAutoClaimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public List<Object> xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName,\n      long minIdleTime, byte[] start, XAutoClaimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Object xinfoStream(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoStream(key));\n  }\n\n  @Override\n  public Object xinfoStreamFull(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoStreamFull(key));\n  }\n\n  @Override\n  public Object xinfoStreamFull(byte[] key, int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoStreamFull(key, count));\n  }\n\n  @Override\n  public List<Object> xinfoGroups(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoGroups(key));\n  }\n\n  @Override\n  public List<Object> xinfoConsumers(byte[] key, byte[] group) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoConsumers(key, group));\n  }\n\n  @Override\n  public byte[] xcfgset(byte[] key, XCfgSetParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xcfgset(key, params));\n  }\n\n  public Object sendCommand(ProtocolCommand cmd, byte[]... args) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(cmd, args);\n    return connection.getOne();\n  }\n\n  public Object sendBlockingCommand(ProtocolCommand cmd, byte[]... args) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(cmd, args);\n    connection.setTimeoutInfinite();\n    try {\n      return connection.getOne();\n    } finally {\n      connection.rollbackTimeout();\n    }\n  }\n\n  public Object sendCommand(ProtocolCommand cmd) {\n    return sendCommand(cmd, DUMMY_ARRAY);\n  }\n\n  /**\n   * COPY source destination [DB destination-db] [REPLACE]\n   *\n   * @param srcKey the source key.\n   * @param dstKey the destination key.\n   * @param db\n   * @param replace\n   */\n  @Override\n  public boolean copy(String srcKey, String dstKey, int db, boolean replace) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.copy(srcKey, dstKey, db, replace));\n  }\n\n  /**\n   * COPY source destination [REPLACE]\n   *\n   * @param srcKey the source key.\n   * @param dstKey the destination key.\n   * @param replace\n   */\n  @Override\n  public boolean copy(String srcKey, String dstKey, boolean replace) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.copy(srcKey, dstKey, replace));\n  }\n\n  /**\n   * Works same as {@link #ping()} but returns argument message instead of PONG.\n   * @param message\n   * @return message\n   */\n  @Override\n  public String ping(final String message) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.PING, message);\n    return connection.getBulkReply();\n  }\n\n  /**\n   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1\n   * GB).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return OK\n   */\n  @Override\n  public String set(final String key, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.set(key, value));\n  }\n\n  /**\n   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1\n   * GB).\n   * @param key\n   * @param value\n   * @param params NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the\n   *          key if it already exists. EX|PX, expire time units: EX = seconds; PX = milliseconds\n   * @return simple-string-reply {@code OK} if {@code SET} was executed correctly, or {@code null}\n   * if the {@code SET} operation was not performed because the user specified the NX or XX option\n   * but the condition was not met.\n   */\n  @Override\n  public String set(final String key, final String value, final SetParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.set(key, value, params));\n  }\n\n  /**\n   * Get the value of the specified key. If the key does not exist the special value 'nil' is\n   * returned. If the value stored at key is not a string an error is returned because GET can only\n   * handle string values.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public String get(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.get(key));\n  }\n\n  @Override\n  public String setGet(final String key, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setGet(key, value));\n  }\n\n  @Override\n  public String setGet(final String key, final String value, final SetParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setGet(key, value, params));\n  }\n\n  /**\n   * Get the value of key and delete the key. This command is similar to GET, except for the fact\n   * that it also deletes the key on success (if and only if the key's value type is a string).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The value of key\n   */\n  @Override\n  public String getDel(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getDel(key));\n  }\n\n  @Override\n  public String getEx(String key, GetExParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getEx(key, params));\n  }\n\n  @Override\n  public String digestKey(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.digestKey(key));\n  }\n\n  @Override\n  public long delex(final String key, final CompareCondition condition) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.delex(key, condition));\n  }\n\n  /**\n   * Test if the specified keys exist. The command returns the number of keys exist.\n   * Time complexity: O(N)\n   * @param keys\n   * @return The number of keys that exist from those specified as {@code keys}\n   */\n  @Override\n  public long exists(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.exists(keys));\n  }\n\n  /**\n   * Test if the specified key exists. The command returns true if the key exists, otherwise false is\n   * returned. Note that even keys set with an empty string as value will return true. Time\n   * complexity: O(1)\n   * @param key\n   * @return {@code true} if the key exists, otherwise {@code false}\n   */\n  @Override\n  public boolean exists(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.exists(key));\n  }\n\n  /**\n   * Remove the specified keys. If a given key does not exist no operation is performed for this\n   * key. The command returns the number of keys removed. Time complexity: O(1)\n   * @param keys\n   * @return An integer greater than 0 if one or more keys were removed, 0 if none of the specified keys existed\n   */\n  @Override\n  public long del(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.del(keys));\n  }\n\n  @Override\n  public long del(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.del(key));\n  }\n\n  /**\n   * This command is very similar to DEL: it removes the specified keys. Just like DEL a key is\n   * ignored if it does not exist. However, the command performs the actual memory reclaiming in a\n   * different thread, so it is not blocking, while DEL is. This is where the command name comes\n   * from: the command just unlinks the keys from the keyspace. The actual removal will happen later\n   * asynchronously.\n   * <p>\n   * Time complexity: O(1) for each key removed regardless of its size. Then the command does O(N)\n   * work in a different thread in order to reclaim memory, where N is the number of allocations the\n   * deleted objects where composed of.\n   * @param keys\n   * @return The number of keys that were unlinked\n   */\n  @Override\n  public long unlink(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.unlink(keys));\n  }\n\n  @Override\n  public long unlink(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.unlink(key));\n  }\n\n  /**\n   * Return the type of the value stored at key in form of a string. The type can be one of \"none\",\n   * \"string\", \"list\", \"set\". \"none\" is returned if the key does not exist. Time complexity: O(1)\n   * @param key\n   * @return \"none\" if the key does not exist, \"string\" if the key contains a String value, \"list\"\n   * if the key contains a List value, \"set\" if the key contains a Set value, \"zset\" if the key\n   * contains a Sorted Set value, \"hash\" if the key contains a Hash value\n   */\n  @Override\n  public String type(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.type(key));\n  }\n\n  @Override\n  public Set<String> keys(final String pattern) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.keys(pattern));\n  }\n\n  /**\n   * Return a randomly selected key from the currently selected DB.\n   * <p>\n   * Time complexity: O(1)\n   * @return Randomly selected key or an empty string if the database is empty\n   */\n  @Override\n  public String randomKey() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.randomKey());\n  }\n\n  /**\n   * Atomically renames the key oldkey to newkey. If the source and destination name are the same an\n   * error is returned. If newkey already exists it is overwritten.\n   * <p>\n   * Time complexity: O(1)\n   * @param oldkey\n   * @param newkey\n   * @return OK\n   */\n  @Override\n  public String rename(final String oldkey, final String newkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rename(oldkey, newkey));\n  }\n\n  /**\n   * Rename oldkey into newkey but fails if the destination key newkey already exists.\n   * <p>\n   * Time complexity: O(1)\n   * @param oldkey\n   * @param newkey\n   * @return 1 if the key was renamed, 0 if the target key already exist\n   */\n  @Override\n  public long renamenx(final String oldkey, final String newkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.renamenx(oldkey, newkey));\n  }\n\n  /**\n   * Set a timeout on the specified key. After the timeout the key will be automatically deleted by\n   * the server. A key with an associated timeout is said to be volatile in Redis terminology.\n   * <p>\n   * Volatile keys are stored on disk like the other keys, the timeout is persistent too like all\n   * the other aspects of the dataset. Saving a dataset containing expires and stopping the server\n   * does not stop the flow of time as Redis stores on disk the time when the key will no longer be\n   * available as Unix time, and not the remaining seconds.\n   * <p>\n   * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire\n   * set. It is also possible to undo the expire at all turning the key into a normal key using the\n   * {@link Jedis#persist(String) PERSIST} command.\n   * <p>\n   * Time complexity: O(1)\n   * @see <a href=\"http://redis.io/commands/expire\">Expire Command</a>\n   * @param key\n   * @param seconds\n   * @return 1: the timeout was set. 0: the timeout was not set since\n   *         the key already has an associated timeout (this may happen only in Redis versions &lt;\n   *         2.1.3, Redis &gt;= 2.1.3 will happily update the timeout), or the key does not exist.\n   */\n  @Override\n  public long expire(final String key, final long seconds) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expire(key, seconds));\n  }\n\n  /**\n   * Similar to {@link Jedis#expire(String, long) EXPIRE} but with optional expiry setting.\n   * @see Jedis#expire(String, long)\n   * @param key\n   * @param seconds time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise. Since the key already has an associated timeout\n   * (this may happen only in Redis versions &lt; 2.1.3, Redis &gt;= 2.1.3 will happily update the timeout),\n   * or the key does not exist.\n   */\n  @Override\n  public long expire(final String key, final long seconds, final ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expire(key, seconds, expiryOption));\n  }\n\n  @Override\n  public long pexpire(final String key, final long milliseconds) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpire(key, milliseconds));\n  }\n\n  @Override\n  public long pexpire(final String key, final long milliseconds, final ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpire(key, milliseconds, expiryOption));\n  }\n\n  /**\n   * Returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire.\n   * <p>\n   * The command returns -1 if the key exists but has no associated expiration time, and -2 if the key does not exist.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return Expiration Unix timestamp in seconds, or a negative value in order to signal an error:\n   * -1 if the key exists but has no associated expiration time, and -2 if the key does not exist.\n   */\n  @Override\n  public long expireTime(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expireTime(key));\n  }\n\n  /**\n   * Similar to {@link Jedis#expireTime(String) EXPIRETIME} but returns the absolute Unix expiration\n   * timestamp in milliseconds instead of seconds.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#expireTime(String)\n   * @param key\n   * @return Expiration Unix timestamp in milliseconds, or a negative value in order to signal an error:\n   * -1 if the key exists but has no associated expiration time, and -2 if the key does not exist.\n   */\n  @Override\n  public long pexpireTime(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpireTime(key));\n  }\n\n  /**\n   * EXPIREAT works exactly like {@link Jedis#expire(String, long) EXPIRE} but instead to get the\n   * number of seconds representing the Time To Live of the key as a second argument (that is a\n   * relative way of specifying the TTL), it takes an absolute one in the form of a UNIX timestamp\n   * (Number of seconds elapsed since 1 Gen 1970).\n   * <p>\n   * EXPIREAT was introduced in order to implement the Append Only File persistence mode so that\n   * EXPIRE commands are automatically translated into EXPIREAT commands for the append only file.\n   * Of course EXPIREAT can also used by programmers that need a way to simply specify that a given\n   * key should expire at a given time in the future.\n   * <p>\n   * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire\n   * set. It is also possible to undo the expire at all turning the key into a normal key using the\n   * {@link Jedis#persist(String) PERSIST} command.\n   * <p>\n   * Time complexity: O(1)\n   * @see <a href=\"http://redis.io/commands/expire\">Expire Command</a>\n   * @param key\n   * @param unixTime\n   * @return 1: the timeout was set. 0: the timeout was not set since\n   *         the key already has an associated timeout (this may happen only in Redis versions &lt;\n   *         2.1.3, Redis &gt;= 2.1.3 will happily update the timeout), or the key does not exist.\n   */\n  @Override\n  public long expireAt(final String key, final long unixTime) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expireAt(key, unixTime));\n  }\n\n  /**\n   * Similar to {@link Jedis#expireAt(String, long) EXPIREAT} but with {@code ExpiryOption}.\n   * @see Jedis#expireAt(String, long)\n   * @param key\n   * @param unixTime time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  @Override\n  public long expireAt(String key, long unixTime, ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.expireAt(key, unixTime, expiryOption));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#expireAt(String, long) EXPIREAT} but\n   * Unix time at which the key will expire is specified in milliseconds instead of seconds.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param millisecondsTimestamp time to expire\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  @Override\n  public long pexpireAt(final String key, final long millisecondsTimestamp) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp));\n  }\n\n  /**\n   * <b><a href=\"http://redis.io/commands/expireat\">ExpireAt Command</a></b>\n   * Similar to {@link Jedis#pexpireAt(String, long) PEXPIREAT} but with {@code ExpiryOption}.\n   * @see Jedis#pexpireAt(String, long)\n   * @param key\n   * @param millisecondsTimestamp time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  @Override\n  public long pexpireAt(String key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption));\n  }\n\n  /**\n   * The TTL command returns the remaining time to live in seconds of a key that has an\n   * {@link Jedis#expire(String, long) EXPIRE} set. This introspection capability allows a Redis\n   * connection to check how many seconds a given key will continue to be part of the dataset.\n   * @param key\n   * @return TTL in seconds, or a negative value in order to signal an error\n\n   */\n  @Override\n  public long ttl(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.ttl(key));\n  }\n\n  /**\n   * Alters the last access time of a key(s). A key is ignored if it does not exist.\n   * Time complexity: O(N) where N is the number of keys that will be touched.\n   * @param keys\n   * @return The number of keys that were touched.\n   */\n  @Override\n  public long touch(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.touch(keys));\n  }\n\n  @Override\n  public long touch(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.touch(key));\n  }\n\n  /**\n   * Move the specified key from the currently selected DB to the specified destination DB. Note\n   * that this command returns 1 only if the key was successfully moved, and 0 if the target key was\n   * already there or if the source key was not found at all, so it is possible to use MOVE as a\n   * locking primitive.\n   * @param key\n   * @param dbIndex\n   * @return 1 if the key was moved, 0 if the key was not moved because already present on the target\n   * DB or was not found in the current DB\n   */\n  @Override\n  public long move(final String key, final int dbIndex) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MOVE, encode(key), toByteArray(dbIndex));\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * GETSET is an atomic set this value and return the old value command. Set key to the string\n   * value and return the old value stored at key. The string can't be longer than 1073741824 bytes\n   * (1 GB).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return Bulk reply\n   * @deprecated Use {@link Jedis#setGet(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  @Override\n  public String getSet(final String key, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getSet(key, value));\n  }\n\n  /**\n   * Get the values of all the specified keys. If one or more keys don't exist or is not of type\n   * String, a 'nil' value is returned instead of the value of the specified key, but the operation\n   * never fails.\n   * <p>\n   * Time complexity: O(1) for every key\n   * @param keys\n   * @return Multi bulk reply\n   */\n  @Override\n  public List<String> mget(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.mget(keys));\n  }\n\n  /**\n   * SETNX works exactly like {@link Jedis#set(String, String) SET} with the only difference that if\n   * the key already exists no operation is performed. SETNX actually means \"SET if Not eXists\".\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return 1 if the key was set, 0 if the key was not set\n   * @deprecated Use {@link Jedis#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public long setnx(final String key, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setnx(key, value));\n  }\n\n  /**\n   * The command is exactly equivalent to the following group of commands:\n   * {@link Jedis#set(String, String) SET} + {@link Jedis#expire(String, long) EXPIRE}. The\n   * operation is atomic.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param seconds\n   * @param value\n   * @return OK\n   * @deprecated Use {@link Jedis#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String setex(final String key, final long seconds, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setex(key, seconds, value));\n  }\n\n  /**\n   * Set the respective keys to the respective values. MSET will replace old values with new\n   * values, while {@link Jedis#msetnx(String...) MSETNX} will not perform any operation at all even\n   * if just a single key already exists.\n   * <p>\n   * Because of this semantic MSETNX can be used in order to set different keys representing\n   * different fields of an unique logic object in a way that ensures that either all the fields or\n   * none at all are set.\n   * <p>\n   * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B\n   * are modified, another connection talking to Redis can either see the changes to both A and B at\n   * once, or no modification at all.\n   * @see Jedis#msetnx(String...)\n   * @param keysvalues\n   * @return OK\n   */\n  @Override\n  public String mset(final String... keysvalues) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.mset(keysvalues));\n  }\n\n  /**\n   * Set the respective keys to the respective values. {@link Jedis#mset(String...) MSET} will\n   * replace old values with new values, while MSETNX will not perform any operation at all even if\n   * just a single key already exists.\n   * <p>\n   * Because of this semantic MSETNX can be used in order to set different keys representing\n   * different fields of an unique logic object in a way that ensures that either all the fields or\n   * none at all are set.\n   * <p>\n   * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B\n   * are modified, another connection talking to Redis can either see the changes to both A and B at\n   * once, or no modification at all.\n   * @see Jedis#mset(String...)\n   * @param keysvalues\n   * @return 1 if the all the keys were set, 0 if no key was set (at least one key already existed)\n   */\n  @Override\n  public long msetnx(final String... keysvalues) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.msetnx(keysvalues));\n  }\n\n  @Override\n  public boolean msetex(final MSetExParams params, final String... keysvalues) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.msetex(params, keysvalues));\n  }\n\n  /**\n   * IDECRBY work just like {@link Jedis#decr(String) INCR} but instead to decrement by 1 the\n   * decrement is integer.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(String)\n   * @see Jedis#decr(String)\n   * @see Jedis#incrBy(String, long)\n   * @param key\n   * @param decrement\n   * @return The value of key after the decrement\n   */\n  @Override\n  public long decrBy(final String key, final long decrement) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.decrBy(key, decrement));\n  }\n\n  /**\n   * Decrement the number stored at key by one. If the key does not exist or contains a value of a\n   * wrong type, set the key to the value of \"0\" before to perform the decrement operation.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(String)\n   * @see Jedis#incrBy(String, long)\n   * @see Jedis#decrBy(String, long)\n   * @param key\n   * @return The value of key after the decrement\n   */\n  @Override\n  public long decr(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.decr(key));\n  }\n\n  /**\n   * INCRBY work just like {@link Jedis#incr(String) INCR} but instead to increment by 1 the\n   * increment is integer.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incr(String)\n   * @see Jedis#decr(String)\n   * @see Jedis#decrBy(String, long)\n   * @param key\n   * @param increment\n   * @return The value of key after the increment\n   */\n  @Override\n  public long incrBy(final String key, final long increment) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.incrBy(key, increment));\n  }\n\n  /**\n   * INCRBYFLOAT\n   * <p>\n   * INCRBYFLOAT commands are limited to double precision floating point values.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"double\" types.\n   * Simply the string stored at the key is parsed as a base double precision floating point value,\n   * incremented, and then converted back as a string. There is no DECRYBYFLOAT but providing a\n   * negative value will work as expected.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param increment\n   * @return The value of key after the increment\n   */\n  @Override\n  public double incrByFloat(final String key, final double increment) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.incrByFloat(key, increment));\n  }\n\n  /**\n   * Increment the number stored at key by one. If the key does not exist or contains a value of a\n   * wrong type, set the key to the value of \"0\" before to perform the increment operation.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#incrBy(String, long)\n   * @see Jedis#decr(String)\n   * @see Jedis#decrBy(String, long)\n   * @param key\n   * @return The value of key after the increment\n   */\n  @Override\n  public long incr(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.incr(key));\n  }\n\n  /**\n   * If the key already exists and is a string, this command appends the provided value at the end\n   * of the string. If the key does not exist it is created and set as an empty string, so APPEND\n   * will be very similar to SET in this special case.\n   * <p>\n   * Time complexity: O(1). The amortized time complexity is O(1) assuming the appended value is\n   * small and the already present value is of any size, since the dynamic string library used by\n   * Redis will double the free space available on every reallocation.\n   * @param key\n   * @param value\n   * @return The total length of the string after the append operation.\n   */\n  @Override\n  public long append(final String key, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.append(key, value));\n  }\n\n  /**\n   * Return a subset of the string from offset start to offset end (both offsets are inclusive).\n   * Negative offsets can be used in order to provide an offset starting from the end of the string.\n   * So -1 means the last char, -2 the penultimate and so forth.\n   * <p>\n   * The function handles out of range requests without raising an error, but just limiting the\n   * resulting range to the actual length of the string.\n   * <p>\n   * Time complexity: O(start+n) (with start being the start index and n the total length of the\n   * requested range). Note that the lookup part of this command is O(1) so for small strings this\n   * is actually an O(1) command.\n   * @param key\n   * @param start\n   * @param end\n   * @return The substring\n   * @deprecated Use {@link Jedis#getrange(String, long, long)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  @Override\n  public String substr(final String key, final int start, final int end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.substr(key, start, end));\n  }\n\n  /**\n   * Set the specified hash field to the specified value.\n   * <p>\n   * If key does not exist, a new key holding a hash is created.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return If the field already exists, and the HSET just produced an update of the value, 0 is\n   *         returned, otherwise if a new field is created 1 is returned.\n   */\n  @Override\n  public long hset(final String key, final String field, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hset(key, field, value));\n  }\n\n  @Override\n  public long hset(final String key, final Map<String, String> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hset(key, hash));\n  }\n\n  @Override\n  public long hsetex(String key, HSetExParams params, String field, String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hsetex(key, params, field, value));\n  }\n\n  @Override\n  public long hsetex(String key, HSetExParams params, Map<String, String> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hsetex(key, params, hash));\n  }\n\n  /**\n   * If key holds a hash, retrieve the value associated to the specified field.\n   * <p>\n   * If the field is not found or the key does not exist, a special 'nil' value is returned.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @return Bulk reply\n   */\n  @Override\n  public String hget(final String key, final String field) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hget(key, field));\n  }\n\n  @Override\n  public List<String> hgetex(String key, HGetExParams params, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hgetex(key, params, fields));\n  }\n\n  @Override\n  public List<String> hgetdel(String key, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hgetdel(key, fields));\n  }\n\n  /**\n   * Set the specified hash field to the specified value if the field not exists. <b>Time\n   * complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return If the field already exists, 0 is returned, otherwise if a new field is created 1 is\n   *         returned.\n   */\n  @Override\n  public long hsetnx(final String key, final String field, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hsetnx(key, field, value));\n  }\n\n  /**\n   * Set the respective fields to the respective values. HMSET replaces old values with new values.\n   * <p>\n   * If key does not exist, a new key holding a hash is created.\n   * <p>\n   * <b>Time complexity:</b> O(N) (with N being the number of fields)\n   * @param key\n   * @param hash\n   * @return Return OK or Exception if hash is empty\n   * @deprecated Use {@link Jedis#hset(String, Map)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  @Override\n  public String hmset(final String key, final Map<String, String> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hmset(key, hash));\n  }\n\n  /**\n   * Retrieve the values associated to the specified fields.\n   * <p>\n   * If some of the specified fields do not exist, nil values are returned. Non existing keys are\n   * considered like empty hashes.\n   * <p>\n   * <b>Time complexity:</b> O(N) (with N being the number of fields)\n   * @param key\n   * @param fields\n   * @return A list of all the values associated with the specified fields, in the same order of the request.\n   */\n  @Override\n  public List<String> hmget(final String key, final String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hmget(key, fields));\n  }\n\n  /**\n   * Increment the number stored at field in the hash at key by value. If key does not exist, a new\n   * key holding a hash is created. If field does not exist or holds a string, the value is set to 0\n   * before applying the operation. Since the value argument is signed you can use this command to\n   * perform both increments and decrements.\n   * <p>\n   * The range of values supported by HINCRBY is limited to 64-bit signed integers.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return The value of key after the increment\n   */\n  @Override\n  public long hincrBy(final String key, final String field, final long value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hincrBy(key, field, value));\n  }\n\n  /**\n   * Increment the number stored at field in the hash at key by a double precision floating point\n   * value. If key does not exist, a new key holding a hash is created. If field does not exist or\n   * holds a string, the value is set to 0 before applying the operation. Since the value argument\n   * is signed you can use this command to perform both increments and decrements.\n   * <p>\n   * The range of values supported by HINCRBYFLOAT is limited to double precision floating point\n   * values.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @param value\n   * @return The new value at field after the increment operation\n   */\n  @Override\n  public double hincrByFloat(final String key, final String field, final double value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hincrByFloat(key, field, value));\n  }\n\n  /**\n   * Test for existence of a specified field in a hash. <b>Time complexity:</b> O(1)\n   * @param key\n   * @param field\n   * @return {@code true} if the hash stored at key contains the specified field, {@code false} if the key is\n   *         not found or the field is not present.\n   */\n  @Override\n  public boolean hexists(final String key, final String field) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexists(key, field));\n  }\n\n  /**\n   * Remove the specified field(s) from a hash stored at key. Specified fields that do not exist\n   * within this hash are ignored.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param fields\n   * @return The number of fields that were removed from the hash, not including specified but\n   *         non-existing fields. If key does not exist, it is treated as an empty hash and this\n   *         command returns 0.\n   */\n  @Override\n  public long hdel(final String key, final String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hdel(key, fields));\n  }\n\n  /**\n   * Return the number of items in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @return The number of entries (fields) contained in the hash stored at key. If the specified\n   *         key does not exist, 0 is returned assuming an empty hash.\n   */\n  @Override\n  public long hlen(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hlen(key));\n  }\n\n  /**\n   * Return all the fields in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the total number of entries\n   * @param key\n   * @return All the fields names contained into a hash.\n   */\n  @Override\n  public Set<String> hkeys(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hkeys(key));\n  }\n\n  /**\n   * Return all the values in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the total number of entries\n   * @param key\n   * @return All the fields values contained into a hash.\n   */\n  @Override\n  public List<String> hvals(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hvals(key));\n  }\n\n  /**\n   * Return all the fields and associated values in a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the total number of entries\n   * @param key\n   * @return All the fields and values contained into a hash.\n   */\n  @Override\n  public Map<String, String> hgetAll(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hgetAll(key));\n  }\n\n  /**\n   * Get one random field from a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the number of fields returned\n   * @param key\n   * @return one random field from a hash.\n   */\n  @Override\n  public String hrandfield(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hrandfield(key));\n  }\n\n  /**\n   * Get multiple random fields from a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the number of fields returned\n   * @param key\n   * @param count\n   * @return multiple random fields from a hash.\n   */\n  @Override\n  public List<String> hrandfield(final String key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hrandfield(key, count));\n  }\n\n  /**\n   * Get one or multiple random fields with values from a hash.\n   * <p>\n   * <b>Time complexity:</b> O(N), where N is the number of fields returned\n   * @param key\n   * @param count\n   * @return one or multiple random fields with values from a hash.\n   */\n  @Override\n  public List<Map.Entry<String, String>> hrandfieldWithValues(final String key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hrandfieldWithValues(key, count));\n  }\n\n  /**\n   * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key\n   * does not exist an empty list is created just before the append operation. If the key exists but\n   * is not a List an error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param strings\n   * @return The number of elements inside the list after the push operation\n   */\n  @Override\n  public long rpush(final String key, final String... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpush(key, strings));\n  }\n\n  /**\n   * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key\n   * does not exist an empty list is created just before the append operation. If the key exists but\n   * is not a List an error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param strings\n   * @return The number of elements inside the list after the push operation\n   */\n  @Override\n  public long lpush(final String key, final String... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpush(key, strings));\n  }\n\n  /**\n   * Return the length of the list stored at the specified key. If the key does not exist zero is\n   * returned (the same behaviour as for empty lists). If the value stored at key is not a list an\n   * error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The length of the list\n   */\n  @Override\n  public long llen(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.llen(key));\n  }\n\n  /**\n   * Return the specified elements of the list stored at the specified key. Start and end are\n   * zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and\n   * so on.\n   * <p>\n   * For example LRANGE foobar 0 2 will return the first three elements of the list.\n   * <p>\n   * start and end can also be negative numbers indicating offsets from the end of the list. For\n   * example -1 is the last element of the list, -2 the penultimate element and so on.\n   * <p>\n   * <b>Consistency with range functions in various programming languages</b>\n   * <p>\n   * Note that if you have a list of numbers from 0 to 100, LRANGE 0 10 will return 11 elements,\n   * that is, rightmost item is included. This may or may not be consistent with behavior of\n   * range-related functions in your programming language of choice (think Ruby's Range.new,\n   * Array#slice or Python's range() function).\n   * <p>\n   * LRANGE behavior is consistent with one of Tcl.\n   * <p>\n   * <b>Out-of-range indexes</b>\n   * <p>\n   * Indexes out of range will not produce an error: if start is over the end of the list, or start\n   * &gt; end, an empty list is returned. If end is over the end of the list Redis will threat it\n   * just like the last element of the list.\n   * <p>\n   * Time complexity: O(start+n) (with n being the length of the range and start being the start\n   * offset)\n   * @param key\n   * @param start\n   * @param stop\n   * @return A list of elements in the specified range\n   */\n  @Override\n  public List<String> lrange(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lrange(key, start, stop));\n  }\n\n  /**\n   * Trim an existing list so that it will contain only the specified range of elements specified.\n   * Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the\n   * next element and so on.\n   * <p>\n   * For example LTRIM foobar 0 2 will modify the list stored at foobar key so that only the first\n   * three elements of the list will remain.\n   * <p>\n   * start and end can also be negative numbers indicating offsets from the end of the list. For\n   * example -1 is the last element of the list, -2 the penultimate element and so on.\n   * <p>\n   * Indexes out of range will not produce an error: if start is over the end of the list, or start\n   * &gt; end, an empty list is left as value. If end over the end of the list Redis will threat it\n   * just like the last element of the list.\n   * <p>\n   * Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:\n   * <p>\n   * {@code lpush(\"mylist\", \"someelement\"); ltrim(\"mylist\", 0, 99); * }\n   * <p>\n   * The above two commands will push elements in the list taking care that the list will not grow\n   * without limits. This is very useful when using Redis to store logs for example. It is important\n   * to note that when used in this way LTRIM is an O(1) operation because in the average case just\n   * one element is removed from the tail of the list.\n   * <p>\n   * Time complexity: O(n) (with n being len of list - len of range)\n   * @param key\n   * @param start\n   * @param stop\n   * @return OK\n   */\n  @Override\n  public String ltrim(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.ltrim(key, start, stop));\n  }\n\n  /**\n   * Return the specified element of the list stored at the specified key. 0 is the first element, 1\n   * the second and so on. Negative indexes are supported, for example -1 is the last element, -2\n   * the penultimate and so on.\n   * <p>\n   * If the value stored at key is not of list type an error is returned. If the index is out of\n   * range a 'nil' reply is returned.\n   * <p>\n   * Note that even if the average time complexity is O(n) asking for the first or the last element\n   * of the list is O(1).\n   * <p>\n   * Time complexity: O(n) (with n being the length of the list)\n   * @param key\n   * @param index\n   * @return The requested element\n   */\n  @Override\n  public String lindex(final String key, final long index) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lindex(key, index));\n  }\n\n  /**\n   * Set a new value as the element at index position of the List at key.\n   * <p>\n   * Out of range indexes will generate an error.\n   * <p>\n   * Similarly to other list commands accepting indexes, the index can be negative to access\n   * elements starting from the end of the list. So -1 is the last element, -2 is the penultimate,\n   * and so forth.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(N) (with N being the length of the list), setting the first or last elements of the list is\n   * O(1).\n   * @see Jedis#lindex(String, long)\n   * @param key\n   * @param index\n   * @param value\n   * @return OK\n   */\n  @Override\n  public String lset(final String key, final long index, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lset(key, index, value));\n  }\n\n  /**\n   * Remove the first count occurrences of the value element from the list. If count is zero all the\n   * elements are removed. If count is negative elements are removed from tail to head, instead to\n   * go from head to tail that is the normal behaviour. So for example LREM with count -2 and hello\n   * as value to remove against the list (a,b,c,hello,x,hello,hello) will leave the list\n   * (a,b,c,hello,x). The number of removed elements is returned as an integer, see below for more\n   * information about the returned value. Note that non existing keys are considered like empty\n   * lists by LREM, so LREM against non existing keys will always return 0.\n   * <p>\n   * Time complexity: O(N) (with N being the length of the list)\n   * @param key\n   * @param count\n   * @param value\n   * @return The number of removed elements if the operation succeeded\n   */\n  @Override\n  public long lrem(final String key, final long count, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lrem(key, count, value));\n  }\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned.\n   * @see Jedis#rpop(String)\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public String lpop(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpop(key));\n  }\n\n  @Override\n  public List<String> lpop(final String key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpop(key, count));\n  }\n\n  @Override\n  public Long lpos(final String key, final String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpos(key, element));\n  }\n\n  @Override\n  public Long lpos(final String key, final String element, final LPosParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpos(key, element, params));\n  }\n\n  @Override\n  public List<Long> lpos(final String key, final String element, final LPosParams params,\n      final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpos(key, element, params, count));\n  }\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" RPOP will return \"c\" and the list will become\n   * \"a\",\"b\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned.\n   * @see Jedis#lpop(String)\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public String rpop(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpop(key));\n  }\n\n  @Override\n  public List<String> rpop(final String key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpop(key, count));\n  }\n\n  /**\n   * Atomically return and remove the last (tail) element of the srckey list, and push the element\n   * as the first (head) element of the dstkey list. For example if the source list contains the\n   * elements \"a\",\"b\",\"c\" and the destination list contains the elements \"foo\",\"bar\" after an\n   * RPOPLPUSH command the content of the two lists will be \"a\",\"b\" and \"c\",\"foo\",\"bar\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned. If\n   * the srckey and dstkey are the same the operation is equivalent to removing the last element\n   * from the list and pushing it as first element of the list, so it's a \"list rotation\" command.\n   * <p>\n   * Time complexity: O(1)\n   * @param srckey\n   * @param dstkey\n   * @return Bulk reply\n   * @deprecated Use {@link Jedis#lmove(String, String, ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public String rpoplpush(final String srckey, final String dstkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpoplpush(srckey, dstkey));\n  }\n\n  /**\n   * Add the specified member to the set value stored at key. If member is already a member of the\n   * set no operation is performed. If key does not exist a new set with the specified member as\n   * sole member is created. If the key exists but does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param members\n   * @return 1 if the new element was added, 0 if the element was already a member of the set\n   */\n  @Override\n  public long sadd(final String key, final String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sadd(key, members));\n  }\n\n  /**\n   * Return all the members (elements) of the set value stored at key. This is just syntax glue for\n   * {@link Jedis#sinter(String...) SINTER}.\n   * <p>\n   * Time complexity O(N)\n   * @param key\n   * @return Multi bulk reply\n   */\n  @Override\n  public Set<String> smembers(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.smembers(key));\n  }\n\n  /**\n   * Remove the specified member from the set value stored at key. If member was not a member of the\n   * set no operation is performed. If key does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param members\n   * @return 1 if the new element was removed, 0 if the new element was not a member of the set\n   */\n  @Override\n  public long srem(final String key, final String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.srem(key, members));\n  }\n\n  /**\n   * Remove a random element from a Set returning it as return value. If the Set is empty or the key\n   * does not exist, a nil object is returned.\n   * <p>\n   * The {@link Jedis#srandmember(String)} command does a similar work but the returned element is\n   * not removed from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return Bulk reply\n   */\n  @Override\n  public String spop(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.spop(key));\n  }\n\n  @Override\n  public Set<String> spop(final String key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.spop(key, count));\n  }\n\n  /**\n   * Move the specified member from the set at srckey to the set at dstkey. This operation is\n   * atomic, in every given moment the element will appear to be in the source or destination set\n   * for accessing clients.\n   * <p>\n   * If the source set does not exist or does not contain the specified element no operation is\n   * performed and zero is returned, otherwise the element is removed from the source set and added\n   * to the destination set. On success one is returned, even if the element was already present in\n   * the destination set.\n   * <p>\n   * An error is raised if the source or destination keys contain a non Set value.\n   * <p>\n   * Time complexity O(1)\n   * @param srckey\n   * @param dstkey\n   * @param member\n   * @return 1 if the element was moved, 0 if the element was not found\n   *         on the first set and no operation was performed\n   */\n  @Override\n  public long smove(final String srckey, final String dstkey, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.smove(srckey, dstkey, member));\n  }\n\n  /**\n   * Return the set cardinality (number of elements). If the key does not exist 0 is returned, like\n   * for empty sets.\n   * @param key\n   * @return The cardinality (number of elements) of the set as an integer\n   */\n  @Override\n  public long scard(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.scard(key));\n  }\n\n  /**\n   * Return true if member is a member of the set stored at key, otherwise false is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param member\n   * @return {@code true} if the element is a member of the set, {@code false} otherwise\n   */\n  @Override\n  public boolean sismember(final String key, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sismember(key, member));\n  }\n\n  /**\n   * Returns whether each member is a member of the set stored at key.\n   * <p>\n   * Time complexity O(N) where N is the number of elements being checked for membership\n   * @param key\n   * @param members\n   * @return List representing the membership of the given elements, in the same order as they are requested\n   */\n  @Override\n  public List<Boolean> smismember(final String key, final String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.smismember(key, members));\n  }\n\n  /**\n   * Return the members of a set resulting from the intersection of all the sets hold at the\n   * specified keys. Like in {@link Jedis#lrange(String, long, long) LRANGE} the result is sent to\n   * the connection as a multi-bulk reply (see the protocol specification for more information). If\n   * just a single key is specified, then this command produces the same result as\n   * {@link Jedis#smembers(String) SMEMBERS}. Actually SMEMBERS is just syntax sugar for SINTER.\n   * <p>\n   * Non existing keys are considered like empty sets, so if one of the keys is missing an empty set\n   * is returned (since the intersection with an empty set always is an empty set).\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the\n   * number of sets\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public Set<String> sinter(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sinter(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sinter(String...) SINTER} but instead of being\n   * returned the resulting set is stored as dstkey.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the\n   * number of sets\n   * @param dstkey\n   * @param keys\n   * @return The number of elements in the resulting set\n   */\n  @Override\n  public long sinterstore(final String dstkey, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sinterstore(dstkey, keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sinter(String...) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param keys\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  @Override\n  public long sintercard(String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sintercard(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sinter(String...) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param limit If the intersection cardinality reaches limit partway through the computation,\n   *              the algorithm will exit and yield limit as the cardinality.\n   * @param keys\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  @Override\n  public long sintercard(int limit, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sintercard(limit, keys));\n  }\n\n  /**\n   * Return the members of a set resulting from the union of all the sets hold at the specified\n   * keys. Like in {@link Jedis#lrange(String, long, long) LRANGE} the result is sent to the\n   * connection as a multi-bulk reply (see the protocol specification for more information). If just\n   * a single key is specified, then this command produces the same result as\n   * {@link Jedis#smembers(String) SMEMBERS}.\n   * <p>\n   * Non existing keys are considered like empty sets.\n   * <p>\n   * Time complexity O(N) where N is the total number of elements in all the provided sets\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public Set<String> sunion(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sunion(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sunion(String...) SUNION} but instead of being\n   * returned the resulting set is stored as dstkey. Any existing value in dstkey will be\n   * over-written.\n   * <p>\n   * Time complexity O(N) where N is the total number of elements in all the provided sets\n   * @param dstkey\n   * @param keys\n   * @return The number of elements in the resulting set\n   */\n  @Override\n  public long sunionstore(final String dstkey, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sunionstore(dstkey, keys));\n  }\n\n  /**\n   * Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN\n   * <p>\n   * <b>Example:</b>\n   *\n   * <pre>\n   * key1 = [x, a, b, c]\n   * key2 = [c]\n   * key3 = [a, d]\n   * SDIFF key1,key2,key3 =&gt; [x, b]\n   * </pre>\n   *\n   * Non existing keys are considered like empty sets.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(N) with N being the total number of elements of all the sets\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public Set<String> sdiff(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sdiff(keys));\n  }\n\n  /**\n   * This command works exactly like {@link Jedis#sdiff(String...) SDIFF} but instead of being\n   * returned the resulting set is stored in dstkey.\n   * @param dstkey\n   * @param keys\n   * @return The number of elements in the resulting set\n   */\n  @Override\n  public long sdiffstore(final String dstkey, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sdiffstore(dstkey, keys));\n  }\n\n  /**\n   * Return a random element from a Set, without removing the element. If the Set is empty or the\n   * key does not exist, a nil object is returned.\n   * <p>\n   * The SPOP command does a similar work but the returned element is popped (removed) from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The randomly selected element\n   */\n  @Override\n  public String srandmember(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.srandmember(key));\n  }\n\n  /**\n   * Return a random elements from a Set, without removing the elements. If the Set is empty or the\n   * key does not exist, an empty list is returned.\n   * <p>\n   * The SPOP command does a similar work but the returned elements is popped (removed) from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param count if positive, return an array of distinct elements.\n   *        If negative the behavior changes and the command is allowed to\n   *        return the same element multiple times\n   * @return A list of randomly selected elements\n   */\n  @Override\n  public List<String> srandmember(final String key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.srandmember(key, count));\n  }\n\n  /**\n   * Add the specified member having the specified score to the sorted set stored at key. If member\n   * is already a member of the sorted set the score is updated, and the element reinserted in the\n   * right position to ensure sorting. If key does not exist a new sorted set with the specified\n   * member as sole member is created. If the key exists but does not hold a sorted set value an\n   * error is returned.\n   * <p>\n   * The score value can be the string representation of a double precision floating point number.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param score\n   * @param member\n   * @return 1 if the new element was added, 0 if the element was already a member of the sorted\n   * set and the score was updated\n   */\n  @Override\n  public long zadd(final String key, final double score, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, score, member));\n  }\n\n  @Override\n  public long zadd(final String key, final double score, final String member,\n      final ZAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, score, member, params));\n  }\n\n  @Override\n  public long zadd(final String key, final Map<String, Double> scoreMembers) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, scoreMembers));\n  }\n\n  @Override\n  public long zadd(final String key, final Map<String, Double> scoreMembers, final ZAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zadd(key, scoreMembers, params));\n  }\n\n  @Override\n  public Double zaddIncr(final String key, final double score, final String member, final ZAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zaddIncr(key, score, member, params));\n  }\n\n  @Override\n  public List<String> zdiff(String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiff(keys));\n  }\n\n  @Override\n  public List<Tuple> zdiffWithScores(String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiffWithScores(keys));\n  }\n\n  @Override\n  @Deprecated\n  public long zdiffStore(final String dstkey, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiffStore(dstkey, keys));\n  }\n\n  @Override\n  public long zdiffstore(final String dstkey, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public List<String> zrange(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrange(key, start, stop));\n  }\n\n  /**\n   * Remove the specified member from the sorted set value stored at key. If member was not a member\n   * of the set no operation is performed. If key does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param members\n   * @return 1 if the new element was removed, 0 if the new element was not a member of the set\n   */\n  @Override\n  public long zrem(final String key, final String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrem(key, members));\n  }\n\n  /**\n   * If member already exists in the sorted set adds the increment to its score and updates the\n   * position of the element in the sorted set accordingly. If member does not already exist in the\n   * sorted set it is added with increment as score (that is, like if the previous score was\n   * virtually zero). If key does not exist a new sorted set with the specified member as sole\n   * member is created. If the key exists but does not hold a sorted set value an error is returned.\n   * <p>\n   * The score value can be the string representation of a double precision floating point number.\n   * It's possible to provide a negative value to perform a decrement.\n   * <p>\n   * For an introduction to sorted sets check the Introduction to Redis data types page.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param increment\n   * @param member\n   * @return The new score\n   */\n  @Override\n  public double zincrby(final String key, final double increment, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zincrby(key, increment, member));\n  }\n\n  @Override\n  public Double zincrby(final String key, final double increment, final String member,\n      final ZIncrByParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zincrby(key, increment, member, params));\n  }\n\n  /**\n   * Return the rank (or index) of member in the sorted set at key, with scores being ordered from\n   * low to high.\n   * <p>\n   * When the given member does not exist in the sorted set, the special value 'nil' is returned.\n   * The returned rank (or index) of the member is 0-based for both commands.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))\n   * @see Jedis#zrevrank(String, String)\n   * @param key\n   * @param member\n   * @return The element as an integer if the element exists. A 'nil' bulk reply if there is no such element.\n   */\n  @Override\n  public Long zrank(final String key, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrank(key, member));\n  }\n\n  /**\n   * Return the rank (or index) of member in the sorted set at key, with scores being ordered from\n   * high to low.\n   * <p>\n   * When the given member does not exist in the sorted set, the special value 'nil' is returned.\n   * The returned rank (or index) of the member is 0-based for both commands.\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))\n   * @see Jedis#zrank(String, String)\n   * @param key\n   * @param member\n   * @return The element as an integer if the element exists. A 'nil' bulk reply if there is no such element.\n   */\n  @Override\n  public Long zrevrank(final String key, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrank(key, member));\n  }\n\n  /**\n   * Returns the rank and the score of member in the sorted set stored at key, with the scores\n   * ordered from low to high.\n   * @param key the key\n   * @param member the member\n   * @return the KeyValue contains rank and score.\n   */\n  @Override\n  public KeyValue<Long, Double> zrankWithScore(String key, String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrankWithScore(key, member));\n  }\n\n  /**\n   * Returns the rank and the score of member in the sorted set stored at key, with the scores\n   * ordered from high to low.\n   * @param key the key\n   * @param member the member\n   * @return the KeyValue contains rank and score.\n   */\n  @Override\n  public KeyValue<Long, Double> zrevrankWithScore(String key, String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrankWithScore(key, member));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrange(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrange(key, start, stop));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeWithScores(key, start, stop));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeWithScores(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public List<String> zrange(String key, ZRangeParams zRangeParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrange(key, zRangeParams));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(String key, ZRangeParams zRangeParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeWithScores(key, zRangeParams));\n  }\n\n  @Override\n  public long zrangestore(String dest, String src, ZRangeParams zRangeParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangestore(dest, src, zRangeParams));\n  }\n\n  @Override\n  public String zrandmember(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrandmember(key));\n  }\n\n  @Override\n  public List<String> zrandmember(final String key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrandmember(key, count));\n  }\n\n  @Override\n  public List<Tuple> zrandmemberWithScores(final String key, final long count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrandmemberWithScores(key, count));\n  }\n\n  /**\n   * Return the sorted set cardinality (number of elements). If the key does not exist 0 is\n   * returned, like for empty sorted sets.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The cardinality (number of elements) of the set as an integer\n   */\n  @Override\n  public long zcard(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zcard(key));\n  }\n\n  /**\n   * Return the score of the specified element of the sorted set at key. If the specified element\n   * does not exist in the sorted set, or the key does not exist at all, a special 'nil' value is\n   * returned.\n   * <p>\n   * <b>Time complexity:</b> O(1)\n   * @param key\n   * @param member\n   * @return The score\n   */\n  @Override\n  public Double zscore(final String key, final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zscore(key, member));\n  }\n\n  /**\n   * Returns the scores associated with the specified members in the sorted set stored at key. For\n   * every member that does not exist in the sorted set, a nil value is returned.\n   * <p>\n   * <b>Time complexity:</b> O(N) where N is the number of members being requested.\n   * @param key\n   * @param members\n   * @return The scores\n   */\n  @Override\n  public List<Double> zmscore(final String key, final String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zmscore(key, members));\n  }\n\n  @Override\n  public Tuple zpopmax(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmax(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmax(final String key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmax(key, count));\n  }\n\n  @Override\n  public Tuple zpopmin(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmin(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmin(final String key, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zpopmin(key, count));\n  }\n\n  public String watch(final String... keys) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(WATCH, keys);\n//    return connection.getStatusCodeReply();\n    String status = connection.getStatusCodeReply();\n    isInWatch = true;\n    return status;\n  }\n\n  /**\n   * Sort a Set or a List.\n   * <p>\n   * Sort the elements contained in the List, Set, or Sorted Set value at key. By default sorting is\n   * numeric with elements being compared as double precision floating point numbers. This is the\n   * simplest form of SORT.\n   * @see Jedis#sort(String, String)\n   * @see Jedis#sort(String, SortingParams)\n   * @see Jedis#sort(String, SortingParams, String)\n   * @param key\n   * @return Assuming the Set/List at key contains a list of numbers, the return value will be the\n   *         list of numbers ordered from the smallest to the biggest number.\n   */\n  @Override\n  public List<String> sort(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key));\n  }\n\n  /**\n   * Sort a Set or a List accordingly to the specified parameters.\n   * <p>\n   * <b>examples:</b>\n   * <p>\n   * Given are the following sets and key/values:\n   *\n   * <pre>\n   * x = [1, 2, 3]\n   * y = [a, b, c]\n   *\n   * k1 = z\n   * k2 = y\n   * k3 = x\n   *\n   * w1 = 9\n   * w2 = 8\n   * w3 = 7\n   * </pre>\n   *\n   * Sort Order:\n   *\n   * <pre>\n   * sort(x) or sort(x, sp.asc())\n   * -&gt; [1, 2, 3]\n   *\n   * sort(x, sp.desc())\n   * -&gt; [3, 2, 1]\n   *\n   * sort(y)\n   * -&gt; [c, a, b]\n   *\n   * sort(y, sp.alpha())\n   * -&gt; [a, b, c]\n   *\n   * sort(y, sp.alpha().desc())\n   * -&gt; [c, a, b]\n   * </pre>\n   *\n   * Limit (e.g. for Pagination):\n   *\n   * <pre>\n   * sort(x, sp.limit(0, 2))\n   * -&gt; [1, 2]\n   *\n   * sort(y, sp.alpha().desc().limit(1, 2))\n   * -&gt; [b, a]\n   * </pre>\n   *\n   * Sorting by external keys:\n   *\n   * <pre>\n   * sort(x, sb.by(w*))\n   * -&gt; [3, 2, 1]\n   *\n   * sort(x, sb.by(w*).desc())\n   * -&gt; [1, 2, 3]\n   * </pre>\n   *\n   * Getting external keys:\n   *\n   * <pre>\n   * sort(x, sp.by(w*).get(k*))\n   * -&gt; [x, y, z]\n   *\n   * sort(x, sp.by(w*).get(#).get(k*))\n   * -&gt; [3, x, 2, y, 1, z]\n   * </pre>\n   * @see Jedis#sort(String)\n   * @see Jedis#sort(String, SortingParams, String)\n   * @param key\n   * @param sortingParams\n   * @return a list of sorted elements.\n   */\n  @Override\n  public List<String> sort(final String key, final SortingParams sortingParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key, sortingParams));\n  }\n\n  /**\n   * Sort a Set or a List accordingly to the specified parameters and store the result at dstkey.\n   * @see Jedis#sort(String, SortingParams)\n   * @see Jedis#sort(String)\n   * @see Jedis#sort(String, String)\n   * @param key\n   * @param sortingParams\n   * @param dstkey\n   * @return The number of elements of the list at dstkey\n   */\n  @Override\n  public long sort(final String key, final SortingParams sortingParams, final String dstkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key, sortingParams, dstkey));\n  }\n\n  @Override\n  public List<String> sortReadonly(String key, SortingParams sortingParams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sortReadonly(key, sortingParams));\n  }\n\n  /**\n   * Sort a Set or a List and Store the Result at dstkey.\n   * <p>\n   * Sort the elements contained in the List, Set, or Sorted Set value at key and store the result\n   * at dstkey. By default sorting is numeric with elements being compared as double precision\n   * floating point numbers. This is the simplest form of SORT.\n   * @see Jedis#sort(String)\n   * @see Jedis#sort(String, SortingParams)\n   * @see Jedis#sort(String, SortingParams, String)\n   * @param key\n   * @param dstkey\n   * @return The number of elements of the list at dstkey\n   */\n  @Override\n  public long sort(final String key, final String dstkey) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sort(key, dstkey));\n  }\n\n  @Override\n  public String lmove(final String srcKey, final String dstKey, final ListDirection from,\n      final ListDirection to) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lmove(srcKey, dstKey, from, to));\n  }\n\n  @Override\n  public String blmove(final String srcKey, final String dstKey, final ListDirection from,\n      final ListDirection to, final double timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blmove(srcKey, dstKey, from, to, timeout));\n  }\n\n  /**\n   * BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commands as blocking\n   * versions of LPOP and RPOP able to block if the specified keys don't exist or contain empty\n   * lists.\n   * <p>\n   * The following is a description of the exact semantic. We describe BLPOP but the two commands\n   * are identical, the only difference is that BLPOP pops the element from the left (head) of the\n   * list, and BRPOP pops from the right (tail).\n   * <p>\n   * <b>Non blocking behavior</b>\n   * <p>\n   * When BLPOP is called, if at least one of the specified keys contain a non empty list, an\n   * element is popped from the head of the list and returned to the caller together with the name\n   * of the key (BLPOP returns a two elements array, the first element is the key, the second the\n   * popped value).\n   * <p>\n   * Keys are scanned from left to right, so for instance if you issue BLPOP list1 list2 list3 0\n   * against a dataset where list1 does not exist but list2 and list3 contain non empty lists, BLPOP\n   * guarantees to return an element from the list stored at list2 (since it is the first non empty\n   * list starting from the left).\n   * <p>\n   * <b>Blocking behavior</b>\n   * <p>\n   * If none of the specified keys exist or contain non empty lists, BLPOP blocks until some other\n   * connection performs a LPUSH or an RPUSH operation against one of the lists.\n   * <p>\n   * Once new data is present on one of the lists, the connection finally returns with the name of the\n   * key unblocking it and the popped value.\n   * <p>\n   * When blocking, if a non-zero timeout is specified, the connection will unblock returning a nil\n   * special value if the specified amount of seconds passed without a push operation against at\n   * least one of the specified keys.\n   * <p>\n   * The timeout argument is interpreted as an integer value. A timeout of zero means instead to\n   * block forever.\n   * <p>\n   * <b>Multiple clients blocking for the same keys</b>\n   * <p>\n   * Multiple clients can block for the same key. They are put into a queue, so the first to be\n   * served will be the one that started to wait earlier, in a first-blpopping first-served fashion.\n   * <p>\n   * <b>blocking POP inside a MULTI/EXEC transaction</b>\n   * <p>\n   * BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies\n   * in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis\n   * transaction).\n   * <p>\n   * The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil\n   * reply, exactly what happens when the timeout is reached. If you like science fiction, think at\n   * it like if inside MULTI/EXEC the time will flow at infinite speed :)\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#brpop(int, String...)\n   * @param timeout\n   * @param keys\n   * @return BLPOP returns a two-elements array via a multi bulk reply in order to return both the\n   *         unblocking key and the popped value.\n   *         <p>\n         When a non-zero timeout is specified, and the BLPOP operation timed out, the return\n         value is a nil multi bulk reply. Most connection values will return false or nil\n         accordingly to the programming language used.\n   */\n  @Override\n  public List<String> blpop(final int timeout, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, String> blpop(final double timeout, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  /**\n   * BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commands as blocking\n   * versions of LPOP and RPOP able to block if the specified keys don't exist or contain empty\n   * lists.\n   * <p>\n   * The following is a description of the exact semantic. We describe BLPOP but the two commands\n   * are identical, the only difference is that BLPOP pops the element from the left (head) of the\n   * list, and BRPOP pops from the right (tail).\n   * <p>\n   * <b>Non blocking behavior</b>\n   * <p>\n   * When BLPOP is called, if at least one of the specified keys contain a non empty list, an\n   * element is popped from the head of the list and returned to the caller together with the name\n   * of the key (BLPOP returns a two elements array, the first element is the key, the second the\n   * popped value).\n   * <p>\n   * Keys are scanned from left to right, so for instance if you issue BLPOP list1 list2 list3 0\n   * against a dataset where list1 does not exist but list2 and list3 contain non empty lists, BLPOP\n   * guarantees to return an element from the list stored at list2 (since it is the first non empty\n   * list starting from the left).\n   * <p>\n   * <b>Blocking behavior</b>\n   * <p>\n   * If none of the specified keys exist or contain non empty lists, BLPOP blocks until some other\n   * connection performs a LPUSH or an RPUSH operation against one of the lists.\n   * <p>\n   * Once new data is present on one of the lists, the connection finally returns with the name of the\n   * key unblocking it and the popped value.\n   * <p>\n   * When blocking, if a non-zero timeout is specified, the connection will unblock returning a nil\n   * special value if the specified amount of seconds passed without a push operation against at\n   * least one of the specified keys.\n   * <p>\n   * The timeout argument is interpreted as an integer value. A timeout of zero means instead to\n   * block forever.\n   * <p>\n   * <b>Multiple clients blocking for the same keys</b>\n   * <p>\n   * Multiple clients can block for the same key. They are put into a queue, so the first to be\n   * served will be the one that started to wait earlier, in a first-blpopping first-served fashion.\n   * <p>\n   * <b>blocking POP inside a MULTI/EXEC transaction</b>\n   * <p>\n   * BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies\n   * in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis\n   * transaction).\n   * <p>\n   * The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil\n   * reply, exactly what happens when the timeout is reached. If you like science fiction, think at\n   * it like if inside MULTI/EXEC the time will flow at infinite speed :)\n   * <p>\n   * Time complexity: O(1)\n   * @see Jedis#blpop(int, String...)\n   * @param timeout\n   * @param keys\n   * @return BLPOP returns a two-elements array via a multi bulk reply in order to return both the\n   *         unblocking key and the popped value.\n   *         <p>\n         When a non-zero timeout is specified, and the BLPOP operation timed out, the return\n         value is a nil multi bulk reply. Most connection values will return false or nil\n         accordingly to the programming language used.\n   */\n  @Override\n  public List<String> brpop(final int timeout, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, String> brpop(final double timeout, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> lmpop(ListDirection direction, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lmpop(direction, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> lmpop(ListDirection direction, int count, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lmpop(direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> blmpop(double timeout, ListDirection direction, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blmpop(timeout, direction, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> blmpop(double timeout, ListDirection direction, int count, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blmpop(timeout, direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<String, Tuple> bzpopmax(double timeout, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bzpopmax(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, Tuple> bzpopmin(double timeout, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bzpopmin(timeout, keys));\n  }\n\n  @Override\n  public List<String> blpop(final int timeout, final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blpop(timeout, key));\n  }\n\n  @Override\n  public KeyValue<String, String> blpop(double timeout, String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.blpop(timeout, key));\n  }\n\n  @Override\n  public List<String> brpop(final int timeout, final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.brpop(timeout, key));\n  }\n\n  @Override\n  public KeyValue<String, String> brpop(double timeout, String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.brpop(timeout, key));\n  }\n\n  @Override\n  public long zcount(final String key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public long zcount(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(String, double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(String, double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(String, double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(String, double, double)\n   * @see Jedis#zrangeByScore(String, double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double)\n   * @see Jedis#zrangeByScoreWithScores(String, String, String)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double, int, int)\n   * @see Jedis#zcount(String, double, double)\n   * @param key\n   * @param min a double or Double.NEGATIVE_INFINITY for \"-inf\"\n   * @param max a double or Double.POSITIVE_INFINITY for \"+inf\"\n   * @return A list of elements in the specified score range\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(final String key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(String, double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(String, double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(String, double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(String, double, double)\n   * @see Jedis#zrangeByScore(String, double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double, int, int)\n   * @see Jedis#zcount(String, double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @param offset\n   * @param count\n   * @return A list of elements in the specified score range\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(final String key, final double min, final double max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(final String key, final String min, final String max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(String, double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(String, double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(String, double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(String, double, double)\n   * @see Jedis#zrangeByScore(String, double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double, int, int)\n   * @see Jedis#zcount(String, double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @return A list of elements in the specified score range\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final String key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * Return the all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max).\n   * <p>\n   * The elements having the same score are returned sorted lexicographically as ASCII strings (this\n   * follows from a property of Redis sorted sets and does not involve further computation).\n   * <p>\n   * Using the optional {@link Jedis#zrangeByScore(String, double, double, int, int) LIMIT} it is\n   * possible to get only a range of the matching elements in an SQL-alike way. Note that if offset\n   * is large the commands needs to traverse the list for offset elements and this adds up to the\n   * O(M) figure.\n   * <p>\n   * The {@link Jedis#zcount(String, double, double) ZCOUNT} command is similar to\n   * {@link Jedis#zrangeByScore(String, double, double) ZRANGEBYSCORE} but instead of returning the\n   * actual elements in the specified interval, it just returns the number of matching elements.\n   * <p>\n   * <b>Exclusive intervals and infinity</b>\n   * <p>\n   * min and max can be -inf and +inf, so that you are not required to know what's the greatest or\n   * smallest element in order to take, for instance, elements \"up to a given value\".\n   * <p>\n   * Also while the interval is for default closed (inclusive) it is possible to specify open\n   * intervals prefixing the score with a \"(\" character, so for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (1.3 5}\n   * <p>\n   * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:\n   * <p>\n   * {@code ZRANGEBYSCORE zset (5 (10}\n   * <p>\n   * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements returned by the command, so if M is constant (for instance you always ask for the\n   * first ten elements with LIMIT) you can consider it O(log(N))\n   * @see Jedis#zrangeByScore(String, double, double)\n   * @see Jedis#zrangeByScore(String, double, double, int, int)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double)\n   * @see Jedis#zrangeByScoreWithScores(String, double, double, int, int)\n   * @see Jedis#zcount(String, double, double)\n   * @param key\n   * @param min\n   * @param max\n   * @param offset\n   * @param count\n   * @return A list of elements in the specified score range\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final String key, final double min, final double max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(final String key, final String min, final String max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(final String key, final double max, final double min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(final String key, final String max, final String min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(final String key, final double max, final double min,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final String key, final double max, final double min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final String key, final double max,\n      final double min, final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final String key, final String max,\n      final String min, final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(final String key, final String max, final String min,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(final String key, final String max, final String min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  /**\n   * Remove all elements in the sorted set at key with rank between start and end. Start and end are\n   * 0-based with rank 0 being the element with the lowest score. Both start and end can be negative\n   * numbers, where they indicate offsets starting at the element with the highest rank. For\n   * example: -1 is the element with the highest score, -2 the element with the second highest score\n   * and so forth.\n   * <p>\n   * <b>Time complexity:</b> O(log(N))+O(M) with N being the number of elements in the sorted set\n   * and M the number of elements removed by the operation\n   * @param key\n   * @param start\n   * @param stop\n   */\n  @Override\n  public long zremrangeByRank(final String key, final long start, final long stop) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByRank(key, start, stop));\n  }\n\n  /**\n   * Remove all the elements in the sorted set at key with a score between min and max (including\n   * elements with score equal to min or max).\n   * <p>\n   * <b>Time complexity:</b>\n   * <p>\n   * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of\n   * elements removed by the operation\n   * @param key\n   * @param min\n   * @param max\n   * @return The number of elements removed\n   */\n  @Override\n  public long zremrangeByScore(final String key, final double min, final double max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public long zremrangeByScore(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  /**\n   * Add multiple sorted sets, This command is similar to ZUNIONSTORE, but instead of storing the\n   * resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public List<String> zunion(ZParams params, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunion(params, keys));\n  }\n\n  /**\n   * Add multiple sorted sets with scores, This command is similar to ZUNIONSTORE, but instead of\n   * storing the resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return A set with members of the resulting set with scores\n   */\n  @Override\n  public List<Tuple> zunionWithScores(ZParams params, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunionWithScores(params, keys));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(String, String...) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(String, String...) ZUNIONSTORE} command inserts all elements across\n   * all inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @see Jedis#zunionstore(String, String...)\n   * @see Jedis#zunionstore(String, ZParams, String...)\n   * @see Jedis#zinterstore(String, String...)\n   * @see Jedis#zinterstore(String, ZParams, String...)\n   * @param dstkey\n   * @param sets\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zunionstore(final String dstkey, final String... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunionstore(dstkey, sets));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(String, String...) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(String, String...) ZUNIONSTORE} command inserts all elements across\n   * all inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @see Jedis#zunionstore(String, String...)\n   * @see Jedis#zunionstore(String, ZParams, String...)\n   * @see Jedis#zinterstore(String, String...)\n   * @see Jedis#zinterstore(String, ZParams, String...)\n   * @param dstkey\n   * @param sets\n   * @param params\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zunionstore(final String dstkey, final ZParams params, final String... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zunionstore(dstkey, params, sets));\n  }\n\n  /**\n   * Intersect multiple sorted sets, This command is similar to ZINTERSTORE, but instead of storing\n   * the resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return A set with members of the resulting set\n   */\n  @Override\n  public List<String> zinter(final ZParams params, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinter(params, keys));\n  }\n\n  /**\n   * Intersect multiple sorted sets, This command is similar to ZINTERSTORE, but instead of storing\n   * the resulting sorted set, it is returned to the connection.\n   * @param params\n   * @param keys\n   * @return A set with members of the resulting set with scores\n   */\n  @Override\n  public List<Tuple> zinterWithScores(final ZParams params, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinterWithScores(params, keys));\n  }\n\n  @Override\n  public long zintercard(String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zintercard(keys));\n  }\n\n  @Override\n  public long zintercard(long limit, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zintercard(limit, keys));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(String, String...) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(String, String...) ZUNIONSTORE} command inserts all elements across\n   * all inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @see Jedis#zunionstore(String, String...)\n   * @see Jedis#zunionstore(String, ZParams, String...)\n   * @see Jedis#zinterstore(String, String...)\n   * @see Jedis#zinterstore(String, ZParams, String...)\n   * @param dstkey\n   * @param sets\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zinterstore(final String dstkey, final String... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinterstore(dstkey, sets));\n  }\n\n  /**\n   * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at\n   * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys\n   * and the other (optional) arguments.\n   * <p>\n   * As the terms imply, the {@link Jedis#zinterstore(String, String...) ZINTERSTORE} command\n   * requires an element to be present in each of the given inputs to be inserted in the result. The\n   * {@link Jedis#zunionstore(String, String...) ZUNIONSTORE} command inserts all elements across\n   * all inputs.\n   * <p>\n   * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means\n   * that the score of each element in the sorted set is first multiplied by this weight before\n   * being passed to the aggregation. When this option is not given, all weights default to 1.\n   * <p>\n   * With the AGGREGATE option, it is possible to specify how the results of the union or\n   * intersection are aggregated. This option defaults to SUM, where the score of an element is\n   * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the\n   * resulting set will contain the minimum or maximum score of an element across the inputs where\n   * it exists.\n   * <p>\n   * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input\n   * sorted sets, and M being the number of elements in the resulting sorted set\n   * @see Jedis#zunionstore(String, String...)\n   * @see Jedis#zunionstore(String, ZParams, String...)\n   * @see Jedis#zinterstore(String, String...)\n   * @see Jedis#zinterstore(String, ZParams, String...)\n   * @param dstkey\n   * @param sets\n   * @param params\n   * @return The number of elements in the sorted set at dstkey\n   */\n  @Override\n  public long zinterstore(final String dstkey, final ZParams params, final String... sets) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zinterstore(dstkey, params, sets));\n  }\n\n  @Override\n  public long zlexcount(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zlexcount(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByLex(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByLex(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByLex(final String key, final String min, final String max,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByLex(final String key, final String max, final String min) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByLex(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByLex(final String key, final String max, final String min,\n      final int offset, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n  }\n\n  @Override\n  public long zremrangeByLex(final String key, final String min, final String max) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zremrangeByLex(key, min, max));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> zmpop(SortedSetOption option, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zmpop(option, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> zmpop(SortedSetOption option, int count, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zmpop(option, count, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> bzmpop(double timeout, SortedSetOption option, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bzmpop(timeout, option, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> bzmpop(double timeout, SortedSetOption option, int count, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bzmpop(timeout, option, count, keys));\n  }\n\n  @Override\n  public long strlen(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.strlen(key));\n  }\n\n  /**\n   * Calculate the longest common subsequence of keyA and keyB.\n   * @param keyA\n   * @param keyB\n   * @param params\n   * @return According to LCSParams to decide to return content to fill LCSMatchResult.\n   */\n  @Override\n  public LCSMatchResult lcs(final String keyA, final String keyB, final LCSParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lcs(keyA, keyB, params));\n  }\n\n  @Override\n  public long lpushx(final String key, final String... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.lpushx(key, strings));\n  }\n\n  /**\n   * Undo a {@link Jedis#expire(String, long) expire} at turning the expire key into a normal key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return 1 if the key is now persist, 0 if the key is not persist (only happens when key not set)\n   */\n  @Override\n  public long persist(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.persist(key));\n  }\n\n  @Override\n  public long rpushx(final String key, final String... strings) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.rpushx(key, strings));\n  }\n\n  @Override\n  public String echo(final String string) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ECHO, string);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public long linsert(final String key, final ListPosition where, final String pivot,\n      final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.linsert(key, where, pivot, value));\n  }\n\n  /**\n   * Pop a value from a list, push it to another list and return it; or block until one is available\n   * @param source\n   * @param destination\n   * @param timeout\n   * @return The element\n   * @deprecated Use {@link Jedis#blmove(String, String, ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public String brpoplpush(final String source, final String destination, final int timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.brpoplpush(source, destination, timeout));\n  }\n\n  /**\n   * Sets or clears the bit at offset in the string value stored at key\n   * @param key\n   * @param offset\n   * @param value\n   */\n  @Override\n  public boolean setbit(final String key, final long offset, final boolean value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setbit(key, offset, value));\n  }\n\n  /**\n   * Returns the bit value at offset in the string value stored at key\n   * @param key\n   * @param offset\n   */\n  @Override\n  public boolean getbit(final String key, final long offset) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getbit(key, offset));\n  }\n\n  @Override\n  public long setrange(final String key, final long offset, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.setrange(key, offset, value));\n  }\n\n  @Override\n  public String getrange(final String key, final long startOffset, final long endOffset) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.getrange(key, startOffset, endOffset));\n  }\n\n  @Override\n  public long bitpos(final String key, final boolean value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitpos(key, value));\n  }\n\n  @Override\n  public long bitpos(final String key, final boolean value, final BitPosParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitpos(key, value, params));\n  }\n\n  @Override\n  public List<Object> role() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ROLE);\n    return BuilderFactory.ENCODED_OBJECT_LIST.build(connection.getOne());\n  }\n\n  /**\n   * Retrieve the configuration of a running Redis server. Not all the configuration parameters are\n   * supported.\n   * <p>\n   * CONFIG GET returns the current configuration parameters. This sub command only accepts a single\n   * argument, that is glob style pattern. All the configuration parameters matching this parameter\n   * are reported as a list of key-value pairs.\n   * <p>\n   * <b>Example:</b>\n   *\n   * <pre>\n   * $ redis-cli config get '*'\n   * 1. \"dbfilename\"\n   * 2. \"dump.rdb\"\n   * 3. \"requirepass\"\n   * 4. (nil)\n   * 5. \"masterauth\"\n   * 6. (nil)\n   * 7. \"maxmemory\"\n   * 8. \"0\\n\"\n   * 9. \"appendfsync\"\n   * 10. \"everysec\"\n   * 11. \"save\"\n   * 12. \"3600 1 300 100 60 10000\"\n   *\n   * $ redis-cli config get 'm*'\n   * 1. \"masterauth\"\n   * 2. (nil)\n   * 3. \"maxmemory\"\n   * 4. \"0\\n\"\n   * </pre>\n   * @param pattern\n   * @return Bulk reply.\n   */\n  @Override\n  public Map<String, String> configGet(final String pattern) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, Keyword.GET.name(), pattern);\n    return BuilderFactory.STRING_MAP.build(connection.getOne());\n  }\n\n  @Override\n  public Map<String, String> configGet(String... patterns) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, joinParameters(Keyword.GET.name(), patterns));\n    return BuilderFactory.STRING_MAP.build(connection.getOne());\n  }\n\n  /**\n   * Alter the configuration of a running Redis server. Not all the configuration parameters are\n   * supported.\n   * <p>\n   * The list of configuration parameters supported by CONFIG SET can be obtained issuing a\n   * {@link Jedis#configGet(String) CONFIG GET *} command.\n   * <p>\n   * The configuration set using CONFIG SET is immediately loaded by the Redis server that will\n   * start acting as specified starting from the next command.\n   * <p>\n   * <b>Parameters value format</b>\n   * <p>\n   * The value of the configuration parameter is the same as the one of the same parameter in the\n   * Redis configuration file, with the following exceptions:\n   * <p>\n   * <ul>\n   * <li>The save parameter is a list of space-separated integers. Every pair of integers specify\n   * the time and number of changes limit to trigger a save. For instance the command CONFIG SET\n   * save \"3600 10 60 10000\" will configure the server to issue a background saving of the RDB file\n   * every 3600 seconds if there are at least 10 changes in the dataset, and every 60 seconds if\n   * there are at least 10000 changes. To completely disable automatic snapshots just set the\n   * parameter as an empty string.\n   * <li>All the integer parameters representing memory are returned and accepted only using bytes\n   * as unit.\n   * </ul>\n   * @param parameter\n   * @param value\n   * @return OK\n   */\n  @Override\n  public String configSet(final String parameter, final String value) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, Keyword.SET.name(), parameter, value);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String configSet(final String... parameterValues) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.CONFIG, joinParameters(Keyword.SET.name(), parameterValues));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String configSet(Map<String, String> parameterValues) {\n    checkIsInMultiOrPipeline();\n    CommandArguments args = new CommandArguments(Command.CONFIG).add(Keyword.SET);\n    parameterValues.forEach((k, v) -> args.add(k).add(v));\n    connection.sendCommand(args);\n    return connection.getStatusCodeReply();\n  }\n\n  public long publish(final String channel, final String message) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBLISH, channel, message);\n    return connection.getIntegerReply();\n  }\n\n  public void subscribe(final JedisPubSub jedisPubSub, final String... channels) {\n    jedisPubSub.proceed(connection, channels);\n  }\n\n  public void psubscribe(final JedisPubSub jedisPubSub, final String... patterns) {\n    jedisPubSub.proceedWithPatterns(connection, patterns);\n  }\n\n  public List<String> pubsubChannels() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, CHANNELS);\n    return connection.getMultiBulkReply();\n  }\n\n  public List<String> pubsubChannels(final String pattern) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, CHANNELS.name(), pattern);\n    return connection.getMultiBulkReply();\n  }\n\n  public Long pubsubNumPat() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, NUMPAT);\n    return connection.getIntegerReply();\n  }\n\n  public Map<String, Long> pubsubNumSub(String... channels) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, joinParameters(NUMSUB.name(), channels));\n    return BuilderFactory.PUBSUB_NUMSUB_MAP.build(connection.getOne());\n  }\n\n  public List<String> pubsubShardChannels() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, SHARDCHANNELS);\n    return connection.getMultiBulkReply();\n  }\n\n  public List<String> pubsubShardChannels(final String pattern) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, SHARDCHANNELS.name(), pattern);\n    return connection.getMultiBulkReply();\n  }\n\n  public Map<String, Long> pubsubShardNumSub(String... channels) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(PUBSUB, joinParameters(SHARDNUMSUB.name(), channels));\n    return BuilderFactory.PUBSUB_NUMSUB_MAP.build(connection.getOne());\n  }\n\n  @Override\n  public Object eval(final String script, final int keyCount, final String... params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.eval(script, keyCount, params));\n  }\n\n  @Override\n  public Object eval(final String script, final List<String> keys, final List<String> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.eval(script, keys, args));\n  }\n\n  @Override\n  public Object evalReadonly(String script, List<String> keys, List<String> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalReadonly(script, keys, args));\n  }\n\n  @Override\n  public Object eval(final String script) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.eval(script));\n  }\n\n  @Override\n  public Object evalsha(final String sha1) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalsha(sha1));\n  }\n\n  @Override\n  public Object evalsha(final String sha1, final List<String> keys, final List<String> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalsha(sha1, keys, args));\n  }\n\n  @Override\n  public Object evalshaReadonly(String sha1, List<String> keys, List<String> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalshaReadonly(sha1, keys, args));\n  }\n\n  @Override\n  public Object evalsha(final String sha1, final int keyCount, final String... params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.evalsha(sha1, keyCount, params));\n  }\n\n  @Override\n  public Boolean scriptExists(final String sha1) {\n    String[] a = new String[1];\n    a[0] = sha1;\n    return scriptExists(a).get(0);\n  }\n\n  @Override\n  public List<Boolean> scriptExists(final String... sha1) {\n    connection.sendCommand(SCRIPT, joinParameters(Keyword.EXISTS.name(), sha1));\n    return BuilderFactory.BOOLEAN_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public String scriptLoad(final String script) {\n    connection.sendCommand(SCRIPT, LOAD.name(), script);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public List<Slowlog> slowlogGet() {\n    connection.sendCommand(SLOWLOG, Keyword.GET);\n    return Slowlog.from(connection.getObjectMultiBulkReply());\n  }\n\n  @Override\n  public List<Slowlog> slowlogGet(final long entries) {\n    connection.sendCommand(SLOWLOG, Keyword.GET.getRaw(), toByteArray(entries));\n    return Slowlog.from(connection.getObjectMultiBulkReply());\n  }\n\n  @Override\n  public Long objectRefcount(final String key) {\n    connection.sendCommand(OBJECT, REFCOUNT.name(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public String objectEncoding(final String key) {\n    connection.sendCommand(OBJECT, ENCODING.name(), key);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public Long objectIdletime(final String key) {\n    connection.sendCommand(OBJECT, IDLETIME.name(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public List<String> objectHelp() {\n    connection.sendCommand(OBJECT, HELP);\n    return connection.getMultiBulkReply();\n  }\n\n  @Override\n  public Long objectFreq(final String key) {\n    connection.sendCommand(OBJECT, FREQ.name(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public long bitcount(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitcount(key));\n  }\n\n  @Override\n  public long bitcount(final String key, final long start, final long end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitcount(key, start, end));\n  }\n\n  @Override\n  public long bitcount(final String key, final long start, final long end, final BitCountOption option) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitcount(key, start, end, option));\n  }\n\n  @Override\n  public long bitop(final BitOP op, final String destKey, final String... srcKeys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitop(op, destKey, srcKeys));\n  }\n\n  @Override\n  public long commandCount() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND, COUNT);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public Map<String, CommandDocument> commandDocs(String... commands) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND, joinParameters(DOCS.name(), commands));\n    return BuilderFactory.COMMAND_DOCS_RESPONSE.build(connection.getOne());\n  }\n\n  @Override\n  public List<String> commandGetKeys(String... command) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND, joinParameters(GETKEYS.name(), command));\n    return BuilderFactory.STRING_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public List<KeyValue<String, List<String>>> commandGetKeysAndFlags(String... command) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND, joinParameters(GETKEYSANDFLAGS.name(), command));\n    return BuilderFactory.KEYED_STRING_LIST_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public Map<String, CommandInfo> commandInfo(String... commands) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND, joinParameters(Keyword.INFO.name(), commands));\n    return CommandInfo.COMMAND_INFO_RESPONSE.build(connection.getOne());\n  }\n\n  @Override\n  public Map<String, CommandInfo> command() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND);\n    return CommandInfo.COMMAND_INFO_RESPONSE.build(connection.getOne());\n  }\n\n  @Override\n  public List<String> commandList() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(COMMAND, LIST);\n    return BuilderFactory.STRING_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public List<String> commandListFilterBy(CommandListFilterByParams filterByParams) {\n    checkIsInMultiOrPipeline();\n    CommandArguments args = new CommandArguments(COMMAND).add(LIST).addParams(filterByParams);\n    connection.sendCommand(args);\n    return BuilderFactory.STRING_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public String sentinelMyId() {\n    connection.sendCommand(SENTINEL, MYID);\n    return connection.getBulkReply();\n  }\n\n  /**\n   * <pre>\n   * redis 127.0.0.1:26381&gt; sentinel masters\n   * 1)  1) \"name\"\n   *     2) \"mymaster\"\n   *     3) \"ip\"\n   *     4) \"127.0.0.1\"\n   *     5) \"port\"\n   *     6) \"6379\"\n   *     7) \"runid\"\n   *     8) \"93d4d4e6e9c06d0eea36e27f31924ac26576081d\"\n   *     9) \"flags\"\n   *    10) \"master\"\n   *    11) \"pending-commands\"\n   *    12) \"0\"\n   *    13) \"last-ok-ping-reply\"\n   *    14) \"423\"\n   *    15) \"last-ping-reply\"\n   *    16) \"423\"\n   *    17) \"info-refresh\"\n   *    18) \"6107\"\n   *    19) \"num-slaves\"\n   *    20) \"1\"\n   *    21) \"num-other-sentinels\"\n   *    22) \"2\"\n   *    23) \"quorum\"\n   *    24) \"2\"\n   *\n   * </pre>\n   */\n  @Override\n  public List<Map<String, String>> sentinelMasters() {\n    connection.sendCommand(SENTINEL, MASTERS);\n    return connection.getObjectMultiBulkReply().stream()\n        .map(BuilderFactory.STRING_MAP::build).collect(Collectors.toList());\n  }\n\n  @Override\n  public Map<String, String> sentinelMaster(String masterName) {\n    connection.sendCommand(SENTINEL, MASTER.name(), masterName);\n    return BuilderFactory.STRING_MAP.build(connection.getOne());\n  }\n\n  @Override\n  public List<Map<String, String>> sentinelSentinels(String masterName) {\n    connection.sendCommand(SENTINEL, SENTINELS.name(), masterName);\n    return connection.getObjectMultiBulkReply().stream()\n        .map(BuilderFactory.STRING_MAP::build).collect(Collectors.toList());\n  }\n\n  /**\n   * <pre>\n   * redis 127.0.0.1:26381&gt; sentinel get-master-addr-by-name mymaster\n   * 1) \"127.0.0.1\"\n   * 2) \"6379\"\n   * </pre>\n   * @param masterName\n   * @return two elements list of strings : host and port.\n   */\n  @Override\n  public List<String> sentinelGetMasterAddrByName(String masterName) {\n    connection.sendCommand(SENTINEL, GET_MASTER_ADDR_BY_NAME.getRaw(), encode(masterName));\n    return connection.getMultiBulkReply();\n  }\n\n  /**\n   * <pre>\n   * redis 127.0.0.1:26381&gt; sentinel reset mymaster\n   * (integer) 1\n   * </pre>\n   * @param pattern\n   */\n  @Override\n  public Long sentinelReset(String pattern) {\n    connection.sendCommand(SENTINEL, SentinelKeyword.RESET.name(), pattern);\n    return connection.getIntegerReply();\n  }\n\n  /**\n   * <pre>\n   * redis 127.0.0.1:26381&gt; sentinel slaves mymaster\n   * 1)  1) \"name\"\n   *     2) \"127.0.0.1:6380\"\n   *     3) \"ip\"\n   *     4) \"127.0.0.1\"\n   *     5) \"port\"\n   *     6) \"6380\"\n   *     7) \"runid\"\n   *     8) \"d7f6c0ca7572df9d2f33713df0dbf8c72da7c039\"\n   *     9) \"flags\"\n   *    10) \"slave\"\n   *    11) \"pending-commands\"\n   *    12) \"0\"\n   *    13) \"last-ok-ping-reply\"\n   *    14) \"47\"\n   *    15) \"last-ping-reply\"\n   *    16) \"47\"\n   *    17) \"info-refresh\"\n   *    18) \"657\"\n   *    19) \"master-link-down-time\"\n   *    20) \"0\"\n   *    21) \"master-link-status\"\n   *    22) \"ok\"\n   *    23) \"master-host\"\n   *    24) \"localhost\"\n   *    25) \"master-port\"\n   *    26) \"6379\"\n   *    27) \"slave-priority\"\n   *    28) \"100\"\n   * </pre>\n   * @param masterName\n   */\n  @Override\n  @Deprecated\n  public List<Map<String, String>> sentinelSlaves(String masterName) {\n    connection.sendCommand(SENTINEL, SLAVES.name(), masterName);\n    return connection.getObjectMultiBulkReply().stream()\n        .map(BuilderFactory.STRING_MAP::build).collect(Collectors.toList());\n  }\n\n  @Override\n  public List<Map<String, String>> sentinelReplicas(String masterName) {\n    connection.sendCommand(SENTINEL, REPLICAS.name(), masterName);\n    return connection.getObjectMultiBulkReply().stream()\n        .map(BuilderFactory.STRING_MAP::build).collect(Collectors.toList());\n  }\n\n  @Override\n  public String sentinelFailover(String masterName) {\n    connection.sendCommand(SENTINEL, SentinelKeyword.FAILOVER.name(), masterName);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String sentinelMonitor(String masterName, String ip, int port, int quorum) {\n    CommandArguments args = new CommandArguments(SENTINEL).add(SentinelKeyword.MONITOR)\n        .add(masterName).add(ip).add(port).add(quorum);\n    connection.sendCommand(args);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String sentinelRemove(String masterName) {\n    connection.sendCommand(SENTINEL, REMOVE.name(), masterName);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String sentinelSet(String masterName, Map<String, String> parameterMap) {\n    CommandArguments args = new CommandArguments(SENTINEL).add(SentinelKeyword.SET).add(masterName);\n    parameterMap.entrySet().forEach(entry -> args.add(entry.getKey()).add(entry.getValue()));\n    connection.sendCommand(args);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public byte[] dump(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.dump(key));\n  }\n\n  @Override\n  public String restore(final String key, final long ttl, final byte[] serializedValue) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.restore(key, ttl, serializedValue));\n  }\n\n  @Override\n  public String restore(final String key, final long ttl, final byte[] serializedValue,\n      final RestoreParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.restore(key, ttl, serializedValue, params));\n  }\n\n  @Override\n  public long pttl(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pttl(key));\n  }\n\n  /**\n   * PSETEX works exactly like {@link Jedis#setex(String, long, String)} with the sole difference\n   * that the expire time is specified in milliseconds instead of seconds. Time complexity: O(1)\n   * @param key\n   * @param milliseconds\n   * @param value\n   * @return OK\n   * @deprecated Use {@link Jedis#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String psetex(final String key, final long milliseconds, final String value) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.psetex(key, milliseconds, value));\n  }\n\n  @Override\n  public String aclSetUser(final String name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, SETUSER.name(), name);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String aclSetUser(String name, String... rules) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, joinParameters(SETUSER.name(), name, rules));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public long aclDelUser(final String... names) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, joinParameters(DELUSER.name(), names));\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public AccessControlUser aclGetUser(final String name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, GETUSER.name(), name);\n    return BuilderFactory.ACCESS_CONTROL_USER.build(connection.getOne());\n  }\n\n  @Override\n  public List<String> aclUsers() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, USERS);\n    return BuilderFactory.STRING_LIST.build(connection.getObjectMultiBulkReply());\n  }\n\n  @Override\n  public List<String> aclList() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LIST);\n    return connection.getMultiBulkReply();\n  }\n\n  @Override\n  public String aclWhoAmI() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, WHOAMI);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public List<String> aclCat() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, CAT);\n    return BuilderFactory.STRING_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public List<String> aclCat(String category) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, CAT.name(), category);\n    return BuilderFactory.STRING_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public List<AccessControlLogEntry> aclLog() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LOG);\n    return BuilderFactory.ACCESS_CONTROL_LOG_ENTRY_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public List<AccessControlLogEntry> aclLog(int limit) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LOG.getRaw(), toByteArray(limit));\n    return BuilderFactory.ACCESS_CONTROL_LOG_ENTRY_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public String aclLoad() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, LOAD);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String aclSave() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, Keyword.SAVE);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String aclGenPass() {\n    connection.sendCommand(ACL, GENPASS);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String aclGenPass(int bits) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ACL, GENPASS.getRaw(), toByteArray(bits));\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String aclDryRun(String username, String command, String... args) {\n    checkIsInMultiOrPipeline();\n    String[] allArgs = new String[3 + args.length];\n    allArgs[0] = DRYRUN.name();\n    allArgs[1] = username;\n    allArgs[2] = command;\n    System.arraycopy(args, 0, allArgs, 3, args.length);\n    connection.sendCommand(ACL, allArgs);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String aclDryRun(String username, CommandArguments commandArgs) {\n    checkIsInMultiOrPipeline();\n    CommandArguments allArgs = new CommandArguments(ACL).add(DRYRUN).add(username);\n    Iterator<Rawable> it = commandArgs.iterator();\n    while (it.hasNext()) allArgs.add(it.next());\n    connection.sendCommand(allArgs);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public byte[] aclDryRunBinary(byte[] username, byte[] command, byte[]... args) {\n    checkIsInMultiOrPipeline();\n    byte[][] allArgs = new byte[3 + args.length][];\n    allArgs[0] = DRYRUN.getRaw();\n    allArgs[1] = username;\n    allArgs[2] = command;\n    System.arraycopy(args, 0, allArgs, 3, args.length);\n    connection.sendCommand(ACL, allArgs);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public byte[] aclDryRunBinary(byte[] username, CommandArguments commandArgs) {\n    checkIsInMultiOrPipeline();\n    CommandArguments allArgs = new CommandArguments(ACL).add(DRYRUN).add(username);\n    Iterator<Rawable> it = commandArgs.iterator();\n    while (it.hasNext()) allArgs.add(it.next());\n    connection.sendCommand(allArgs);\n    return connection.getBinaryBulkReply();\n  }\n\n  @Override\n  public String clientKill(final String ipPort) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, KILL.name(), ipPort);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clientGetname() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, GETNAME);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientList() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, LIST);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientList(ClientType type) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, LIST.getRaw(), Keyword.TYPE.getRaw(), type.getRaw());\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientList(final long... clientIds) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, clientListParams(clientIds));\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientInfo() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, Keyword.INFO);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clientSetInfo(ClientAttributeOption attr, String value) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, SETINFO.getRaw(), attr.getRaw(), encode(value));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clientSetname(final String name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLIENT, SETNAME.name(), name);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String migrate(final String host, final int port, final String key,\n      final int destinationDb, final int timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, key, destinationDb, timeout));\n  }\n\n  @Override\n  public String migrate(final String host, final int port, final int destinationDB,\n      final int timeout, final MigrateParams params, final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, destinationDB, timeout, params, keys));\n  }\n\n  @Override\n  public String migrate(String host, int port, String key, int timeout) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, key, timeout));\n  }\n\n  @Override\n  public String migrate(String host, int port, int timeout, MigrateParams params, String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.migrate(host, port, timeout, params, keys));\n  }\n\n  @Override\n  public ScanResult<String> scan(final String cursor) {\n    return connection.executeCommand(commandObjects.scan(cursor));\n  }\n\n  @Override\n  public ScanResult<String> scan(final String cursor, final ScanParams params) {\n    return connection.executeCommand(commandObjects.scan(cursor, params));\n  }\n\n  @Override\n  public ScanResult<String> scan(final String cursor, final ScanParams params, final String type) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.scan(cursor, params, type));\n  }\n\n  @Override\n  public ScanResult<Map.Entry<String, String>> hscan(final String key, final String cursor,\n      final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<String> hscanNoValues(final String key, final String cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hscanNoValues(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<String> sscan(final String key, final String cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.sscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<Tuple> zscan(final String key, final String cursor, final ScanParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.zscan(key, cursor, params));\n  }\n\n  @Override\n  public String readonly() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(READONLY);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String readwrite() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(READWRITE);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterNodes() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.NODES);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clusterMeet(final String ip, final int port) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.MEET.name(), ip, Integer.toString(port));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterReset() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.RESET);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterReset(final ClusterResetType resetType) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.RESET.getRaw(), resetType.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterAddSlots(final int... slots) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, joinParameters(ClusterKeyword.ADDSLOTS.getRaw(), joinParameters(slots)));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterDelSlots(final int... slots) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, joinParameters(ClusterKeyword.DELSLOTS.getRaw(), joinParameters(slots)));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterInfo() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.INFO);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public List<String> clusterGetKeysInSlot(final int slot, final int count) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.GETKEYSINSLOT.getRaw(), toByteArray(slot), toByteArray(count));\n    return connection.getMultiBulkReply();\n  }\n\n  @Override\n  public List<byte[]> clusterGetKeysInSlotBinary(final int slot, final int count) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.GETKEYSINSLOT.getRaw(), toByteArray(slot), toByteArray(count));\n    return connection.getBinaryMultiBulkReply();\n  }\n\n  @Override\n  public String clusterSetSlotNode(final int slot, final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SETSLOT.getRaw(), toByteArray(slot), ClusterKeyword.NODE.getRaw(), encode(nodeId));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterSetSlotMigrating(final int slot, final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SETSLOT.getRaw(), toByteArray(slot), ClusterKeyword.MIGRATING.getRaw(), encode(nodeId));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterSetSlotImporting(final int slot, final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SETSLOT.getRaw(), toByteArray(slot), ClusterKeyword.IMPORTING.getRaw(), encode(nodeId));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterSetSlotStable(final int slot) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SETSLOT.getRaw(), toByteArray(slot), ClusterKeyword.STABLE.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterForget(final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.FORGET.name(), nodeId);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterFlushSlots() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.FLUSHSLOTS);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public long clusterKeySlot(final String key) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.KEYSLOT.name(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public long clusterCountFailureReports(final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, \"COUNT-FAILURE-REPORTS\",  nodeId);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public long clusterCountKeysInSlot(final int slot) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.COUNTKEYSINSLOT.getRaw(), toByteArray(slot));\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public String clusterSaveConfig() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SAVECONFIG);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterSetConfigEpoch(long configEpoch) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, \"SET-CONFIG-EPOCH\", Long.toString(configEpoch));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterBumpEpoch() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.BUMPEPOCH);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clusterReplicate(final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.REPLICATE.name(), nodeId);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  @Deprecated\n  public List<String> clusterSlaves(final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SLAVES.name(), nodeId);\n    return connection.getMultiBulkReply();\n  }\n\n  @Override\n  public List<String> clusterReplicas(final String nodeId) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.REPLICAS.name(), nodeId);\n    return connection.getMultiBulkReply();\n  }\n\n  @Override\n  public String clusterFailover() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.FAILOVER);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterFailover(ClusterFailoverOption failoverOption) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.FAILOVER.getRaw(), failoverOption.getRaw());\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  @Deprecated\n  public List<Object> clusterSlots() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SLOTS);\n    return connection.getObjectMultiBulkReply();\n  }\n\n  @Override\n  public List<ClusterShardInfo> clusterShards() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.SHARDS);\n    return BuilderFactory.CLUSTER_SHARD_INFO_LIST.build(connection.getObjectMultiBulkReply());\n  }\n\n  @Override\n  public String clusterMyId() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.MYID);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String clusterMyShardId() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.MYSHARDID);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public List<Map<String, Object>> clusterLinks() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER, ClusterKeyword.LINKS);\n    return connection.getObjectMultiBulkReply().stream()\n            .map(BuilderFactory.ENCODED_OBJECT_MAP::build).collect(Collectors.toList());\n  }\n\n  @Override\n  public String clusterAddSlotsRange(int... ranges) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER,\n        joinParameters(ClusterKeyword.ADDSLOTSRANGE.getRaw(), joinParameters(ranges)));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String clusterDelSlotsRange(int... ranges) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(CLUSTER,\n        joinParameters(ClusterKeyword.DELSLOTSRANGE.getRaw(), joinParameters(ranges)));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String asking() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(ASKING);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public long pfadd(final String key, final String... elements) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfadd(key, elements));\n  }\n\n  @Override\n  public long pfcount(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfcount(key));\n  }\n\n  @Override\n  public long pfcount(final String... keys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfcount(keys));\n  }\n\n  @Override\n  public String pfmerge(final String destkey, final String... sourcekeys) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.pfmerge(destkey, sourcekeys));\n  }\n\n  @Override\n  public Object fcall(final String name, final List<String> keys, final List<String> args) {\n    return connection.executeCommand(commandObjects.fcall(name, keys, args));\n  }\n\n  @Override\n  public Object fcallReadonly(final String name, final List<String> keys, final List<String> args) {\n    return connection.executeCommand(commandObjects.fcallReadonly(name, keys, args));\n  }\n\n  @Override\n  public String functionDelete(final String libraryName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionDelete(libraryName));\n  }\n\n  @Override\n  public String functionLoad(final String functionCode) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionLoad(functionCode));\n  }\n\n  @Override\n  public String functionLoadReplace(final String functionCode) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionLoadReplace(functionCode));\n  }\n\n  @Override\n  public FunctionStats functionStats() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionStats());\n  }\n\n  @Override\n  public String functionFlush() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionFlush());\n  }\n\n  @Override\n  public String functionFlush(final FlushMode mode) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionFlush(mode));\n  }\n\n  @Override\n  public String functionKill() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionKill());\n  }\n\n  @Override\n  public List<LibraryInfo> functionList() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionList());\n  }\n\n  @Override\n  public List<LibraryInfo> functionList(final String libraryNamePattern) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionList(libraryNamePattern));\n  }\n\n  @Override\n  public List<LibraryInfo> functionListWithCode() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionListWithCode());  }\n\n  @Override\n  public List<LibraryInfo> functionListWithCode(String libraryNamePattern) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionListWithCode(libraryNamePattern));\n  }\n\n  @Override\n  public long geoadd(final String key, final double longitude, final double latitude,\n      final String member) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geoadd(key, longitude, latitude, member));\n  }\n\n  @Override\n  public long geoadd(final String key, final Map<String, GeoCoordinate> memberCoordinateMap) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geoadd(key, memberCoordinateMap));\n  }\n\n  @Override\n  public long geoadd(final String key, final GeoAddParams params, final Map<String, GeoCoordinate> memberCoordinateMap) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geoadd(key, params, memberCoordinateMap));\n  }\n\n  @Override\n  public Double geodist(final String key, final String member1, final String member2) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geodist(key, member1, member2));\n  }\n\n  @Override\n  public Double geodist(final String key, final String member1, final String member2,\n      final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geodist(key, member1, member2, unit));\n  }\n\n  @Override\n  public List<String> geohash(final String key, String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geohash(key, members));\n  }\n\n  @Override\n  public List<GeoCoordinate> geopos(final String key, String... members) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geopos(key, members));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadius(final String key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(final String key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadius(final String key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearchStore(String, String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public long georadiusStore(final String key, double longitude, double latitude, double radius,\n      GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(final String key, final double longitude,\n      final double latitude, final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(final String key, final String member,\n      final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMember(key, member, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(final String key, final String member,\n      final double radius, final GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(final String key, final String member,\n      final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearchStore(String, String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public long georadiusByMemberStore(final String key, String member, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam));\n  }\n\n  /**\n   * @deprecated Use {@link Jedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(final String key, final String member,\n      final double radius, final GeoUnit unit, final GeoRadiusParam param) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, String member, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, member, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, coord, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, String member, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, member, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, coord, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, GeoSearchParam params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearch(key, params));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, String member, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, member, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, GeoCoordinate coord, double radius, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, coord, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, String member, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, member, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, coord, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, GeoSearchParam params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStore(dest, src, params));\n  }\n\n  @Override\n  public long geosearchStoreStoreDist(String dest, String src, GeoSearchParam params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.geosearchStoreStoreDist(dest, src, params));\n  }\n\n  @Override\n  public String moduleLoad(final String path) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.MODULE, LOAD.name(), path);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String moduleLoad(String path, String... args) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.MODULE, joinParameters(LOAD.name(), path, args));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String moduleLoadEx(String path, ModuleLoadExParams params) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(new CommandArguments(Command.MODULE).add(LOADEX).add(path)\n        .addParams(params));\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String moduleUnload(final String name) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.MODULE, UNLOAD.name(), name);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public List<Module> moduleList() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(Command.MODULE, LIST);\n    return BuilderFactory.MODULE_LIST.build(connection.getOne());\n  }\n\n  @Override\n  public List<Long> bitfield(final String key, final String... arguments) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitfield(key, arguments));\n  }\n\n  @Override\n  public List<Long> bitfieldReadonly(final String key, final String... arguments) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.bitfieldReadonly(key, arguments));\n  }\n\n  @Override\n  public long hstrlen(final String key, final String field) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hstrlen(key, field));\n  }\n\n  @Override\n  public List<Long> hexpire(String key, long seconds, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpire(key, seconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpire(String key, long seconds, ExpiryOption condition, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpire(key, seconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(String key, long milliseconds, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(String key, long unixTimeSeconds, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(String key, long unixTimeMillis, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireTime(String key, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireTime(String key, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> httl(String key, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.httl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpttl(String key, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpttl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpersist(String key, String... fields) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hpersist(key, fields));\n  }\n\n  @Override\n  public String memoryDoctor() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, DOCTOR);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public Long memoryUsage(final String key) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, USAGE.name(), key);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public Long memoryUsage(final String key, final int samples) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, USAGE.getRaw(), encode(key), SAMPLES.getRaw(), toByteArray(samples));\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public String memoryPurge() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, PURGE);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public Map<String, Object> memoryStats() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(MEMORY, STATS);\n    return BuilderFactory.ENCODED_OBJECT_MAP.build(connection.getOne());\n  }\n\n  @Override\n  public String lolwut() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(LOLWUT);\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String lolwut(LolwutParams lolwutParams) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(new CommandArguments(LOLWUT).addParams(lolwutParams));\n    return connection.getBulkReply();\n  }\n\n  @Override\n  public String reset() {\n    connection.sendCommand(Command.RESET);\n    return connection.getStatusCodeReply();\n  }\n\n  @Override\n  public String latencyDoctor() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(LATENCY, DOCTOR);\n    return connection.getBulkReply();\n  }\n\n  public Map<String, LatencyLatestInfo> latencyLatest() {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(LATENCY, LATEST);\n    return BuilderFactory.LATENCY_LATEST_RESPONSE.build(connection.getOne());\n  }\n\n  public List<LatencyHistoryInfo> latencyHistory(LatencyEvent event) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(new CommandArguments(LATENCY).add(HISTORY).add(event));\n    return BuilderFactory.LATENCY_HISTORY_RESPONSE.build(connection.getOne());\n  }\n\n  public long latencyReset(LatencyEvent... events) {\n    checkIsInMultiOrPipeline();\n    CommandArguments arguments = new CommandArguments(LATENCY).add(Keyword.RESET);\n    Arrays.stream(events).forEach(arguments::add);\n    connection.sendCommand(arguments);\n    return connection.getIntegerReply();\n  }\n\n  @Override\n  public String hotkeysStart(HotkeysParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hotkeysStart(params));\n  }\n\n  @Override\n  public String hotkeysStop() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hotkeysStop());\n  }\n\n  @Override\n  public String hotkeysReset() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hotkeysReset());\n  }\n\n  @Override\n  public HotkeysInfo hotkeysGet() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.hotkeysGet());\n  }\n\n  @Override\n  public StreamEntryID xadd(final String key, final StreamEntryID id, final Map<String, String> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xadd(key, id, hash));\n  }\n\n  @Override\n  public StreamEntryID xadd(final String key, final XAddParams params, final Map<String, String> hash) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xadd(key, params, hash));\n  }\n\n  @Override\n  public long xlen(final String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xlen(key));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(final String key, final StreamEntryID start, final StreamEntryID end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(final String key, final StreamEntryID start,\n      final StreamEntryID end, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(final String key, final StreamEntryID end,\n      final StreamEntryID start) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(final String key, final StreamEntryID end,\n      final StreamEntryID start, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(final String key, final String start, final String end) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(final String key, final String start, final String end, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(final String key, final String end, final String start) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(final String key, final String end, final String start, final int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public List<Map.Entry<String, List<StreamEntry>>> xread(final XReadParams xReadParams, final Map<String, StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xread(xReadParams, streams));\n  }\n\n  @Override\n  public Map<String, List<StreamEntry>> xreadAsMap(final XReadParams xReadParams, final Map<String, StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadAsMap(xReadParams, streams));\n  }\n\n  @Override\n  public long xack(final String key, final String group, final StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xack(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(final String key, final String group, final StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xackdel(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(final String key, final String group, final StreamDeletionPolicy trimMode, final StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xackdel(key, group, trimMode, ids));\n  }\n\n  @Override\n  public String xgroupCreate(final String key, final String groupName, final StreamEntryID id,\n      final boolean makeStream) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream));\n  }\n\n  @Override\n  public String xgroupSetID(final String key, final String groupName, final StreamEntryID id) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupSetID(key, groupName, id));\n  }\n\n  @Override\n  public long xgroupDestroy(final String key, final String groupName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupDestroy(key, groupName));\n  }\n\n  @Override\n  public boolean xgroupCreateConsumer(String key, String groupName, String consumerName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xgroupDelConsumer(final String key, final String groupName, final String consumerName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xdel(final String key, final StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xdel(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(final String key, final StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xdelex(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(final String key, final StreamDeletionPolicy trimMode, final StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xdelex(key, trimMode, ids));\n  }\n\n  @Override\n  public long xtrim(final String key, final long maxLen, final boolean approximateLength) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xtrim(key, maxLen, approximateLength));\n  }\n\n  @Override\n  public long xtrim(final String key, final XTrimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xtrim(key, params));\n  }\n\n  @Override\n  public List<Map.Entry<String, List<StreamEntry>>> xreadGroup(final String groupName, final String consumer,\n      final XReadGroupParams xReadGroupParams, final Map<String, StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Map<String, List<StreamEntry>> xreadGroupAsMap(final String groupName, final String consumer,\n      final XReadGroupParams xReadGroupParams, final Map<String, StreamEntryID> streams) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xreadGroupAsMap(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public StreamPendingSummary xpending(final String key, final String groupName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xpending(key, groupName));\n  }\n\n  @Override\n  public List<StreamPendingEntry> xpending(final String key, final String groupName, final XPendingParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xpending(key, groupName, params));\n  }\n\n  @Override\n  public List<StreamEntry> xclaim(String key, String group, String consumerName, long minIdleTime,\n      XClaimParams params, StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public List<StreamEntryID> xclaimJustId(String key, String group, String consumerName,\n      long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public Map.Entry<StreamEntryID, List<StreamEntry>> xautoclaim(String key, String group, String consumerName,\n      long minIdleTime, StreamEntryID start, XAutoClaimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xautoclaim(key, group, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Map.Entry<StreamEntryID, List<StreamEntryID>> xautoclaimJustId(String key, String group, String consumerName,\n      long minIdleTime, StreamEntryID start, XAutoClaimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public StreamInfo xinfoStream(String key) {\n    return connection.executeCommand(commandObjects.xinfoStream(key));\n  }\n\n  @Override\n  public StreamFullInfo xinfoStreamFull(String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoStreamFull(key));\n  }\n\n  @Override\n  public String xcfgset(String key, redis.clients.jedis.params.XCfgSetParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xcfgset(key, params));\n  }\n\n  @Override\n  public StreamFullInfo xinfoStreamFull(String key, int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.xinfoStreamFull(key, count));\n  }\n\n  @Override\n  public List<StreamGroupInfo> xinfoGroups(String key) {\n    return connection.executeCommand(commandObjects.xinfoGroups(key));\n  }\n\n  @Override\n  public List<StreamConsumersInfo> xinfoConsumers(String key, String group) {\n    return connection.executeCommand(commandObjects.xinfoConsumers(key, group));\n  }\n\n  @Override\n  public List<StreamConsumerInfo> xinfoConsumers2(String key, String group) {\n    return connection.executeCommand(commandObjects.xinfoConsumers2(key, group));\n  }\n\n  @Override\n  public Object fcall(final byte[] name, final List<byte[]> keys, final List<byte[]> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.fcall(name, keys, args));\n  }\n\n  @Override\n  public Object fcallReadonly(final byte[] name, final List<byte[]> keys, final List<byte[]> args) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.fcallReadonly(name, keys, args));\n  }\n\n  @Override\n  public String functionDelete(final byte[] libraryName) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionDelete(libraryName));\n  }\n\n  @Override\n  public byte[] functionDump() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionDump());\n  }\n\n  @Override\n  public List<Object> functionListBinary() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionListBinary());\n  }\n\n  @Override\n  public List<Object> functionList(final byte[] libraryNamePattern) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionList(libraryNamePattern));\n  }\n\n  @Override\n  public List<Object> functionListWithCodeBinary() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionListWithCodeBinary());\n  }\n\n  @Override\n  public List<Object> functionListWithCode(final byte[] libraryNamePattern) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionListWithCode(libraryNamePattern));\n  }\n\n  @Override\n  public String functionLoad(final byte[] functionCode) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionLoad(functionCode));\n  }\n\n  @Override\n  public String functionLoadReplace(final byte[] functionCode) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionLoadReplace(functionCode));\n  }\n\n  @Override\n  public String functionRestore(final byte[] serializedValue) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionRestore(serializedValue));\n  }\n\n  @Override\n  public String functionRestore(final byte[] serializedValue, final FunctionRestorePolicy policy) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionRestore(serializedValue, policy));\n  }\n\n  @Override\n  public Object functionStatsBinary() {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.functionStatsBinary());\n  }\n\n  public Object sendCommand(ProtocolCommand cmd, String... args) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(cmd, args);\n    return connection.getOne();\n  }\n\n  public Object sendBlockingCommand(ProtocolCommand cmd, String... args) {\n    checkIsInMultiOrPipeline();\n    connection.sendCommand(cmd, args);\n    connection.setTimeoutInfinite();\n    try {\n      return connection.getOne();\n    } finally {\n      connection.rollbackTimeout();\n    }\n  }\n\n  private static byte[][] joinParameters(int... params) {\n    byte[][] result = new byte[params.length][];\n    for (int i = 0; i < params.length; i++) {\n      result[i] = toByteArray(params[i]);\n    }\n    return result;\n  }\n\n  private static byte[][] joinParameters(byte[] first, byte[][] rest) {\n    byte[][] result = new byte[rest.length + 1][];\n    result[0] = first;\n    System.arraycopy(rest, 0, result, 1, rest.length);\n    return result;\n  }\n\n  private static byte[][] joinParameters(byte[] first, byte[] second, byte[][] rest) {\n    byte[][] result = new byte[rest.length + 2][];\n    result[0] = first;\n    result[1] = second;\n    System.arraycopy(rest, 0, result, 2, rest.length);\n    return result;\n  }\n\n  private static String[] joinParameters(String first, String[] rest) {\n    String[] result = new String[rest.length + 1];\n    result[0] = first;\n    System.arraycopy(rest, 0, result, 1, rest.length);\n    return result;\n  }\n\n  private static String[] joinParameters(String first, String second, String[] rest) {\n    String[] result = new String[rest.length + 2];\n    result[0] = first;\n    result[1] = second;\n    System.arraycopy(rest, 0, result, 2, rest.length);\n    return result;\n  }\n\n  // Vector Set commands\n  @Override\n  public boolean vadd(String key, float[] vector, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vadd(key, vector, element));\n  }\n\n  @Override\n  public boolean vadd(String key, float[] vector, String element, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vadd(key, vector, element, params));\n  }\n\n  @Override\n  public boolean vaddFP32(String key, byte[] vectorBlob, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vaddFP32(key, vectorBlob, element));\n  }\n\n  @Override\n  public boolean vaddFP32(String key, byte[] vectorBlob, String element, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, params));\n  }\n\n  @Override\n  public boolean vadd(String key, float[] vector, String element, int reduceDim, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vadd(key, vector, element, reduceDim, params));\n  }\n\n  @Override\n  public boolean vaddFP32(String key, byte[] vectorBlob, String element, int reduceDim, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, reduceDim, params));\n  }\n\n  @Override\n  public List<String> vsim(String key, float[] vector) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsim(key, vector));\n  }\n\n  @Override\n  public List<String> vsim(String key, float[] vector, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsim(key, vector, params));\n  }\n\n  @Override\n  public Map<String, Double> vsimWithScores(String key, float[] vector, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimWithScores(key, vector, params));\n  }\n\n  @Override\n  public Map<String, VSimScoreAttribs> vsimWithScoresAndAttribs(String key, float[] vector, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params));\n  }\n\n  @Override\n  public List<String> vsimByElement(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElement(key, element));\n  }\n\n  @Override\n  public List<String> vsimByElement(String key, String element, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElement(key, element, params));\n  }\n\n  @Override\n  public Map<String, Double> vsimByElementWithScores(String key, String element, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElementWithScores(key, element, params));\n  }\n\n  @Override\n  public Map<String, VSimScoreAttribs> vsimByElementWithScoresAndAttribs(String key, String element, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params));\n  }\n\n  @Override\n  public long vdim(String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vdim(key));\n  }\n\n  @Override\n  public long vcard(String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vcard(key));\n  }\n\n  @Override\n  public List<Double> vemb(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vemb(key, element));\n  }\n\n  @Override\n  public RawVector vembRaw(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vembRaw(key, element));\n  }\n\n  @Override\n  public boolean vrem(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vrem(key, element));\n  }\n\n  @Override\n  public List<List<String>> vlinks(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vlinks(key, element));\n  }\n\n  @Override\n  public List<Map<String, Double>> vlinksWithScores(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vlinksWithScores(key, element));\n  }\n\n  @Override\n  public String vrandmember(String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vrandmember(key));\n  }\n\n  @Override\n  public List<String> vrandmember(String key, int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vrandmember(key, count));\n  }\n\n  @Override\n  public String vgetattr(String key, String element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vgetattr(key, element));\n  }\n\n  @Override\n  public boolean vsetattr(String key, String element, String attributes) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsetattr(key, element, attributes));\n  }\n\n  @Override\n  public VectorInfo vinfo(String key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vinfo(key));\n  }\n\n  // Binary vector set commands\n  @Override\n  public boolean vadd(byte[] key, float[] vector, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vadd(key, vector, element));\n  }\n\n  @Override\n  public boolean vadd(byte[] key, float[] vector, byte[] element, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vadd(key, vector, element, params));\n  }\n\n  @Override\n  public boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vaddFP32(key, vectorBlob, element));\n  }\n\n  @Override\n  public boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, params));\n  }\n\n  @Override\n  public boolean vadd(byte[] key, float[] vector, byte[] element, int reduceDim, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vadd(key, vector, element, reduceDim, params));\n  }\n\n  @Override\n  public boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, int reduceDim, VAddParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, reduceDim, params));\n  }\n\n  @Override\n  public List<byte[]> vsim(byte[] key, float[] vector) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsim(key, vector));\n  }\n\n  @Override\n  public List<byte[]> vsim(byte[] key, float[] vector, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsim(key, vector, params));\n  }\n\n  @Override\n  public Map<byte[], Double> vsimWithScores(byte[] key, float[] vector, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimWithScores(key, vector, params));\n  }\n\n  @Override\n  public Map<byte[], VSimScoreAttribs> vsimWithScoresAndAttribs(byte[] key, float[] vector, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params));\n  }\n\n  @Override\n  public List<byte[]> vsimByElement(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElement(key, element));\n  }\n\n  @Override\n  public List<byte[]> vsimByElement(byte[] key, byte[] element, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElement(key, element, params));\n  }\n\n  @Override\n  public Map<byte[], Double> vsimByElementWithScores(byte[] key, byte[] element, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElementWithScores(key, element, params));\n  }\n\n  @Override\n  public Map<byte[], VSimScoreAttribs> vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, VSimParams params) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params));\n  }\n\n  @Override\n  public long vdim(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vdim(key));\n  }\n\n  @Override\n  public long vcard(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vcard(key));\n  }\n\n  @Override\n  public List<Double> vemb(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vemb(key, element));\n  }\n\n  @Override\n  public RawVector vembRaw(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vembRaw(key, element));\n  }\n\n  @Override\n  public boolean vrem(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vrem(key, element));\n  }\n\n  @Override\n  public List<List<byte[]>> vlinks(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vlinks(key, element));\n  }\n\n  @Override\n  public List<Map<byte[], Double>> vlinksWithScores(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vlinksWithScores(key, element));\n  }\n\n  @Override\n  public byte[] vrandmember(byte[] key) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vrandmember(key));\n  }\n\n  @Override\n  public List<byte[]> vrandmember(byte[] key, int count) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vrandmember(key, count));\n  }\n\n  @Override\n  public byte[] vgetattr(byte[] key, byte[] element) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vgetattr(key, element));\n  }\n\n  @Override\n  public boolean vsetattr(byte[] key, byte[] element, byte[] attributes) {\n    checkIsInMultiOrPipeline();\n    return connection.executeCommand(commandObjects.vsetattr(key, element, attributes));\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisClientConfig.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.function.Supplier;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport redis.clients.jedis.authentication.AuthXManager;\n\npublic interface JedisClientConfig {\n\n  default RedisProtocol getRedisProtocol() {\n    return null;\n  }\n\n  /**\n   * @return Connection timeout in milliseconds\n   */\n  default int getConnectionTimeoutMillis() {\n    return Protocol.DEFAULT_TIMEOUT;\n  }\n\n  /**\n   * @return Socket timeout in milliseconds\n   */\n  default int getSocketTimeoutMillis() {\n    return Protocol.DEFAULT_TIMEOUT;\n  }\n\n  /**\n   * @return Socket timeout (in milliseconds) to use during blocking operation. Default is '0',\n   *         which means to block forever.\n   */\n  default int getBlockingSocketTimeoutMillis() {\n    return 0;\n  }\n\n  /**\n   * @return Redis ACL user\n   */\n  default String getUser() {\n    return null;\n  }\n\n  default String getPassword() {\n    return null;\n  }\n\n  // TODO: return null\n  default Supplier<RedisCredentials> getCredentialsProvider() {\n    return new DefaultRedisCredentialsProvider(\n        new DefaultRedisCredentials(getUser(), getPassword()));\n  }\n\n  default AuthXManager getAuthXManager() {\n    return null;\n  }\n\n  default int getDatabase() {\n    return Protocol.DEFAULT_DATABASE;\n  }\n\n  default String getClientName() {\n    return null;\n  }\n\n  /**\n   * @return {@code true} - to create TLS connection(s). {@code false} - otherwise.\n   */\n  default boolean isSsl() {\n    return false;\n  }\n\n  default SSLSocketFactory getSslSocketFactory() {\n    return null;\n  }\n\n  default SSLParameters getSslParameters() {\n    return null;\n  }\n\n  /**\n   * {@link JedisClientConfig#isSsl()}, {@link JedisClientConfig#getSslSocketFactory()} and\n   * {@link JedisClientConfig#getSslParameters()} will be ignored if\n   * {@link JedisClientConfig#getSslOptions() this} is set.\n   * @return ssl options\n   */\n  default SslOptions getSslOptions() {\n    return null;\n  }\n\n  default HostnameVerifier getHostnameVerifier() {\n    return null;\n  }\n\n  default HostAndPortMapper getHostAndPortMapper() {\n    return null;\n  }\n\n  /**\n   * Execute READONLY command to connections.\n   * <p>\n   * READONLY command is specific to Redis Cluster replica nodes. So this config param is only\n   * intended for Redis Cluster connections.\n   * @return {@code true} - to execute READONLY command to connection(s). {@code false} - otherwise.\n   */\n  default boolean isReadOnlyForRedisClusterReplicas() {\n    return false;\n  }\n\n  /**\n   * Modify the behavior of internally executing CLIENT SETINFO command.\n   * @return CLIENT SETINFO config\n   */\n  default ClientSetInfoConfig getClientSetInfoConfig() {\n    return ClientSetInfoConfig.DEFAULT;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisCluster.java",
    "content": "package redis.clients.jedis;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.builders.ClusterClientBuilder;\nimport redis.clients.jedis.executors.ClusterCommandExecutor;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.csc.CacheFactory;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.util.JedisClusterCRC16;\n\n/**\n * JedisCluster is a client for Redis Cluster deployments.\n *\n * @deprecated Use {@link RedisClusterClient} instead. RedisClusterClient provides the same functionality\n *             with a cleaner API and simplified constructor options. For basic usage, simple\n *             constructors are available. For advanced configuration, use {@link RedisClusterClient#builder()}.\n */\n@Deprecated\npublic class JedisCluster extends UnifiedJedis {\n\n  public static final String INIT_NO_ERROR_PROPERTY = \"jedis.cluster.initNoError\";\n\n  /**\n   * Default timeout in milliseconds.\n   */\n  public static final int DEFAULT_TIMEOUT = 2000;\n\n  /**\n   * Default amount of attempts for executing a command\n   */\n  public static final int DEFAULT_MAX_ATTEMPTS = 5;\n\n  private final CommandFlagsRegistry commandFlagsRegistry;\n\n  /**\n   * Creates a JedisCluster instance. The provided node is used to make the first contact with the cluster.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.JedisCluster#DEFAULT_TIMEOUT} ms is being used with\n   * {@value redis.clients.jedis.JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts.\n   * @param node Node to first connect to.\n   */\n  public JedisCluster(HostAndPort node) {\n    this(Collections.singleton(node));\n  }\n\n  /**\n   * Creates a JedisCluster instance. The provided node is used to make the first contact with the cluster.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.JedisCluster#DEFAULT_TIMEOUT} ms is being used with\n   * {@value redis.clients.jedis.JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts.\n   * @param node Node to first connect to.\n   * @param timeout connection and socket timeout in milliseconds.\n   */\n  public JedisCluster(HostAndPort node, int timeout) {\n    this(Collections.singleton(node), timeout);\n  }\n\n  /**\n   * Creates a JedisCluster instance. The provided node is used to make the first contact with the cluster.<br>\n   * You can specify the timeout and the maximum attempts.\n   * @param node Node to first connect to.\n   * @param timeout connection and socket timeout in milliseconds.\n   * @param maxAttempts maximum attempts for executing a command.\n   */\n  public JedisCluster(HostAndPort node, int timeout, int maxAttempts) {\n    this(Collections.singleton(node), timeout, maxAttempts);\n  }\n\n  public JedisCluster(HostAndPort node, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int timeout, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), timeout, poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int timeout, int maxAttempts,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), timeout, maxAttempts, poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts,\n      String password, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, password,\n        poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts,\n      String password, String clientName, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, password,\n        clientName, poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts,\n      String user, String password, String clientName,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, user, password,\n        clientName, poolConfig);\n  }\n\n  public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts,\n      String password, String clientName, final GenericObjectPoolConfig<Connection> poolConfig,\n      boolean ssl) {\n    this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, password,\n        clientName, poolConfig, ssl);\n  }\n\n  public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts,\n      String user, String password, String clientName,\n      final GenericObjectPoolConfig<Connection> poolConfig, boolean ssl) {\n    this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, user, password,\n        clientName, poolConfig, ssl);\n  }\n\n  public JedisCluster(HostAndPort node, final JedisClientConfig clientConfig, int maxAttempts,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(Collections.singleton(node), clientConfig, maxAttempts, poolConfig);\n  }\n\n  /**\n   * Creates a JedisCluster with multiple entry points.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.JedisCluster#DEFAULT_TIMEOUT} ms is being used with\n   * {@value redis.clients.jedis.JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts.\n   * @param nodes Nodes to connect to.\n   */\n  public JedisCluster(Set<HostAndPort> nodes) {\n    this(nodes, DEFAULT_TIMEOUT);\n  }\n\n  /**\n   * Creates a JedisCluster with multiple entry points.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.JedisCluster#DEFAULT_TIMEOUT} ms is being used with\n   * {@value redis.clients.jedis.JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts.\n   * @param nodes Nodes to connect to.\n   * @param timeout connection and socket timeout in milliseconds.\n   */\n  public JedisCluster(Set<HostAndPort> nodes, int timeout) {\n    this(nodes, DefaultJedisClientConfig.builder().timeoutMillis(timeout).build());\n  }\n\n  /**\n   * Creates a JedisCluster with multiple entry points.<br>\n   * You can specify the timeout and the maximum attempts.\n   * @param nodes Nodes to connect to.\n   * @param timeout connection and socket timeout in milliseconds.\n   * @param maxAttempts maximum attempts for executing a command.\n   */\n  public JedisCluster(Set<HostAndPort> nodes, int timeout, int maxAttempts) {\n    this(nodes, DefaultJedisClientConfig.builder().timeoutMillis(timeout).build(), maxAttempts);\n  }\n\n  public JedisCluster(Set<HostAndPort> nodes, String user, String password) {\n    this(nodes, DefaultJedisClientConfig.builder().user(user).password(password).build());\n  }\n\n  public JedisCluster(Set<HostAndPort> nodes, String user, String password,\n      HostAndPortMapper hostAndPortMap) {\n    this(nodes, DefaultJedisClientConfig.builder().user(user).password(password)\n        .hostAndPortMapper(hostAndPortMap).build());\n  }\n\n  public JedisCluster(Set<HostAndPort> nodes, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(nodes, DEFAULT_TIMEOUT, DEFAULT_MAX_ATTEMPTS, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> nodes, int timeout,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(nodes, timeout, DEFAULT_MAX_ATTEMPTS, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int timeout, int maxAttempts,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, timeout, timeout, maxAttempts, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int maxAttempts, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, connectionTimeout, soTimeout, maxAttempts, null, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int maxAttempts, String password, GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, connectionTimeout, soTimeout, maxAttempts, password, null, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int maxAttempts, String password, String clientName,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, connectionTimeout, soTimeout, maxAttempts, null, password, clientName,\n        poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int maxAttempts, String user, String password, String clientName,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).user(user).password(password).clientName(clientName).build(),\n        maxAttempts, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int infiniteSoTimeout, int maxAttempts, String user, String password, String clientName,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout)\n        .user(user).password(password).clientName(clientName).build(), maxAttempts, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int maxAttempts, String password, String clientName,\n      GenericObjectPoolConfig<Connection> poolConfig, boolean ssl) {\n    this(clusterNodes, connectionTimeout, soTimeout, maxAttempts, null, password, clientName,\n        poolConfig, ssl);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, int connectionTimeout, int soTimeout,\n      int maxAttempts, String user, String password, String clientName,\n      GenericObjectPoolConfig<Connection> poolConfig, boolean ssl) {\n    this(clusterNodes, DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).user(user).password(password).clientName(clientName).ssl(ssl).build(),\n        maxAttempts, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig) {\n    this(clusterNodes, clientConfig, DEFAULT_MAX_ATTEMPTS);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, int maxAttempts) {\n    this(clusterNodes, clientConfig, maxAttempts,\n        Duration.ofMillis((long) clientConfig.getSocketTimeoutMillis() * maxAttempts));\n  }\n\n  /**\n   * Creates a JedisCluster with multiple entry points.<br>\n   * You can specify the timeout and the maximum attempts.<br>\n   *\n   * Additionally, you are free to provide a {@link JedisClientConfig} instance.<br>\n   * You can use the {@link DefaultJedisClientConfig#builder()} builder pattern to customize your configuration, including socket timeouts,\n   * username and passwords as well as SSL related parameters.\n   *\n   * @param clusterNodes Nodes to connect to.\n   * @param clientConfig Client configuration parameters.\n   * @param maxAttempts maximum attempts for executing a command.\n   * @param maxTotalRetriesDuration Maximum time used for reconnecting.\n   */\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, int maxAttempts,\n      Duration maxTotalRetriesDuration) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig), maxAttempts, maxTotalRetriesDuration,\n        clientConfig.getRedisProtocol());\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, clientConfig, DEFAULT_MAX_ATTEMPTS, poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, int maxAttempts,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(clusterNodes, clientConfig, maxAttempts,\n        Duration.ofMillis((long) clientConfig.getSocketTimeoutMillis() * maxAttempts), poolConfig);\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, int maxAttempts,\n      Duration maxTotalRetriesDuration, GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig), maxAttempts, maxTotalRetriesDuration,\n        clientConfig.getRedisProtocol());\n  }\n\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig, Duration topologyRefreshPeriod, int maxAttempts,\n      Duration maxTotalRetriesDuration) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig, topologyRefreshPeriod),\n        maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol());\n  }\n\n  // Uses a fetched connection to process protocol. Should be avoided if possible.\n  public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {\n    super(provider, maxAttempts, maxTotalRetriesDuration);\n    this.commandFlagsRegistry = StaticCommandFlagsRegistry.registry();\n  }\n\n  private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration,\n      RedisProtocol protocol) {\n    super(provider, maxAttempts, maxTotalRetriesDuration, protocol);\n    this.commandFlagsRegistry = StaticCommandFlagsRegistry.registry();\n  }\n\n  @Experimental\n  public JedisCluster(Set<HostAndPort> hnp, JedisClientConfig jedisClientConfig, CacheConfig cacheConfig) {\n    this(hnp, jedisClientConfig, CacheFactory.getCache(cacheConfig));\n  }\n\n  @Experimental\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache) {\n    this(clusterNodes, clientConfig, clientSideCache, DEFAULT_MAX_ATTEMPTS,\n        Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis()));\n  }\n\n  @Experimental\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache,\n      int maxAttempts, Duration maxTotalRetriesDuration) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache), maxAttempts,\n        maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  @Experimental\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache,\n      int maxAttempts, Duration maxTotalRetriesDuration, GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig),\n        maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  @Experimental\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig),\n        DEFAULT_MAX_ATTEMPTS, Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis()),\n        clientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  @Experimental\n  public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache,\n      GenericObjectPoolConfig<Connection> poolConfig, Duration topologyRefreshPeriod, int maxAttempts,\n      Duration maxTotalRetriesDuration) {\n    this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig, topologyRefreshPeriod),\n        maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  @Experimental\n  private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration,\n      RedisProtocol protocol, Cache clientSideCache) {\n    super(provider, maxAttempts, maxTotalRetriesDuration, protocol, clientSideCache);\n    this.commandFlagsRegistry = StaticCommandFlagsRegistry.registry();\n  }\n\n  private JedisCluster(CommandExecutor commandExecutor, ConnectionProvider connectionProvider,\n      CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache,\n      CommandFlagsRegistry commandFlagsRegistry) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n    this.commandFlagsRegistry = commandFlagsRegistry;\n  }\n\n  /**\n   * Fluent builder for {@link JedisCluster} (Redis Cluster).\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  static public class Builder extends ClusterClientBuilder<JedisCluster> {\n\n    @Override\n    protected JedisCluster createClient() {\n      return new JedisCluster(commandExecutor, connectionProvider, commandObjects,\n          clientConfig.getRedisProtocol(), cache, getCommandFlags());\n    }\n  }\n\n  /**\n   * Create a new builder for configuring JedisCluster instances.\n   * @return a new {@link JedisCluster.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Returns all nodes that were configured to connect to in key-value pairs ({@link Map}).<br>\n   * Key is the HOST:PORT and the value is the connection pool.\n   * @return the map of all connections.\n   */\n  public Map<String, ConnectionPool> getClusterNodes() {\n    return ((ClusterConnectionProvider) provider).getNodes();\n  }\n\n  /**\n   * Returns the connection for one of the 16,384 slots.\n   * @param slot the slot to retrieve the connection for.\n   * @return connection of the provided slot. {@code close()} of this connection must be called after use.\n   */\n  public Connection getConnectionFromSlot(int slot) {\n    return ((ClusterConnectionProvider) provider).getConnectionFromSlot(slot);\n  }\n\n  // commands\n  public long spublish(String channel, String message) {\n    return executeCommand(commandObjects.spublish(channel, message));\n  }\n\n  public long spublish(byte[] channel, byte[] message) {\n    return executeCommand(commandObjects.spublish(channel, message));\n  }\n\n  public void ssubscribe(final JedisShardedPubSub jedisPubSub, final String... channels) {\n    try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) {\n      jedisPubSub.proceed(connection, channels);\n    }\n  }\n\n  public void ssubscribe(BinaryJedisShardedPubSub jedisPubSub, final byte[]... channels) {\n    try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) {\n      jedisPubSub.proceed(connection, channels);\n    }\n  }\n  // commands\n\n  @Override\n  public ClusterPipeline pipelined() {\n    return new ClusterPipeline((ClusterConnectionProvider) provider,\n        (ClusterCommandObjects) commandObjects, commandFlagsRegistry);\n  }\n\n  /**\n   * @param doMulti param\n   * @return nothing\n   * @throws UnsupportedOperationException\n   */\n  @Override\n  public AbstractTransaction transaction(boolean doMulti) {\n    throw new UnsupportedOperationException();\n  }\n\n  public final <T> T executeCommandToReplica(CommandObject<T> commandObject) {\n    if (!(executor instanceof ClusterCommandExecutor)) {\n      throw new UnsupportedOperationException(\"Support only execute to replica in ClusterCommandExecutor\");\n    }\n    return ((ClusterCommandExecutor) executor).executeCommandToReplica(commandObject);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisClusterInfoCache.java",
    "content": "package redis.clients.jedis;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.annots.Internal;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport static redis.clients.jedis.RedisClusterClient.INIT_NO_ERROR_PROPERTY;\n\n@Internal\npublic class JedisClusterInfoCache {\n\n  private static final Logger logger = LoggerFactory.getLogger(JedisClusterInfoCache.class);\n\n  private final Map<String, ConnectionPool> nodes = new HashMap<>();\n  private final Map<String, ConnectionPool> primaryNodesCache = new HashMap<>();\n  private final ConnectionPool[] slots = new ConnectionPool[Protocol.CLUSTER_HASHSLOTS];\n  private final HostAndPort[] slotNodes = new HostAndPort[Protocol.CLUSTER_HASHSLOTS];\n  private final List<ConnectionPool>[] replicaSlots;\n\n  private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();\n  private final Lock r = rwl.readLock();\n  private final Lock w = rwl.writeLock();\n  private final Lock rediscoverLock = new ReentrantLock();\n\n  private final GenericObjectPoolConfig<Connection> poolConfig;\n  private final JedisClientConfig clientConfig;\n  private final Cache clientSideCache;\n  private final Set<HostAndPort> startNodes;\n\n  private static final int MASTER_NODE_INDEX = 2;\n\n  /**\n   * The single thread executor for the topology refresh task.\n   */\n  private ScheduledExecutorService topologyRefreshExecutor = null;\n\n  class TopologyRefreshTask implements Runnable {\n    @Override\n    public void run() {\n      logger.debug(\"Cluster topology refresh run, old nodes: {}\", nodes.keySet());\n      renewClusterSlots(null);\n      logger.debug(\"Cluster topology refresh run, new nodes: {}\", nodes.keySet());\n    }\n  }\n\n  public JedisClusterInfoCache(final JedisClientConfig clientConfig, final Set<HostAndPort> startNodes) {\n    this(clientConfig, null, null, startNodes);\n  }\n\n  @Experimental\n  public JedisClusterInfoCache(final JedisClientConfig clientConfig, Cache clientSideCache,\n      final Set<HostAndPort> startNodes) {\n    this(clientConfig, clientSideCache, null, startNodes);\n  }\n\n  public JedisClusterInfoCache(final JedisClientConfig clientConfig,\n      final GenericObjectPoolConfig<Connection> poolConfig, final Set<HostAndPort> startNodes) {\n    this(clientConfig, null, poolConfig, startNodes);\n  }\n\n  @Experimental\n  public JedisClusterInfoCache(final JedisClientConfig clientConfig, Cache clientSideCache,\n      final GenericObjectPoolConfig<Connection> poolConfig, final Set<HostAndPort> startNodes) {\n    this(clientConfig, clientSideCache, poolConfig, startNodes, null);\n  }\n\n  public JedisClusterInfoCache(final JedisClientConfig clientConfig,\n      final GenericObjectPoolConfig<Connection> poolConfig, final Set<HostAndPort> startNodes,\n      final Duration topologyRefreshPeriod) {\n    this(clientConfig, null, poolConfig, startNodes, topologyRefreshPeriod);\n  }\n\n  @Experimental\n  public JedisClusterInfoCache(final JedisClientConfig clientConfig, Cache clientSideCache,\n      final GenericObjectPoolConfig<Connection> poolConfig, final Set<HostAndPort> startNodes,\n      final Duration topologyRefreshPeriod) {\n    this.poolConfig = poolConfig;\n    this.clientConfig = clientConfig;\n    this.clientSideCache = clientSideCache;\n    this.startNodes = startNodes;\n    if (clientConfig.getAuthXManager() != null) {\n      clientConfig.getAuthXManager().start();\n    }\n    if (topologyRefreshPeriod != null) {\n      logger.info(\"Cluster topology refresh start, period: {}, startNodes: {}\", topologyRefreshPeriod, startNodes);\n      topologyRefreshExecutor = Executors.newSingleThreadScheduledExecutor();\n      topologyRefreshExecutor.scheduleWithFixedDelay(new TopologyRefreshTask(), topologyRefreshPeriod.toMillis(),\n          topologyRefreshPeriod.toMillis(), TimeUnit.MILLISECONDS);\n    }\n    if (clientConfig.isReadOnlyForRedisClusterReplicas()) {\n      replicaSlots = new ArrayList[Protocol.CLUSTER_HASHSLOTS];\n    } else {\n      replicaSlots = null;\n    }\n  }\n\n  /**\n   * Check whether the number and order of slots in the cluster topology are equal to CLUSTER_HASHSLOTS\n   * @param slotsInfo the cluster topology\n   * @return if slots is ok, return true, elese return false.\n   */\n  private boolean checkClusterSlotSequence(List<Object> slotsInfo) {\n    List<Integer> slots = new ArrayList<>();\n    for (Object slotInfoObj : slotsInfo) {\n      List<Object> slotInfo = (List<Object>)slotInfoObj;\n      slots.addAll(getAssignedSlotArray(slotInfo));\n    }\n    Collections.sort(slots);\n    if (slots.size() != Protocol.CLUSTER_HASHSLOTS) {\n      return false;\n    }\n    for (int i = 0; i < Protocol.CLUSTER_HASHSLOTS; ++i) {\n      if (i != slots.get(i)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  public void discoverClusterNodesAndSlots(Connection jedis) {\n    List<Object> slotsInfo = executeClusterSlots(jedis);\n    if (System.getProperty(INIT_NO_ERROR_PROPERTY) == null) {\n      if (slotsInfo.isEmpty()) {\n        throw new JedisClusterOperationException(\"Cluster slots list is empty.\");\n      }\n      if (!checkClusterSlotSequence(slotsInfo)) {\n        throw new JedisClusterOperationException(\"Cluster slots have holes.\");\n      }\n    }\n    w.lock();\n    try {\n      reset();\n      for (Object slotInfoObj : slotsInfo) {\n        List<Object> slotInfo = (List<Object>) slotInfoObj;\n\n        if (slotInfo.size() <= MASTER_NODE_INDEX) {\n          continue;\n        }\n\n        List<Integer> slotNums = getAssignedSlotArray(slotInfo);\n\n        // hostInfos\n        int size = slotInfo.size();\n        for (int i = MASTER_NODE_INDEX; i < size; i++) {\n          List<Object> hostInfos = (List<Object>) slotInfo.get(i);\n          if (hostInfos.isEmpty()) {\n            continue;\n          }\n\n          HostAndPort targetNode = generateHostAndPort(hostInfos);\n          setupNodeIfNotExist(targetNode);\n          if (i == MASTER_NODE_INDEX) {\n            primaryNodesCache.put(getNodeKey(targetNode), getNode(targetNode));\n            assignSlotsToNode(slotNums, targetNode);\n          } else if (clientConfig.isReadOnlyForRedisClusterReplicas()) {\n            assignSlotsToReplicaNode(slotNums, targetNode);\n          }\n        }\n      }\n    } finally {\n      w.unlock();\n    }\n  }\n\n  public void renewClusterSlots(Connection jedis) {\n    // If rediscovering is already in process - no need to start one more same rediscovering, just return\n    if (rediscoverLock.tryLock()) {\n      try {\n        // First, if jedis is available, use jedis renew.\n        if (jedis != null) {\n          try {\n            discoverClusterSlots(jedis);\n            return;\n          } catch (JedisException e) {\n            // try nodes from all pools\n          }\n        }\n\n        // Then, we use startNodes to try, as long as startNodes is available,\n        // whether it is vip, domain, or physical ip, it will succeed.\n        if (startNodes != null) {\n          for (HostAndPort hostAndPort : startNodes) {\n            try (Connection j = new Connection(hostAndPort, clientConfig)) {\n              discoverClusterSlots(j);\n              return;\n            } catch (JedisException e) {\n              // try next nodes\n            }\n          }\n        }\n\n        // Finally, we go back to the ShuffledNodesPool and try the remaining physical nodes.\n        for (ConnectionPool jp : getShuffledNodesPool()) {\n          try (Connection j = jp.getResource()) {\n            // If already tried in startNodes, skip this node.\n            if (startNodes != null && startNodes.contains(j.getHostAndPort())) {\n              continue;\n            }\n            discoverClusterSlots(j);\n            return;\n          } catch (JedisException e) {\n            // try next nodes\n          }\n        }\n\n      } finally {\n        rediscoverLock.unlock();\n      }\n    }\n  }\n\n  private void discoverClusterSlots(Connection jedis) {\n    List<Object> slotsInfo = executeClusterSlots(jedis);\n    if (System.getProperty(INIT_NO_ERROR_PROPERTY) == null) {\n      if (slotsInfo.isEmpty()) {\n        throw new JedisClusterOperationException(\"Cluster slots list is empty.\");\n      }\n      if (!checkClusterSlotSequence(slotsInfo)) {\n        throw new JedisClusterOperationException(\"Cluster slots have holes.\");\n      }\n    }\n    w.lock();\n    try {\n      resetSlots();\n      if (clientSideCache != null) {\n        clientSideCache.flush();\n      }\n      Set<String> hostAndPortKeys = new HashSet<>();\n\n      for (Object slotInfoObj : slotsInfo) {\n        List<Object> slotInfo = (List<Object>) slotInfoObj;\n\n        if (slotInfo.size() <= MASTER_NODE_INDEX) {\n          continue;\n        }\n\n        List<Integer> slotNums = getAssignedSlotArray(slotInfo);\n\n        int size = slotInfo.size();\n        for (int i = MASTER_NODE_INDEX; i < size; i++) {\n          List<Object> hostInfos = (List<Object>) slotInfo.get(i);\n          if (hostInfos.isEmpty()) {\n            continue;\n          }\n\n          HostAndPort targetNode = generateHostAndPort(hostInfos);\n          hostAndPortKeys.add(getNodeKey(targetNode));\n          setupNodeIfNotExist(targetNode);\n          if (i == MASTER_NODE_INDEX) {\n            assignSlotsToNode(slotNums, targetNode);\n          } else if (clientConfig.isReadOnlyForRedisClusterReplicas()) {\n            assignSlotsToReplicaNode(slotNums, targetNode);\n          }\n        }\n      }\n\n      // Remove dead nodes according to the latest query\n      Iterator<Entry<String, ConnectionPool>> entryIt = nodes.entrySet().iterator();\n      while (entryIt.hasNext()) {\n        Entry<String, ConnectionPool> entry = entryIt.next();\n        if (!hostAndPortKeys.contains(entry.getKey())) {\n          ConnectionPool pool = entry.getValue();\n          try {\n            if (pool != null) {\n              pool.destroy();\n            }\n          } catch (Exception e) {\n            // pass, may be this node dead\n          }\n          entryIt.remove();\n        }\n      }\n    } finally {\n      w.unlock();\n    }\n  }\n\n  private HostAndPort generateHostAndPort(List<Object> hostInfos) {\n    String host = SafeEncoder.encode((byte[]) hostInfos.get(0));\n    int port = ((Long) hostInfos.get(1)).intValue();\n    return new HostAndPort(host, port);\n  }\n\n  public ConnectionPool setupNodeIfNotExist(final HostAndPort node) {\n    w.lock();\n    try {\n      String nodeKey = getNodeKey(node);\n      ConnectionPool existingPool = nodes.get(nodeKey);\n      if (existingPool != null) return existingPool;\n\n      ConnectionPool nodePool = createNodePool(node);\n      nodes.put(nodeKey, nodePool);\n      return nodePool;\n    } finally {\n      w.unlock();\n    }\n  }\n\n  private ConnectionPool createNodePool(HostAndPort node) {\n    if (poolConfig == null) {\n      if (clientSideCache == null) {\n        return new ConnectionPool(node, clientConfig);\n      } else {\n        return new ConnectionPool(node, clientConfig, clientSideCache);\n      }\n    } else {\n      if (clientSideCache == null) {\n        return new ConnectionPool(node, clientConfig, poolConfig);\n      } else {\n        return new ConnectionPool(node, clientConfig, clientSideCache, poolConfig);\n      }\n    }\n  }\n\n  public void assignSlotToNode(int slot, HostAndPort targetNode) {\n    w.lock();\n    try {\n      ConnectionPool targetPool = setupNodeIfNotExist(targetNode);\n      slots[slot] = targetPool;\n      slotNodes[slot] = targetNode;\n    } finally {\n      w.unlock();\n    }\n  }\n\n  public void assignSlotsToNode(List<Integer> targetSlots, HostAndPort targetNode) {\n    w.lock();\n    try {\n      ConnectionPool targetPool = setupNodeIfNotExist(targetNode);\n      for (Integer slot : targetSlots) {\n        slots[slot] = targetPool;\n        slotNodes[slot] = targetNode;\n      }\n    } finally {\n      w.unlock();\n    }\n  }\n\n  public void assignSlotsToReplicaNode(List<Integer> targetSlots, HostAndPort targetNode) {\n    w.lock();\n    try {\n      ConnectionPool targetPool = setupNodeIfNotExist(targetNode);\n      for (Integer slot : targetSlots) {\n        if (replicaSlots[slot] == null) {\n          replicaSlots[slot] = new ArrayList<>();\n        }\n        replicaSlots[slot].add(targetPool);\n      }\n    } finally {\n      w.unlock();\n    }\n  }\n\n  public ConnectionPool getNode(String nodeKey) {\n    r.lock();\n    try {\n      return nodes.get(nodeKey);\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public ConnectionPool getNode(HostAndPort node) {\n    return getNode(getNodeKey(node));\n  }\n\n  public ConnectionPool getSlotPool(int slot) {\n    r.lock();\n    try {\n      return slots[slot];\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public HostAndPort getSlotNode(int slot) {\n    r.lock();\n    try {\n      return slotNodes[slot];\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public List<ConnectionPool> getSlotReplicaPools(int slot) {\n    r.lock();\n    try {\n      return replicaSlots[slot];\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public Map<String, ConnectionPool> getNodes() {\n    r.lock();\n    try {\n      return new HashMap<>(nodes);\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public Map<String, ConnectionPool> getPrimaryNodes() {\n    r.lock();\n    try {\n      return new HashMap<>(primaryNodesCache);\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public List<ConnectionPool> getShuffledPrimaryNodesPool() {\n    r.lock();\n    try {\n      List<ConnectionPool> pools = new ArrayList<>(primaryNodesCache.values());\n      Collections.shuffle(pools);\n      return pools;\n    } finally {\n      r.unlock();\n    }\n  }\n\n  public List<ConnectionPool> getShuffledNodesPool() {\n    r.lock();\n    try {\n      List<ConnectionPool> pools = new ArrayList<>(nodes.values());\n      Collections.shuffle(pools);\n      return pools;\n    } finally {\n      r.unlock();\n    }\n  }\n\n  /**\n   * Clear discovered nodes collections and gently release allocated resources\n   */\n  public void reset() {\n    w.lock();\n    try {\n      resetNodes();\n      resetSlots();\n    } finally {\n      w.unlock();\n    }\n  }\n\n  private void resetSlots() {\n    Arrays.fill(slots, null);\n    Arrays.fill(slotNodes, null);\n    resetReplicaSlots();\n  }\n\n  private void resetReplicaSlots() {\n    if (replicaSlots == null) {\n      return;\n    }\n\n    Arrays.stream(replicaSlots).filter(Objects::nonNull).forEach(List::clear);\n    Arrays.fill(replicaSlots, null);\n  }\n\n  private void resetNodes() {\n    for (ConnectionPool pool : nodes.values()) {\n      try {\n        if (pool != null) {\n          pool.destroy();\n        }\n      } catch (RuntimeException e) {\n        // pass\n      }\n    }\n    nodes.clear();\n    primaryNodesCache.clear();\n  }\n\n  public void close() {\n    reset();\n    if (topologyRefreshExecutor != null) {\n      logger.info(\"Cluster topology refresh shutdown, startNodes: {}\", startNodes);\n      topologyRefreshExecutor.shutdownNow();\n    }\n  }\n\n  public static String getNodeKey(HostAndPort hnp) {\n    return hnp.toString();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private List<Object> executeClusterSlots(Connection jedis) {\n    CommandArguments clusterSlotsCmd = new CommandArguments(Protocol.Command.CLUSTER).add(\n        \"SLOTS\");\n    return (List<Object>) jedis.executeCommand(clusterSlotsCmd);\n  }\n\n  private List<Integer> getAssignedSlotArray(List<Object> slotInfo) {\n    List<Integer> slotNums = new ArrayList<>();\n    for (int slot = ((Long) slotInfo.get(0)).intValue(); slot <= ((Long) slotInfo.get(1))\n        .intValue(); slot++) {\n      slotNums.add(slot);\n    }\n    return slotNums;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisFactory.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObject;\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.DefaultPooledObject;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.exceptions.InvalidURIException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.JedisURIHelper;\n\n/**\n * PooledObjectFactory implementation for creating and managing {@link Jedis} instances in connection pools.\n * <p>\n * This factory is used internally by {@link JedisPool} and {@link JedisSentinelPool} to create, validate,\n * and destroy pooled Jedis connections.\n * </p>\n *\n * @deprecated JedisFactory is used exclusively with the deprecated {@link JedisPool} and {@link JedisSentinelPool}\n *             classes. For modern Redis clients ({@link RedisClient}, {@link RedisSentinelClient}), the framework\n *             uses {@link ConnectionFactory} internally, which manages {@link Connection} objects instead of\n *             {@link Jedis} instances. There is no direct replacement for JedisFactory as connection management\n *             is handled automatically by the new client architecture.\n */\n@Deprecated\npublic class JedisFactory implements PooledObjectFactory<Jedis> {\n\n  private static final Logger logger = LoggerFactory.getLogger(JedisFactory.class);\n\n  private final JedisSocketFactory jedisSocketFactory;\n\n  private final JedisClientConfig clientConfig;\n\n  protected JedisFactory(final String host, final int port, final int connectionTimeout,\n      final int soTimeout, final String password, final int database, final String clientName) {\n    this(host, port, connectionTimeout, soTimeout, password, database, clientName, false, null, null, null);\n  }\n\n  protected JedisFactory(final String host, final int port, final int connectionTimeout,\n               final int soTimeout, final String user, final String password, final int database, final String clientName) {\n    this(host, port, connectionTimeout, soTimeout, 0, user, password, database, clientName);\n  }\n\n  protected JedisFactory(final String host, final int port, final int connectionTimeout, final int soTimeout,\n      final int infiniteSoTimeout, final String user, final String password, final int database, final String clientName) {\n    this(host, port, connectionTimeout, soTimeout, infiniteSoTimeout, user, password, database, clientName, false, null, null, null);\n  }\n\n  /**\n   * {@link #setHostAndPort(redis.clients.jedis.HostAndPort) setHostAndPort} must be called later.\n   */\n  JedisFactory(final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String user, final String password, final int database, final String clientName) {\n    this(connectionTimeout, soTimeout, infiniteSoTimeout, user, password, database, clientName, false, null, null, null);\n  }\n\n  protected JedisFactory(final String host, final int port, final int connectionTimeout,\n      final int soTimeout, final String password, final int database, final String clientName,\n      final boolean ssl, final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(host, port, connectionTimeout, soTimeout, null, password, database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  protected JedisFactory(final String host, final int port, final int connectionTimeout,\n               final int soTimeout, final String user, final String password, final int database, final String clientName,\n               final boolean ssl, final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n               final HostnameVerifier hostnameVerifier) {\n    this(host, port, connectionTimeout, soTimeout, 0, user, password, database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  protected JedisFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) {\n    this.clientConfig = clientConfig;\n    this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig);\n  }\n\n  protected JedisFactory(final String host, final int port, final int connectionTimeout, final int soTimeout,\n      final int infiniteSoTimeout, final String user, final String password, final int database,\n      final String clientName, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this.clientConfig = DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout).user(user)\n        .password(password).database(database).clientName(clientName)\n        .ssl(ssl).sslSocketFactory(sslSocketFactory)\n        .sslParameters(sslParameters).hostnameVerifier(hostnameVerifier).build();\n    this.jedisSocketFactory = new DefaultJedisSocketFactory(new HostAndPort(host, port), this.clientConfig);\n  }\n\n  protected JedisFactory(final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) {\n    this.clientConfig = clientConfig;\n    this.jedisSocketFactory = jedisSocketFactory;\n  }\n\n  /**\n   * {@link #setHostAndPort(redis.clients.jedis.HostAndPort) setHostAndPort} must be called later.\n   */\n  JedisFactory(final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String user, final String password, final int database, final String clientName, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout).user(user)\n        .password(password).database(database).clientName(clientName)\n        .ssl(ssl).sslSocketFactory(sslSocketFactory)\n        .sslParameters(sslParameters).hostnameVerifier(hostnameVerifier).build());\n  }\n\n  /**\n   * {@link JedisFactory#setHostAndPort(redis.clients.jedis.HostAndPort) setHostAndPort} must be called later.\n   */\n  JedisFactory(final JedisClientConfig clientConfig) {\n    this.clientConfig = clientConfig;\n    this.jedisSocketFactory = new DefaultJedisSocketFactory(clientConfig);\n  }\n\n  protected JedisFactory(final URI uri, final int connectionTimeout, final int soTimeout,\n      final String clientName) {\n    this(uri, connectionTimeout, soTimeout, clientName, null, null, null);\n  }\n\n  protected JedisFactory(final URI uri, final int connectionTimeout, final int soTimeout,\n      final String clientName, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(uri, connectionTimeout, soTimeout, 0, clientName, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  protected JedisFactory(final URI uri, final int connectionTimeout, final int soTimeout,\n      final int infiniteSoTimeout, final String clientName, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    if (!JedisURIHelper.isValid(uri)) {\n      throw new InvalidURIException(String.format(\n          \"Cannot open Redis connection due invalid URI. %s\", uri.toString()));\n    }\n    this.clientConfig = DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n        .socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout)\n        .user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri))\n        .database(JedisURIHelper.getDBIndex(uri)).clientName(clientName)\n        .protocol(JedisURIHelper.getRedisProtocol(uri))\n        .ssl(JedisURIHelper.isRedisSSLScheme(uri)).sslSocketFactory(sslSocketFactory)\n        .sslParameters(sslParameters).hostnameVerifier(hostnameVerifier).build();\n    this.jedisSocketFactory = new DefaultJedisSocketFactory(new HostAndPort(uri.getHost(), uri.getPort()), this.clientConfig);\n  }\n\n  void setHostAndPort(final HostAndPort hostAndPort) {\n    if (!(jedisSocketFactory instanceof DefaultJedisSocketFactory)) {\n      throw new IllegalStateException(\"setHostAndPort method has limited capability.\");\n    }\n    ((DefaultJedisSocketFactory) jedisSocketFactory).updateHostAndPort(hostAndPort);\n  }\n\n  @Override\n  public void activateObject(PooledObject<Jedis> pooledJedis) throws Exception {\n    final Jedis jedis = pooledJedis.getObject();\n    if (jedis.getDB() != clientConfig.getDatabase()) {\n      jedis.select(clientConfig.getDatabase());\n    }\n  }\n\n  @Override\n  public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {\n    final Jedis jedis = pooledJedis.getObject();\n    if (jedis.isConnected()) {\n      try {\n        jedis.close();\n      } catch (RuntimeException e) {\n        logger.debug(\"Error while close\", e);\n      }\n    }\n  }\n\n  @Override\n  public PooledObject<Jedis> makeObject() throws Exception {\n    Jedis jedis = null;\n    try {\n      jedis = new Jedis(jedisSocketFactory, clientConfig);\n      return new DefaultPooledObject<>(jedis);\n    } catch (JedisException je) {\n      logger.debug(\"Error while makeObject\", je);\n      throw je;\n    }\n  }\n\n  @Override\n  public void passivateObject(PooledObject<Jedis> pooledJedis) throws Exception {\n    // TODO maybe should select db 0? Not sure right now.\n  }\n\n  @Override\n  public boolean validateObject(PooledObject<Jedis> pooledJedis) {\n    final Jedis jedis = pooledJedis.getObject();\n    try {\n      boolean targetHasNotChanged = true;\n      if (jedisSocketFactory instanceof DefaultJedisSocketFactory) {\n        HostAndPort targetAddress = ((DefaultJedisSocketFactory) jedisSocketFactory).getHostAndPort();\n        HostAndPort objectAddress = jedis.getConnection().getHostAndPort();\n\n        targetHasNotChanged = targetAddress.getHost().equals(objectAddress.getHost())\n            && targetAddress.getPort() == objectAddress.getPort();\n      }\n\n      return targetHasNotChanged\n          && jedis.getConnection().isConnected()\n          && jedis.ping().equals(\"PONG\");\n    } catch (final Exception e) {\n      logger.warn(\"Error while validating pooled Jedis object.\", e);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisMetaInfo.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.InputStream;\nimport java.util.Properties;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Jedis Meta info load version groupId\n */\nclass JedisMetaInfo {\n\n  private static final String groupId;\n  private static final String artifactId;\n  private static final String version;\n\n  static {\n    Properties p = new Properties();\n    try (InputStream in = JedisMetaInfo.class.getClassLoader()\n        .getResourceAsStream(\"redis/clients/jedis/pom.properties\")) {\n      p.load(in);\n    } catch (Exception e) {\n      LoggerFactory.getLogger(JedisMetaInfo.class)\n          .error(\"Load Jedis meta info from pom.properties failed\", e);\n    }\n\n    groupId = p.getProperty(\"groupId\", null);\n    artifactId = p.getProperty(\"artifactId\", null);\n    version = p.getProperty(\"version\", null);\n  }\n\n  public static String getGroupId() {\n    return groupId;\n  }\n\n  public static String getArtifactId() {\n    return artifactId;\n  }\n\n  public static String getVersion() {\n    return version;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisMonitor.java",
    "content": "package redis.clients.jedis;\n\npublic abstract class JedisMonitor {\n\n  protected Connection client;\n\n  public void proceed(Connection client) {\n    this.client = client;\n    this.client.setTimeoutInfinite();\n    do {\n      String command = client.getBulkReply();\n      onCommand(command);\n    } while (client.isConnected());\n  }\n\n  public abstract void onCommand(String command);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisPool.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.util.JedisURIHelper;\nimport redis.clients.jedis.util.Pool;\n\n/**\n * JedisPool is a pooled connection client for standalone Redis servers.\n *\n * @deprecated Use {@link RedisClient} instead. RedisClient provides the same functionality\n *             with a cleaner API and simplified constructor options. For basic usage, simple\n *             constructors are available. For advanced configuration, use {@link RedisClient#builder()}.\n */\n@Deprecated\npublic class JedisPool extends Pool<Jedis> {\n\n  private static final Logger log = LoggerFactory.getLogger(JedisPool.class);\n\n  public JedisPool() {\n    this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);\n  }\n\n  /**\n   * WARNING: This constructor only accepts a uri string as {@code url}. {@link JedisURIHelper#isValid(java.net.URI)}\n   * can be used before this.\n   * <p>\n   * To use a host string, {@link #JedisPool(java.lang.String, int)} can be used with {@link Protocol#DEFAULT_PORT}.\n   *\n   * @param url\n   */\n  public JedisPool(final String url) {\n    this(URI.create(url));\n  }\n\n  /**\n   * WARNING: This constructor only accepts a uri string as {@code url}. {@link JedisURIHelper#isValid(java.net.URI)}\n   * can be used before this.\n   * <p>\n   * To use a host string,\n   * {@link #JedisPool(java.lang.String, int, boolean, javax.net.ssl.SSLSocketFactory, javax.net.ssl.SSLParameters,\n   * javax.net.ssl.HostnameVerifier)} can be used with {@link Protocol#DEFAULT_PORT} and {@code ssl=true}.\n   *\n   * @param url\n   * @param sslSocketFactory\n   * @param sslParameters\n   * @param hostnameVerifier\n   */\n  public JedisPool(final String url, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(new GenericObjectPoolConfig<Jedis>(), new JedisFactory(URI.create(url), Protocol.DEFAULT_TIMEOUT,\n        Protocol.DEFAULT_TIMEOUT, null, sslSocketFactory, sslParameters, hostnameVerifier));\n  }\n\n  public JedisPool(final String host, final int port) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().build());\n  }\n\n  public JedisPool(final String host, final int port, final boolean ssl) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().ssl(ssl).build());\n  }\n\n  public JedisPool(final String host, final int port, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().ssl(ssl)\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public JedisPool(final String host, int port, String user, final String password) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().user(user).password(password).build());\n  }\n\n  public JedisPool(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) {\n    this(new JedisFactory(hostAndPort, clientConfig));\n  }\n\n  public JedisPool(PooledObjectFactory<Jedis> factory) {\n    super(factory);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig) {\n    this(poolConfig, Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);\n  }\n\n  /**\n   * WARNING: This constructor only accepts a uri string as {@code url}. {@link JedisURIHelper#isValid(java.net.URI)}\n   * can be used before this.\n   * <p>\n   * To use a host string,\n   * {@link #JedisPool(org.apache.commons.pool2.impl.GenericObjectPoolConfig, java.lang.String, int)} can be used with\n   * {@link Protocol#DEFAULT_PORT}.\n   *\n   * @param poolConfig\n   * @param url\n   */\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String url) {\n    this(poolConfig, URI.create(url));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final boolean ssl) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, ssl, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      String user, final String password) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, user, password, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final int timeout) {\n    this(poolConfig, host, port, timeout, null);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final int timeout, final boolean ssl) {\n    this(poolConfig, host, port, timeout, null, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final int timeout, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, null, ssl, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password) {\n    this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final boolean ssl) {\n    this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, ssl,\n        sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String user, final String password) {\n    this(poolConfig, host, port, timeout, user, password, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final boolean ssl) {\n    this(poolConfig, host, port, timeout, user, password, Protocol.DEFAULT_DATABASE, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final int database) {\n    this(poolConfig, host, port, timeout, password, database, null);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final boolean ssl) {\n    this(poolConfig, host, port, timeout, password, database, null, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, password, database, null, ssl, sslSocketFactory,\n        sslParameters, hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database) {\n    this(poolConfig, host, port, timeout, user, password, database, null);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database, final boolean ssl) {\n    this(poolConfig, host, port, timeout, user, password, database, null, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final String clientName) {\n    this(poolConfig, host, port, timeout, timeout, password, database, clientName);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final String clientName,\n      final boolean ssl) {\n    this(poolConfig, host, port, timeout, timeout, password, database, clientName, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final String clientName,\n      final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, timeout, password, database, clientName, ssl,\n        sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database,\n      final String clientName) {\n    this(poolConfig, host, port, timeout, timeout, user, password, database, clientName);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database,\n      final String clientName, final boolean ssl) {\n    this(poolConfig, host, port, timeout, timeout, user, password, database, clientName, ssl);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String password, final int database,\n      final String clientName) {\n    this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, password,\n        database, clientName));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final int connectionTimeout, final int soTimeout, final String password,\n      final int database, final String clientName, final boolean ssl) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, password, database, clientName, ssl,\n        null, null, null);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String password, final int database,\n      final String clientName, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, password,\n        database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String user, final String password,\n      final int database, final String clientName) {\n    this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, user, password,\n        database, clientName));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,\n      final int port, final int connectionTimeout, final int soTimeout, final String user,\n      final String password, final int database, final String clientName, final boolean ssl) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, user, password, database,\n        clientName, ssl, null, null, null);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String user, final String password,\n      final int database, final String clientName, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, 0, user, password, database,\n        clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String password, final int database, final String clientName, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, infiniteSoTimeout, null, password,\n        database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String user, final String password, final int database, final String clientName) {\n    this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, infiniteSoTimeout,\n        user, password, database, clientName));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String user, final String password, final int database, final String clientName,\n      final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, infiniteSoTimeout,\n        user, password, database, clientName, ssl, sslSocketFactory, sslParameters,\n        hostnameVerifier));\n  }\n\n  public JedisPool(final URI uri) {\n    this(new GenericObjectPoolConfig<Jedis>(), uri);\n  }\n\n  public JedisPool(final URI uri, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(new GenericObjectPoolConfig<Jedis>(), uri, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPool(final URI uri, final int timeout) {\n    this(new GenericObjectPoolConfig<Jedis>(), uri, timeout);\n  }\n\n  public JedisPool(final URI uri, final int timeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(new GenericObjectPoolConfig<Jedis>(), uri, timeout, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri) {\n    this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri, final int timeout) {\n    this(poolConfig, uri, timeout, timeout);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,\n      final int timeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, uri, timeout, timeout, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,\n      final int connectionTimeout, final int soTimeout) {\n    this(poolConfig, uri, connectionTimeout, soTimeout, null, null, null);\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,\n      final int connectionTimeout, final int soTimeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, new JedisFactory(uri, connectionTimeout, soTimeout, null, sslSocketFactory,\n        sslParameters, hostnameVerifier));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, new JedisFactory(uri, connectionTimeout, soTimeout, infiniteSoTimeout, null,\n        sslSocketFactory, sslParameters, hostnameVerifier));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final HostAndPort hostAndPort,\n      final JedisClientConfig clientConfig) {\n    this(poolConfig, new JedisFactory(hostAndPort, clientConfig));\n  }\n\n  public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig,\n      final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) {\n    this(poolConfig, new JedisFactory(jedisSocketFactory, clientConfig));\n  }\n\n  public JedisPool(GenericObjectPoolConfig<Jedis> poolConfig, PooledObjectFactory<Jedis> factory) {\n    super(poolConfig, factory);\n  }\n\n  @Override\n  public Jedis getResource() {\n    Jedis jedis = super.getResource();\n    jedis.setDataSource(this);\n    return jedis;\n  }\n\n  @Override\n  public void returnResource(final Jedis resource) {\n    if (resource != null) {\n      try {\n        resource.resetState();\n        super.returnResource(resource);\n      } catch (RuntimeException e) {\n        super.returnBrokenResource(resource);\n        log.warn(\"Resource is returned to the pool as broken\", e);\n      }\n    }\n  }\n\n  public void withResource(Consumer<Jedis> consumer) {\n    try (Jedis jedis = this.getResource()) {\n      consumer.accept(jedis);\n    }\n  }\n\n  public <K> K withResourceGet(Function<Jedis, K> function) {\n    try (Jedis jedis = this.getResource()) {\n      return function.apply(jedis);\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisPoolConfig.java",
    "content": "package redis.clients.jedis;\n\nimport java.time.Duration;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\n/**\n * Configuration class for {@link JedisPool} connection pooling.\n *\n * @deprecated JedisPoolConfig is used with the deprecated {@link JedisPool} and {@link JedisSentinelPool} classes.\n *             Use {@link ConnectionPoolConfig} instead, which is designed for the modern {@link RedisClient}\n *             and {@link RedisSentinelClient} classes. ConnectionPoolConfig provides the same pooling configuration\n *             options with better integration into the new client architecture.\n */\n@Deprecated\npublic class JedisPoolConfig extends GenericObjectPoolConfig<Jedis> {\n\n  public JedisPoolConfig() {\n    // defaults to make your life with connection pool easier :)\n    setTestWhileIdle(true);\n    setMinEvictableIdleTime(Duration.ofMillis(60000));\n    setTimeBetweenEvictionRuns(Duration.ofMillis(30000));\n    setNumTestsPerEvictionRun(-1);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisPooled.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.builders.StandaloneClientBuilder;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.csc.CacheFactory;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\nimport redis.clients.jedis.util.JedisURIHelper;\nimport redis.clients.jedis.util.Pool;\n\n/**\n * JedisPooled is a pooled connection client for standalone Redis servers.\n *\n * @deprecated Use {@link RedisClient} instead. RedisClient provides the same functionality\n *             with a cleaner API and simplified constructor options. For basic usage, simple\n *             constructors are available. For advanced configuration, use {@link RedisClient#builder()}.\n */\n@Deprecated\npublic class JedisPooled extends UnifiedJedis {\n\n  public JedisPooled() {\n    this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);\n  }\n\n  /**\n   * WARNING: This constructor only accepts a uri string as {@code url}. {@link JedisURIHelper#isValid(java.net.URI)}\n   * can be used before this.\n   * <p>\n   * To use a host string, {@link #JedisPooled(java.lang.String, int)} can be used with {@link Protocol#DEFAULT_PORT}.\n   *\n   * @param url\n   */\n  public JedisPooled(final String url) {\n    super(url);\n  }\n\n  /**\n   * WARNING: This constructor only accepts a uri string as {@code url}. {@link JedisURIHelper#isValid(java.net.URI)}\n   * can be used before this.\n   * <p>\n   * To use a host string, {@link #JedisPooled(java.lang.String, int, boolean, javax.net.ssl.SSLSocketFactory,\n   * javax.net.ssl.SSLParameters, javax.net.ssl.HostnameVerifier)} can be used with {@link Protocol#DEFAULT_PORT} and\n   * {@code ssl=true}.\n   *\n   * @param url\n   * @param sslSocketFactory\n   * @param sslParameters\n   * @param hostnameVerifier\n   */\n  public JedisPooled(final String url, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(URI.create(url), sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final String host, final int port) {\n    this(new HostAndPort(host, port));\n  }\n\n  public JedisPooled(final HostAndPort hostAndPort) {\n    super(hostAndPort);\n  }\n\n  public JedisPooled(final String host, final int port, final boolean ssl) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().ssl(ssl).build());\n  }\n\n  public JedisPooled(final String host, final int port, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().ssl(ssl)\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build());\n  }\n\n  public JedisPooled(final String host, final int port, final String user, final String password) {\n    this(new HostAndPort(host, port), DefaultJedisClientConfig.builder().user(user).password(password).build());\n  }\n\n  public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) {\n    super(hostAndPort, clientConfig);\n  }\n\n  @Experimental\n  public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig) {\n    this(hostAndPort, clientConfig, CacheFactory.getCache(cacheConfig));\n  }\n\n  @Experimental\n  public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache) {\n    super(hostAndPort, clientConfig, clientSideCache);\n  }\n\n  public JedisPooled(PooledObjectFactory<Connection> factory) {\n    this(new PooledConnectionProvider(factory));\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(poolConfig, Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);\n  }\n\n  /**\n   * WARNING: This constructor only accepts a uri string as {@code url}. {@link JedisURIHelper#isValid(java.net.URI)}\n   * can be used before this.\n   * <p>\n   * To use a host string,\n   * {@link #JedisPooled(org.apache.commons.pool2.impl.GenericObjectPoolConfig, java.lang.String, int)} can be used with\n   * {@link Protocol#DEFAULT_PORT}.\n   *\n   * @param poolConfig\n   * @param url\n   */\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String url) {\n    this(poolConfig, URI.create(url));\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port, final boolean ssl) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, ssl, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port, final String user, final String password) {\n    this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, user, password,\n        Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port, final int timeout) {\n    this(poolConfig, host, port, timeout, null);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port, final int timeout, final boolean ssl) {\n    this(poolConfig, host, port, timeout, null, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host,\n      final int port, final int timeout, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, null, ssl, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password) {\n    this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final boolean ssl) {\n    this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, ssl, sslSocketFactory,\n        sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String user, final String password) {\n    this(poolConfig, host, port, timeout, user, password, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final boolean ssl) {\n    this(poolConfig, host, port, timeout, user, password, Protocol.DEFAULT_DATABASE, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final int database) {\n    this(poolConfig, host, port, timeout, password, database, null);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final boolean ssl) {\n    this(poolConfig, host, port, timeout, password, database, null, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, password, database, null, ssl, sslSocketFactory,\n        sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database) {\n    this(poolConfig, host, port, timeout, user, password, database, null);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database, final boolean ssl) {\n    this(poolConfig, host, port, timeout, user, password, database, null, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final String clientName) {\n    this(poolConfig, host, port, timeout, timeout, password, database, clientName);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final String clientName,\n      final boolean ssl) {\n    this(poolConfig, host, port, timeout, timeout, password, database, clientName, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String password, final int database, final String clientName,\n      final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, timeout, timeout, password, database, clientName, ssl,\n        sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database,\n      final String clientName) {\n    this(poolConfig, host, port, timeout, timeout, user, password, database, clientName);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      int timeout, final String user, final String password, final int database,\n      final String clientName, final boolean ssl) {\n    this(poolConfig, host, port, timeout, timeout, user, password, database, clientName, ssl);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String password, final int database,\n      final String clientName) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, null, password, database, clientName);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String password, final int database,\n      final String clientName, final boolean ssl) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, password, database, clientName, ssl,\n        null, null, null);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String password, final int database,\n      final String clientName, final boolean ssl, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, null, password, database, clientName,\n        ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String user, final String password,\n      final int database, final String clientName) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, 0, user, password, database, clientName);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String user, final String password,\n      final int database, final String clientName, final boolean ssl) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, user, password, database, clientName,\n        ssl, null, null, null);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final String user, final String password,\n      final int database, final String clientName, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, 0, user, password, database,\n        clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String password, final int database, final String clientName, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, host, port, connectionTimeout, soTimeout, infiniteSoTimeout, null, password,\n        database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout, final String user,\n      final String password, final int database, final String clientName) {\n    this(new HostAndPort(host, port),\n        DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)\n            .blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password).database(database)\n            .clientName(clientName).build(),\n        poolConfig);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout, final String user,\n      final String password, final int database, final String clientName, final boolean ssl,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(new HostAndPort(host, port),\n        DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)\n            .blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password).database(database)\n            .clientName(clientName).ssl(ssl).sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n            .hostnameVerifier(hostnameVerifier).build(),\n        poolConfig);\n  }\n\n  public JedisPooled(final URI uri) {\n    super(uri);\n  }\n\n  public JedisPooled(final URI uri, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(new GenericObjectPoolConfig<Connection>(), uri, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPooled(final URI uri, final int timeout) {\n    this(new GenericObjectPoolConfig<Connection>(), uri, timeout);\n  }\n\n  public JedisPooled(final URI uri, final int timeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(new GenericObjectPoolConfig<Connection>(), uri, timeout, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri) {\n    this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT, sslSocketFactory, sslParameters,\n        hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri,\n      final int timeout) {\n    this(poolConfig, uri, timeout, timeout);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri,\n      final int timeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, uri, timeout, timeout, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri,\n      final int connectionTimeout, final int soTimeout) {\n    this(poolConfig, uri, connectionTimeout, soTimeout, null, null, null);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri,\n      final int connectionTimeout, final int soTimeout, final SSLSocketFactory sslSocketFactory,\n      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {\n    this(poolConfig, uri, connectionTimeout, soTimeout, 0, sslSocketFactory, sslParameters, hostnameVerifier);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final URI uri,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,\n      final HostnameVerifier hostnameVerifier) {\n    this(new HostAndPort(uri.getHost(), uri.getPort()), DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)\n        .blockingSocketTimeoutMillis(infiniteSoTimeout).user(JedisURIHelper.getUser(uri))\n        .password(JedisURIHelper.getPassword(uri)).database(JedisURIHelper.getDBIndex(uri))\n        .protocol(JedisURIHelper.getRedisProtocol(uri)).ssl(JedisURIHelper.isRedisSSLScheme(uri))\n        .sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)\n        .hostnameVerifier(hostnameVerifier).build(), poolConfig);\n  }\n\n  public JedisPooled(final HostAndPort hostAndPort, final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(hostAndPort, DefaultJedisClientConfig.builder().build(), poolConfig);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final HostAndPort hostAndPort,\n      final JedisClientConfig clientConfig) {\n    this(hostAndPort, clientConfig, poolConfig);\n  }\n\n  public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    super(new PooledConnectionProvider(hostAndPort, clientConfig, poolConfig), clientConfig.getRedisProtocol());\n  }\n\n  @Experimental\n  public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    this(hostAndPort, clientConfig, CacheFactory.getCache(cacheConfig), poolConfig);\n  }\n\n  @Experimental\n  public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache,\n      final GenericObjectPoolConfig<Connection> poolConfig) {\n    super(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache, poolConfig),\n        clientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig,\n      final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) {\n    super(new PooledConnectionProvider(new ConnectionFactory(jedisSocketFactory, clientConfig), poolConfig),\n        clientConfig.getRedisProtocol());\n  }\n\n  public JedisPooled(GenericObjectPoolConfig<Connection> poolConfig, PooledObjectFactory<Connection> factory) {\n    this(factory, poolConfig);\n  }\n\n  public JedisPooled(PooledObjectFactory<Connection> factory, GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new PooledConnectionProvider(factory, poolConfig));\n  }\n\n  public JedisPooled(PooledConnectionProvider provider) {\n    super(provider);\n  }\n\n  private JedisPooled(CommandExecutor commandExecutor, ConnectionProvider connectionProvider, CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n  }\n\n  /**\n   * Fluent builder for {@link JedisPooled} (standalone).\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  static public class Builder extends StandaloneClientBuilder<JedisPooled> {\n\n    @Override\n    protected JedisPooled createClient() {\n      return new JedisPooled(commandExecutor, connectionProvider, commandObjects, clientConfig.getRedisProtocol(),\n          cache);\n    }\n  }\n\n  /**\n   * Create a new builder for configuring JedisPooled instances.\n   * @return a new {@link JedisPooled.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public final Pool<Connection> getPool() {\n    return ((PooledConnectionProvider) provider).getPool();\n  }\n\n  @Override\n  public Pipeline pipelined() {\n    return (Pipeline) super.pipelined();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisPubSub.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic abstract class JedisPubSub extends JedisPubSubBase<String> {\n\n  @Override\n  protected final String encode(byte[] raw) {\n    return SafeEncoder.encode(raw);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisPubSubBase.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.ResponseKeyword.*;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic abstract class JedisPubSubBase<T> {\n\n  private int subscribedChannels = 0;\n  private final JedisSafeAuthenticator authenticator = new JedisSafeAuthenticator();\n  private final Consumer<Object> pingResultHandler = this::processPingReply;\n\n  public void onMessage(T channel, T message) {\n  }\n\n  public void onPMessage(T pattern, T channel, T message) {\n  }\n\n  public void onSubscribe(T channel, int subscribedChannels) {\n  }\n\n  public void onUnsubscribe(T channel, int subscribedChannels) {\n  }\n\n  public void onPUnsubscribe(T pattern, int subscribedChannels) {\n  }\n\n  public void onPSubscribe(T pattern, int subscribedChannels) {\n  }\n\n  public void onPong(T pattern) {\n  }\n\n  private void sendAndFlushCommand(Command command, T... args) {\n    authenticator.sendAndFlushCommand(command, args);\n  }\n\n  public final void unsubscribe() {\n    sendAndFlushCommand(Command.UNSUBSCRIBE);\n  }\n\n  public final void unsubscribe(T... channels) {\n    sendAndFlushCommand(Command.UNSUBSCRIBE, channels);\n  }\n\n  public final void subscribe(T... channels) {\n    checkConnectionSuitableForPubSub();\n    sendAndFlushCommand(Command.SUBSCRIBE, channels);\n  }\n\n  public final void psubscribe(T... patterns) {\n    checkConnectionSuitableForPubSub();\n    sendAndFlushCommand(Command.PSUBSCRIBE, patterns);\n  }\n\n  private void checkConnectionSuitableForPubSub() {\n    if (authenticator.client.protocol != RedisProtocol.RESP3\n        && authenticator.client.isTokenBasedAuthenticationEnabled()) {\n      throw new JedisException(\n          \"Blocking pub/sub operations are not supported on token-based authentication enabled connections with RESP2 protocol!\");\n    }\n  }\n\n  public final void punsubscribe() {\n    sendAndFlushCommand(Command.PUNSUBSCRIBE);\n  }\n\n  public final void punsubscribe(T... patterns) {\n    sendAndFlushCommand(Command.PUNSUBSCRIBE, patterns);\n  }\n\n  public final void ping() {\n    authenticator.commandSync.lock();\n    try {\n      sendAndFlushCommand(Command.PING);\n      authenticator.resultHandler.add(pingResultHandler);\n    } finally {\n      authenticator.commandSync.unlock();\n    }\n  }\n\n  public final void ping(T argument) {\n    authenticator.commandSync.lock();\n    try {\n      sendAndFlushCommand(Command.PING, argument);\n      authenticator.resultHandler.add(pingResultHandler);\n    } finally {\n      authenticator.commandSync.unlock();\n    }\n  }\n\n  public final boolean isSubscribed() {\n    return subscribedChannels > 0;\n  }\n\n  public final int getSubscribedChannels() {\n    return subscribedChannels;\n  }\n\n  public final void proceed(Connection client, T... channels) {\n    authenticator.registerForAuthentication(client);\n    authenticator.client.setTimeoutInfinite();\n    try {\n      subscribe(channels);\n      process();\n    } finally {\n      authenticator.client.rollbackTimeout();\n    }\n  }\n\n  public final void proceedWithPatterns(Connection client, T... patterns) {\n    authenticator.registerForAuthentication(client);\n    authenticator.client.setTimeoutInfinite();\n    try {\n      psubscribe(patterns);\n      process();\n    } finally {\n      authenticator.client.rollbackTimeout();\n    }\n  }\n\n  protected abstract T encode(byte[] raw);\n\n  //  private void process(Client client) {\n  private void process() {\n\n    do {\n      Object reply = authenticator.client.getUnflushedObject();\n\n      if (reply instanceof List) {\n        List<Object> listReply = (List<Object>) reply;\n        final Object firstObj = listReply.get(0);\n        if (!(firstObj instanceof byte[])) {\n          throw new JedisException(\"Unknown message type: \" + firstObj);\n        }\n        final byte[] resp = (byte[]) firstObj;\n        if (Arrays.equals(SUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          onSubscribe(enchannel, subscribedChannels);\n        } else if (Arrays.equals(UNSUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          onUnsubscribe(enchannel, subscribedChannels);\n        } else if (Arrays.equals(MESSAGE.getRaw(), resp)) {\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final Object mesg = listReply.get(2);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          if (mesg instanceof List) {\n            ((List<byte[]>) mesg).forEach(bmesg -> onMessage(enchannel, encode(bmesg)));\n          } else {\n            onMessage(enchannel, (mesg == null) ? null : encode((byte[]) mesg));\n          }\n        } else if (Arrays.equals(PMESSAGE.getRaw(), resp)) {\n          final byte[] bpattern = (byte[]) listReply.get(1);\n          final byte[] bchannel = (byte[]) listReply.get(2);\n          final byte[] bmesg = (byte[]) listReply.get(3);\n          final T enpattern = (bpattern == null) ? null : encode(bpattern);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          final T enmesg = (bmesg == null) ? null : encode(bmesg);\n          onPMessage(enpattern, enchannel, enmesg);\n        } else if (Arrays.equals(PSUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bpattern = (byte[]) listReply.get(1);\n          final T enpattern = (bpattern == null) ? null : encode(bpattern);\n          onPSubscribe(enpattern, subscribedChannels);\n        } else if (Arrays.equals(PUNSUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bpattern = (byte[]) listReply.get(1);\n          final T enpattern = (bpattern == null) ? null : encode(bpattern);\n          onPUnsubscribe(enpattern, subscribedChannels);\n        } else if (Arrays.equals(PONG.getRaw(), resp)) {\n          final byte[] bpattern = (byte[]) listReply.get(1);\n          final T enpattern = (bpattern == null) ? null : encode(bpattern);\n          onPong(enpattern);\n        } else {\n          throw new JedisException(\"Unknown message type: \" + firstObj);\n        }\n      } else if (reply instanceof byte[]) {\n        Consumer<Object> resultHandler = authenticator.resultHandler.poll();\n        if (resultHandler == null) {\n          throw new JedisException(\"Unexpected message : \" + SafeEncoder.encode((byte[]) reply));\n        }\n        resultHandler.accept(reply);\n      } else {\n        throw new JedisException(\"Unknown message type: \" + reply);\n      }\n    } while (!Thread.currentThread().isInterrupted() && isSubscribed());\n\n    //    /* Invalidate instance since this thread is no longer listening */\n    //    this.client = null;\n  }\n\n  private void processPingReply(Object reply) {\n    byte[] resp = (byte[]) reply;\n    if (\"PONG\".equals(SafeEncoder.encode(resp))) {\n      onPong(null);\n    } else {\n      onPong(encode(resp));\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisSafeAuthenticator.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.function.Consumer;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.authentication.core.SimpleToken;\nimport redis.clients.authentication.core.Token;\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.authentication.JedisAuthenticationException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.SafeEncoder;\n\nclass JedisSafeAuthenticator {\n\n  private static final Token PLACEHOLDER_TOKEN = new SimpleToken(null, null, 0, 0, null);\n  private static final Logger logger = LoggerFactory.getLogger(JedisSafeAuthenticator.class);\n\n  protected volatile Connection client;\n  protected final Consumer<Object> authResultHandler = this::processAuthReply;\n  protected final Consumer<Token> authenticationHandler = this::safeReAuthenticate;\n\n  protected final AtomicReference<Token> pendingTokenRef = new AtomicReference<Token>(null);\n  protected final ReentrantLock commandSync = new ReentrantLock();\n  protected final Queue<Consumer<Object>> resultHandler = new ConcurrentLinkedQueue<Consumer<Object>>();\n\n  protected void sendAndFlushCommand(Command command, Object... args) {\n    if (client == null) {\n      throw new JedisException(getClass() + \" is not connected to a Connection.\");\n    }\n    CommandArguments cargs = new CommandArguments(command).addObjects(args);\n\n    Token newToken = pendingTokenRef.getAndSet(PLACEHOLDER_TOKEN);\n\n    // lets send the command without locking !!IF!! we know that pendingTokenRef is null replaced with PLACEHOLDER_TOKEN and no re-auth will go into action\n    // !!ELSE!! we are locking since we already know a re-auth is still in progress in another thread and we need to wait for it to complete, we do nothing but wait on it!\n    if (newToken != null) {\n      commandSync.lock();\n    }\n    try {\n      client.sendCommand(cargs);\n      client.flush();\n    } finally {\n      Token newerToken = pendingTokenRef.getAndSet(null);\n      // lets check if a newer token received since the beginning of this sendAndFlushCommand call\n      if (newerToken != null && newerToken != PLACEHOLDER_TOKEN) {\n        safeReAuthenticate(newerToken);\n      }\n      if (newToken != null) {\n        commandSync.unlock();\n      }\n    }\n  }\n\n  protected void registerForAuthentication(Connection newClient) {\n    Connection oldClient = this.client;\n    if (oldClient == newClient) return;\n    if (oldClient != null && oldClient.getAuthXManager() != null) {\n      oldClient.getAuthXManager().removePostAuthenticationHook(authenticationHandler);\n    }\n    if (newClient != null && newClient.getAuthXManager() != null) {\n      newClient.getAuthXManager().addPostAuthenticationHook(authenticationHandler);\n    }\n    this.client = newClient;\n  }\n\n  private void safeReAuthenticate(Token token) {\n    try {\n      byte[] rawPass = client.encodeToBytes(token.getValue().toCharArray());\n      byte[] rawUser = client.encodeToBytes(token.getUser().toCharArray());\n\n      Token newToken = pendingTokenRef.getAndSet(token);\n      if (newToken == null) {\n        commandSync.lock();\n        try {\n          sendAndFlushCommand(Command.AUTH, rawUser, rawPass);\n          resultHandler.add(this.authResultHandler);\n        } finally {\n          pendingTokenRef.set(null);\n          commandSync.unlock();\n        }\n      }\n    } catch (Exception e) {\n      logger.error(\"Error while re-authenticating connection\", e);\n      client.getAuthXManager().getListener().onConnectionAuthenticationError(e);\n    }\n  }\n\n  protected void processAuthReply(Object reply) {\n    byte[] resp = (byte[]) reply;\n    String response = SafeEncoder.encode(resp);\n    if (!\"OK\".equals(response)) {\n      String msg = \"Re-authentication failed with server response: \" + response;\n      Exception failedAuth = new JedisAuthenticationException(msg);\n      logger.error(failedAuth.getMessage(), failedAuth);\n      client.getAuthXManager().getListener().onConnectionAuthenticationError(failedAuth);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisSentinelPool.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.Pool;\n\n/**\n * JedisSentinelPool is a pooled connection client for Redis Sentinel deployments.\n *\n * @deprecated Use {@link RedisSentinelClient} instead. RedisSentinelClient provides the same functionality\n *             with a cleaner API and simplified constructor options. For basic usage, simple\n *             constructors are available. For advanced configuration, use {@link RedisSentinelClient#builder()}.\n */\n@Deprecated\npublic class JedisSentinelPool extends Pool<Jedis> {\n\n  private static final Logger LOG = LoggerFactory.getLogger(JedisSentinelPool.class);\n\n  private final JedisFactory factory;\n\n  private final JedisClientConfig sentinelClientConfig;\n\n  protected final Collection<MasterListener> masterListeners = new ArrayList<>();\n\n  private volatile HostAndPort currentHostMaster;\n  \n  private final Lock initPoolLock = new ReentrantLock(true);\n\n  public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,\n      final JedisClientConfig masterClientConfig, final JedisClientConfig sentinelClientConfig) {\n    this(masterName, sentinels, new JedisFactory(masterClientConfig), sentinelClientConfig);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig) {\n    this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, null,\n        Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels) {\n    this(masterName, sentinels, new GenericObjectPoolConfig<Jedis>(), Protocol.DEFAULT_TIMEOUT, null,\n        Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels, String password) {\n    this(masterName, sentinels, new GenericObjectPoolConfig<Jedis>(), Protocol.DEFAULT_TIMEOUT, password);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels, String password, String sentinelPassword) {\n    this(masterName, sentinels, new GenericObjectPoolConfig<Jedis>(), Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT,\n        password, Protocol.DEFAULT_DATABASE, null, Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, sentinelPassword, null);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, int timeout, final String password) {\n    this(masterName, sentinels, poolConfig, timeout, password, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int timeout) {\n    this(masterName, sentinels, poolConfig, timeout, null, Protocol.DEFAULT_DATABASE);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final String password) {\n    this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, password);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, int timeout, final String password,\n      final int database) {\n    this(masterName, sentinels, poolConfig, timeout, timeout, null, password, database);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, int timeout, final String user,\n      final String password, final int database) {\n    this(masterName, sentinels, poolConfig, timeout, timeout, user, password, database);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, int timeout, final String password,\n      final int database, final String clientName) {\n    this(masterName, sentinels, poolConfig, timeout, timeout, password, database, clientName);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, int timeout, final String user,\n      final String password, final int database, final String clientName) {\n    this(masterName, sentinels, poolConfig, timeout, timeout, user, password, database, clientName);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout,\n      final String password, final int database) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, null, password, database, null);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout,\n      final String user, final String password, final int database) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, user, password, database, null);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout,\n      final String password, final int database, final String clientName) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, null, password, database, clientName);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout,\n      final String user, final String password, final int database, final String clientName) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, user, password, database, clientName,\n        Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null, null, null);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String user, final String password, final int database, final String clientName) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout, user, password, database, clientName,\n        Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null, null, null);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout,\n      final String password, final int database, final String clientName,\n      final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelPassword,\n      final String sentinelClientName) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, null, password, database, clientName,\n        sentinelConnectionTimeout, sentinelSoTimeout, null, sentinelPassword, sentinelClientName);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final int connectionTimeout, final int soTimeout,\n      final String user, final String password, final int database, final String clientName,\n      final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser,\n      final String sentinelPassword, final String sentinelClientName) {\n    this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, 0, user, password, database, clientName,\n        sentinelConnectionTimeout, sentinelSoTimeout, sentinelUser, sentinelPassword, sentinelClientName);\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig,\n      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,\n      final String user, final String password, final int database, final String clientName,\n      final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser,\n      final String sentinelPassword, final String sentinelClientName) {\n    this(masterName, parseHostAndPorts(sentinels), poolConfig,\n        DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)\n            .socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout)\n            .user(user).password(password).database(database).clientName(clientName).build(),\n        DefaultJedisClientConfig.builder().connectionTimeoutMillis(sentinelConnectionTimeout)\n            .socketTimeoutMillis(sentinelSoTimeout).user(sentinelUser).password(sentinelPassword)\n            .clientName(sentinelClientName).build()\n    );\n  }\n\n  public JedisSentinelPool(String masterName, Set<String> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final JedisFactory factory) {\n    this(masterName, parseHostAndPorts(sentinels), poolConfig, factory,\n        DefaultJedisClientConfig.builder().build());\n  }\n\n  public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final JedisClientConfig masterClientConfig,\n      final JedisClientConfig sentinelClientConfig) {\n    this(masterName, sentinels, poolConfig, new JedisFactory(masterClientConfig), sentinelClientConfig);\n  }\n\n  public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,\n      final JedisFactory factory, final JedisClientConfig sentinelClientConfig) {\n    super(factory);\n\n    this.factory = factory;\n    this.sentinelClientConfig = sentinelClientConfig;\n\n    HostAndPort master = initSentinels(sentinels, masterName);\n    initMaster(master);\n  }\n\n  public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,\n      final GenericObjectPoolConfig<Jedis> poolConfig, final JedisFactory factory,\n      final JedisClientConfig sentinelClientConfig) {\n    super(poolConfig, factory);\n\n    this.factory = factory;\n    this.sentinelClientConfig = sentinelClientConfig;\n\n    HostAndPort master = initSentinels(sentinels, masterName);\n    initMaster(master);\n  }\n\n  private static Set<HostAndPort> parseHostAndPorts(Set<String> strings) {\n    return strings.stream().map(HostAndPort::from).collect(Collectors.toSet());\n  }\n\n  @Override\n  public void destroy() {\n    for (MasterListener m : masterListeners) {\n      m.shutdown();\n    }\n\n    super.destroy();\n  }\n\n  public HostAndPort getCurrentHostMaster() {\n    return currentHostMaster;\n  }\n\n  private void initMaster(HostAndPort master) {\n    initPoolLock.lock();\n    \n    try {\n      if (!master.equals(currentHostMaster)) {\n        currentHostMaster = master;\n        factory.setHostAndPort(currentHostMaster);\n        // although we clear the pool, we still have to check the returned object in getResource,\n        // this call only clears idle instances, not borrowed instances\n        super.clear();\n\n        LOG.info(\"Created JedisSentinelPool to master at {}\", master);\n      }\n    } finally {\n      initPoolLock.unlock();\n    }\n  }\n\n  private HostAndPort initSentinels(Set<HostAndPort> sentinels, final String masterName) {\n\n    HostAndPort master = null;\n    boolean sentinelAvailable = false;\n\n    LOG.info(\"Trying to find master from available Sentinels...\");\n\n    for (HostAndPort sentinel : sentinels) {\n\n      LOG.debug(\"Connecting to Sentinel {}\", sentinel);\n\n      try (Jedis jedis = new Jedis(sentinel, sentinelClientConfig)) {\n\n        List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);\n\n        // connected to sentinel...\n        sentinelAvailable = true;\n\n        if (masterAddr == null || masterAddr.size() != 2) {\n          LOG.warn(\"Can not get master addr, master name: {}. Sentinel: {}\", masterName, sentinel);\n          continue;\n        }\n\n        master = toHostAndPort(masterAddr);\n        LOG.debug(\"Found Redis master at {}\", master);\n        break;\n      } catch (JedisException e) {\n        // resolves #1036, it should handle JedisException there's another chance\n        // of raising JedisDataException\n        LOG.warn(\n          \"Cannot get master address from sentinel running @ {}. Reason: {}. Trying next one.\", sentinel, e);\n      }\n    }\n\n    if (master == null) {\n      if (sentinelAvailable) {\n        // can connect to sentinel, but master name seems to not monitored\n        throw new JedisException(\"Can connect to sentinel, but \" + masterName\n            + \" seems to be not monitored...\");\n      } else {\n        throw new JedisConnectionException(\"All sentinels down, cannot determine where is \"\n            + masterName + \" master is running...\");\n      }\n    }\n\n    LOG.info(\"Redis master running at {}, starting Sentinel listeners...\", master);\n\n    for (HostAndPort sentinel : sentinels) {\n\n      MasterListener masterListener = new MasterListener(masterName, sentinel.getHost(), sentinel.getPort());\n      // whether MasterListener threads are alive or not, process can be stopped\n      masterListener.setDaemon(true);\n      masterListeners.add(masterListener);\n      masterListener.start();\n    }\n\n    return master;\n  }\n\n  private HostAndPort toHostAndPort(List<String> getMasterAddrByNameResult) {\n    String host = getMasterAddrByNameResult.get(0);\n    int port = Integer.parseInt(getMasterAddrByNameResult.get(1));\n\n    return new HostAndPort(host, port);\n  }\n\n  @Override\n  public Jedis getResource() {\n    while (true) {\n      Jedis jedis = super.getResource();\n      jedis.setDataSource(this);\n\n      // get a reference because it can change concurrently\n      final HostAndPort master = currentHostMaster;\n      final HostAndPort connection = jedis.getClient().getHostAndPort();\n\n      if (master.equals(connection)) {\n        // connected to the correct master\n        return jedis;\n      } else {\n        returnBrokenResource(jedis);\n      }\n    }\n  }\n\n  @Override\n  public void returnResource(final Jedis resource) {\n    if (resource != null) {\n      try {\n        resource.resetState();\n        super.returnResource(resource);\n      } catch (RuntimeException e) {\n        returnBrokenResource(resource);\n        LOG.debug(\"Resource is returned to the pool as broken\", e);\n      }\n    }\n  }\n\n  protected class MasterListener extends Thread {\n\n    protected String masterName;\n    protected String host;\n    protected int port;\n    protected long subscribeRetryWaitTimeMillis = 5000;\n    protected volatile Jedis j;\n    protected AtomicBoolean running = new AtomicBoolean(false);\n\n    protected MasterListener() {\n    }\n\n    public MasterListener(String masterName, String host, int port) {\n      super(String.format(\"MasterListener-%s-[%s:%d]\", masterName, host, port));\n      this.masterName = masterName;\n      this.host = host;\n      this.port = port;\n    }\n\n    public MasterListener(String masterName, String host, int port,\n        long subscribeRetryWaitTimeMillis) {\n      this(masterName, host, port);\n      this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;\n    }\n\n    @Override\n    public void run() {\n\n      running.set(true);\n\n      while (running.get()) {\n\n        try {\n          // double check that it is not being shutdown\n          if (!running.get()) {\n            break;\n          }\n          \n          final HostAndPort hostPort = new HostAndPort(host, port);\n          j = new Jedis(hostPort, sentinelClientConfig);\n\n          // code for active refresh\n          List<String> masterAddr = j.sentinelGetMasterAddrByName(masterName);\n          if (masterAddr == null || masterAddr.size() != 2) {\n            LOG.warn(\"Can not get master addr, master name: {}. Sentinel: {}.\", masterName,\n                hostPort);\n          } else {\n            initMaster(toHostAndPort(masterAddr));\n          }\n\n          j.subscribe(new JedisPubSub() {\n            @Override\n            public void onMessage(String channel, String message) {\n              LOG.debug(\"Sentinel {} published: {}.\", hostPort, message);\n\n              String[] switchMasterMsg = message.split(\" \");\n\n              if (switchMasterMsg.length > 3) {\n\n                if (masterName.equals(switchMasterMsg[0])) {\n                  initMaster(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));\n                } else {\n                  LOG.debug(\n                    \"Ignoring message on +switch-master for master name {}, our master name is {}\",\n                    switchMasterMsg[0], masterName);\n                }\n\n              } else {\n                LOG.error(\"Invalid message received on Sentinel {} on channel +switch-master: {}\",\n                    hostPort, message);\n              }\n            }\n          }, \"+switch-master\");\n\n        } catch (JedisException e) {\n\n          if (running.get()) {\n            LOG.warn(\"Lost connection to Sentinel {}:{}. Sleeping {}ms and retrying.\", host, port, subscribeRetryWaitTimeMillis,\n                    e);\n            try {\n              Thread.sleep(subscribeRetryWaitTimeMillis);\n            } catch (InterruptedException e1) {\n              LOG.error(\"Sleep interrupted: \", e1);\n            }\n          } else {\n            LOG.debug(\"Unsubscribing from Sentinel at {}:{}\", host, port);\n          }\n        } finally {\n          if (j != null) {\n            j.close();\n          }\n        }\n      }\n    }\n\n    public void shutdown() {\n      try {\n        LOG.debug(\"Shutting down listener on {}:{}\", host, port);\n        running.set(false);\n        // This isn't good, the Jedis object is not thread safe\n        if (j != null) {\n          j.close();\n        }\n      } catch (RuntimeException e) {\n        LOG.error(\"Caught exception while shutting down: \", e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisSentineled.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.Set;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.builders.SentinelClientBuilder;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.csc.CacheFactory;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.SentineledConnectionProvider;\n\n/**\n * JedisSentineled is a client for Redis Sentinel deployments.\n *\n * @deprecated Use {@link RedisSentinelClient} instead. RedisSentinelClient provides the same functionality\n *             with a cleaner API. Use {@link RedisSentinelClient#builder()} to configure the client\n *             with sentinel settings, master configuration, and connection pooling options.\n */\n@Deprecated\npublic class JedisSentineled extends UnifiedJedis {\n\n  public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    super(new SentineledConnectionProvider(masterName, masterClientConfig, sentinels, sentinelClientConfig),\n        masterClientConfig.getRedisProtocol());\n  }\n\n  @Experimental\n  public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, CacheConfig cacheConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    this(masterName, masterClientConfig, CacheFactory.getCache(cacheConfig),\n        sentinels, sentinelClientConfig);\n  }\n\n  @Experimental\n  public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, Cache clientSideCache,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache,\n        sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig,\n      final GenericObjectPoolConfig<Connection> poolConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    super(new SentineledConnectionProvider(masterName, masterClientConfig, poolConfig, sentinels, sentinelClientConfig),\n        masterClientConfig.getRedisProtocol());\n  }\n\n  @Experimental\n  public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, Cache clientSideCache,\n      final GenericObjectPoolConfig<Connection> poolConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, poolConfig,\n        sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache);\n  }\n\n  public JedisSentineled(SentineledConnectionProvider sentineledConnectionProvider) {\n    super(sentineledConnectionProvider);\n  }\n\n  private JedisSentineled(CommandExecutor commandExecutor, ConnectionProvider connectionProvider, CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n  }\n\n  /**\n   * Fluent builder for {@link JedisSentineled} (Redis Sentinel).\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  static public class Builder extends SentinelClientBuilder<JedisSentineled> {\n\n    @Override\n    protected JedisSentineled createClient() {\n      return new JedisSentineled(commandExecutor, connectionProvider, commandObjects, clientConfig.getRedisProtocol(),\n          cache);\n    }\n  }\n\n  /**\n   * Create a new builder for configuring JedisSentineled instances.\n   *\n   * @return a new {@link JedisSentineled.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public HostAndPort getCurrentMaster() {\n    return ((SentineledConnectionProvider) provider).getCurrentMaster();\n  }\n\n  @Override\n  public Pipeline pipelined() {\n    return (Pipeline) super.pipelined();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisShardedPubSub.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic abstract class JedisShardedPubSub extends JedisShardedPubSubBase<String> {\n\n  @Override\n  protected final String encode(byte[] raw) {\n    return SafeEncoder.encode(raw);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisShardedPubSubBase.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.ResponseKeyword.*;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic abstract class JedisShardedPubSubBase<T> {\n\n  private int subscribedChannels = 0;\n  private final JedisSafeAuthenticator authenticator = new JedisSafeAuthenticator();\n\n  public void onSMessage(T channel, T message) {\n  }\n\n  public void onSSubscribe(T channel, int subscribedChannels) {\n  }\n\n  public void onSUnsubscribe(T channel, int subscribedChannels) {\n  }\n\n  private void sendAndFlushCommand(Command command, T... args) {\n    authenticator.sendAndFlushCommand(command, args);\n  }\n\n  public final void sunsubscribe() {\n    sendAndFlushCommand(Command.SUNSUBSCRIBE);\n  }\n\n  public final void sunsubscribe(T... channels) {\n    sendAndFlushCommand(Command.SUNSUBSCRIBE, channels);\n  }\n\n  public final void ssubscribe(T... channels) {\n    checkConnectionSuitableForPubSub();\n    sendAndFlushCommand(Command.SSUBSCRIBE, channels);\n  }\n\n  private void checkConnectionSuitableForPubSub() {\n    if (authenticator.client.protocol != RedisProtocol.RESP3\n        && authenticator.client.isTokenBasedAuthenticationEnabled()) {\n      throw new JedisException(\n          \"Blocking pub/sub operations are not supported on token-based authentication enabled connections with RESP2 protocol!\");\n    }\n  }\n\n  public final boolean isSubscribed() {\n    return subscribedChannels > 0;\n  }\n\n  public final int getSubscribedChannels() {\n    return subscribedChannels;\n  }\n\n  public final void proceed(Connection client, T... channels) {\n    authenticator.registerForAuthentication(client);\n    authenticator.client.setTimeoutInfinite();\n    try {\n      ssubscribe(channels);\n      process();\n    } finally {\n      authenticator.client.rollbackTimeout();\n    }\n  }\n\n  protected abstract T encode(byte[] raw);\n\n  private void process() {\n\n    do {\n      Object reply = authenticator.client.getUnflushedObject();\n\n      if (reply instanceof List) {\n        List<Object> listReply = (List<Object>) reply;\n        final Object firstObj = listReply.get(0);\n        if (!(firstObj instanceof byte[])) {\n          throw new JedisException(\"Unknown message type: \" + firstObj);\n        }\n        final byte[] resp = (byte[]) firstObj;\n        if (Arrays.equals(SSUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          onSSubscribe(enchannel, subscribedChannels);\n        } else if (Arrays.equals(SUNSUBSCRIBE.getRaw(), resp)) {\n          subscribedChannels = ((Long) listReply.get(2)).intValue();\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          onSUnsubscribe(enchannel, subscribedChannels);\n        } else if (Arrays.equals(SMESSAGE.getRaw(), resp)) {\n          final byte[] bchannel = (byte[]) listReply.get(1);\n          final byte[] bmesg = (byte[]) listReply.get(2);\n          final T enchannel = (bchannel == null) ? null : encode(bchannel);\n          final T enmesg = (bmesg == null) ? null : encode(bmesg);\n          onSMessage(enchannel, enmesg);\n        } else {\n          throw new JedisException(\"Unknown message type: \" + firstObj);\n        }\n      } else if (reply instanceof byte[]) {\n        Consumer<Object> resultHandler = authenticator.resultHandler.poll();\n        if (resultHandler == null) {\n          throw new JedisException(\"Unexpected message : \" + SafeEncoder.encode((byte[]) reply));\n        }\n        resultHandler.accept(reply);\n      } else {\n        throw new JedisException(\"Unknown message type: \" + reply);\n      }\n    } while (!Thread.currentThread().isInterrupted() && isSubscribed());\n\n//    /* Invalidate instance since this thread is no longer listening */\n//    this.client = null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/JedisSocketFactory.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.Socket;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\n/**\n * JedisSocketFactory: responsible for creating socket connections\n * from the within the Jedis client, the default socket factory will\n * create TCP sockets with the recommended configuration.\n * <p>\n * You can use a custom JedisSocketFactory for many use cases, such as:\n * - a custom address resolver\n * - a unix domain socket\n * - a custom configuration for you TCP sockets\n */\npublic interface JedisSocketFactory {\n\n  Socket createSocket() throws JedisConnectionException;\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Module.java",
    "content": "package redis.clients.jedis;\n\n// TODO: 'resps' package\n// TODO: remove\npublic class Module {\n\n  private final String name;\n  private final int version;\n\n  public Module(String name, int version) {\n    this.name = name;\n    this.version = version;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public int getVersion() {\n    return version;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == null) return false;\n    if (o == this) return true;\n    if (!(o instanceof Module)) return false;\n\n    Module module = (Module) o;\n\n    if (version != module.version) return false;\n    return !(name != null ? !name.equals(module.name) : module.name != null);\n\n  }\n\n  @Override\n  public int hashCode() {\n    int result = name != null ? name.hashCode() : 0;\n    result = 31 * result + version;\n    return result;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/MultiDbClient.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.builders.MultiDbClientBuilder;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.exceptions.JedisValidationException;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.mcf.MultiDbCommandExecutor;\nimport redis.clients.jedis.mcf.MultiDbPipeline;\nimport redis.clients.jedis.mcf.MultiDbTransaction;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\n\nimport java.util.Set;\n\n/**\n * MultiDbClient provides high-availability Redis connectivity with automatic failover and failback\n * capabilities across multiple weighted endpoints.\n * <p>\n * This client extends UnifiedJedis to support resilient operations with:\n * <ul>\n * <li><strong>Multi-Endpoint Support:</strong> Configure multiple Redis endpoints with individual\n * weights</li>\n * <li><strong>Automatic Failover:</strong> Seamless switching to backup endpoints when primary\n * becomes unavailable</li>\n * <li><strong>Circuit Breaker Pattern:</strong> Built-in circuit breaker to prevent cascading\n * failures</li>\n * <li><strong>Weight-Based Selection:</strong> Intelligent endpoint selection based on configured\n * weights</li>\n * <li><strong>Health Monitoring:</strong> Continuous health checks with automatic failback to\n * recovered endpoints</li>\n * <li><strong>Retry Logic:</strong> Configurable retry mechanisms with exponential backoff</li>\n * </ul>\n * <p>\n * <strong>Usage Example:</strong>\n * </p>\n * \n * <pre>\n * // Create multi-db client with multiple endpoints\n * HostAndPort primary = new HostAndPort(\"localhost\", 29379);\n * HostAndPort secondary = new HostAndPort(\"localhost\", 29380);\n *\n *\n * MultiDbClient client = MultiDbClient.builder()\n *                 .multiDbConfig(\n *                         MultiDbConfig.builder()\n *                                 .database(\n *                                         DatabaseConfig.builder(\n *                                                         primary,\n *                                                         DefaultJedisClientConfig.builder().build())\n *                                                 .weight(100.0f)\n *                                                 .build())\n *                                 .database(DatabaseConfig.builder(\n *                                                 secondary,\n *                                                 DefaultJedisClientConfig.builder().build())\n *                                         .weight(50.0f).build())\n *                                 .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n *                                         .failureRateThreshold(50.0f)\n *                                         .build())\n *                                 .commandRetry(MultiDbConfig.RetryConfig.builder()\n *                                         .maxAttempts(3)\n *                                         .build())\n *                                 .build()\n *                 )\n *                 .databaseSwitchListener(event -&gt;\n *                    System.out.println(\"Switched to: \" + event.getEndpoint()))\n *                 .build();\n * \n * // Use like any other Jedis client\n * client.set(\"key\", \"value\");\n * String value = client.get(\"key\");\n * \n * // Automatic failover happens transparently\n * client.close();\n * </pre>\n * <p>\n * The client automatically handles endpoint failures and recoveries, providing transparent high\n * availability for Redis operations. All standard Jedis operations are supported with the added\n * resilience features.\n * </p>\n * @author Ivo Gaydazhiev\n * @since 7.0.0\n * @see MultiDbConnectionProvider\n * @see MultiDbCommandExecutor\n * @see MultiDbConfig\n */\n@Experimental\npublic class MultiDbClient extends UnifiedJedis {\n\n  /**\n   * Creates a MultiDbClient with custom components.\n   * <p>\n   * This constructor allows full customization of the client components and is primarily used by\n   * the builder pattern for advanced configurations. For most use cases, prefer using\n   * {@link #builder()} to create instances.\n   * </p>\n   * @param commandExecutor the command executor (typically MultiDbCommandExecutor)\n   * @param connectionProvider the connection provider (typically MultiDbConnectionProvider)\n   * @param commandObjects the command objects\n   * @param redisProtocol the Redis protocol version\n   * @param cache the client-side cache (may be null)\n   */\n  MultiDbClient(CommandExecutor commandExecutor, ConnectionProvider connectionProvider,\n      CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n  }\n\n  /**\n   * Returns the underlying MultiDbConnectionProvider.\n   * <p>\n   * This provides access to multi-database specific operations like manual failover, health status\n   * monitoring, and database switch event handling.\n   * </p>\n   * @return the multi-db connection provider\n   * @throws ClassCastException if the provider is not a MultiDbConnectionProvider\n   */\n  private MultiDbConnectionProvider getMultiDbConnectionProvider() {\n    return (MultiDbConnectionProvider) this.provider;\n  }\n\n  /**\n   * Manually switches to the specified endpoint.\n   * <p>\n   * This method allows manual failover to a specific endpoint, bypassing the automatic weight-based\n   * selection. The switch will only succeed if the target endpoint is healthy.\n   * </p>\n   * @param endpoint the endpoint to switch to\n   */\n  public void setActiveDatabase(Endpoint endpoint) {\n    getMultiDbConnectionProvider().setActiveDatabase(endpoint);\n  }\n\n  /**\n   * Adds a pre-configured database configuration.\n   * <p>\n   * This method allows adding a fully configured DatabaseConfig instance, providing maximum\n   * flexibility for advanced configurations including custom health check strategies, connection\n   * pool settings, etc.\n   * </p>\n   * @param databaseConfig the pre-configured database configuration\n   */\n  public void addDatabase(DatabaseConfig databaseConfig) {\n    getMultiDbConnectionProvider().add(databaseConfig);\n  }\n\n  /**\n   * Dynamically adds a new database endpoint to the multi-database client.\n   * <p>\n   * This allows adding new database endpoints at runtime without recreating the client. The new\n   * endpoint will be available for failover operations immediately after being added and passing\n   * health checks (if configured).\n   * </p>\n   * @param endpoint the Redis server endpoint\n   * @param weight the weight for this endpoint (higher values = higher priority)\n   * @param clientConfig the client configuration for this endpoint\n   * @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint already exists\n   */\n  public void addDatabase(Endpoint endpoint, float weight, JedisClientConfig clientConfig) {\n    DatabaseConfig databaseConfig = DatabaseConfig.builder(endpoint, clientConfig).weight(weight)\n        .build();\n\n    getMultiDbConnectionProvider().add(databaseConfig);\n  }\n\n  /**\n   * Returns the set of all configured database endpoints.\n   * <p>\n   * This method provides a view of all database endpoints currently configured in the\n   * multi-database client. These are the endpoints that can be used for failover operations.\n   * </p>\n   * @return the set of all configured database endpoints\n   */\n  public Set<Endpoint> getDatabaseEndpoints() {\n    return getMultiDbConnectionProvider().getEndpoints();\n  }\n\n  /**\n   * Returns the health status of the specified database.\n   * <p>\n   * This method provides the current health status of a specific endpoint.\n   * </p>\n   * @param endpoint the endpoint to check\n   * @return the health status of the endpoint\n   */\n  public boolean isHealthy(Endpoint endpoint) {\n    return getMultiDbConnectionProvider().isHealthy(endpoint);\n  }\n\n  /**\n   * Returns the weight of the specified database.\n   * <p>\n   * This method provides the current weight of a specific endpoint.\n   * </p>\n   * @param endpoint the endpoint to check\n   * @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint doesn't exist\n   * @return the weight of the endpoint\n   */\n  public float getWeight(Endpoint endpoint) {\n    Database db = getMultiDbConnectionProvider().getDatabase(endpoint);\n    if (db == null) {\n      throw new JedisValidationException(\"Endpoint \" + endpoint + \" does not exist.\");\n    }\n    return db.getWeight();\n  }\n\n  /**\n   * Sets the weight of the specified database.\n   * <p>\n   * This method allows changing the weight of a specific endpoint at runtime. The weight determines\n   * the priority of the endpoint during failover operations.\n   * </p>\n   * @param endpoint the endpoint to change\n   * @param weight the new weight for the endpoint\n   * @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint doesn't exist\n   */\n  public void setWeight(Endpoint endpoint, float weight) {\n    Database db = getMultiDbConnectionProvider().getDatabase(endpoint);\n    if (db == null) {\n      throw new JedisValidationException(\"Endpoint \" + endpoint + \" does not exist.\");\n    }\n    db.setWeight(weight);\n  }\n\n  /**\n   * Dynamically removes a database endpoint from the multi-database client.\n   * <p>\n   * This allows removing database endpoints at runtime. If the removed endpoint is currently\n   * active, the client will automatically failover to the next available healthy endpoint based on\n   * weight priority.\n   * </p>\n   * @param endpoint the endpoint to remove\n   * @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint doesn't exist\n   * @throws redis.clients.jedis.exceptions.JedisException if removing the endpoint would leave no\n   *           healthy databases available\n   */\n  public void removeDatabase(Endpoint endpoint) {\n    getMultiDbConnectionProvider().remove(endpoint);\n  }\n\n  /**\n   * Forces the client to switch to a specific database for a duration.\n   * <p>\n   * This method forces the client to use the specified database endpoint and puts all other\n   * endpoints in a grace period, preventing automatic failover for the specified duration. This is\n   * useful for maintenance scenarios or testing specific database endpoints.\n   * </p>\n   * @param endpoint the endpoint to force as active\n   * @param forcedActiveDurationMs the duration in milliseconds to keep this endpoint forced\n   * @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint is not healthy\n   *           or doesn't exist\n   */\n  public void forceActiveDatabase(Endpoint endpoint, long forcedActiveDurationMs) {\n    getMultiDbConnectionProvider().forceActiveDatabase(endpoint, forcedActiveDurationMs);\n  }\n\n  /**\n   * Creates a new pipeline for batch operations with multi-db support.\n   * <p>\n   * The returned pipeline supports the same resilience features as the main client, including\n   * automatic failover during batch execution.\n   * </p>\n   * @return a new MultiDbPipeline instance\n   */\n  @Override\n  public MultiDbPipeline pipelined() {\n    return new MultiDbPipeline(getMultiDbConnectionProvider(), commandObjects);\n  }\n\n  /**\n   * Creates a new transaction with multi-database support.\n   * <p>\n   * The returned transaction supports the same resilience features as the main client, including\n   * automatic failover during transaction execution.\n   * </p>\n   * @return a new MultiDbTransaction instance\n   */\n  @Override\n  public MultiDbTransaction multi() {\n    return new MultiDbTransaction((MultiDbConnectionProvider) provider, true, commandObjects);\n  }\n\n  /**\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @return transaction object\n   */\n  @Override\n  public MultiDbTransaction transaction(boolean doMulti) {\n    if (provider == null) {\n      throw new IllegalStateException(\n          \"It is not allowed to create Transaction from this \" + getClass());\n    }\n\n    return new MultiDbTransaction(getMultiDbConnectionProvider(), doMulti, commandObjects);\n  }\n\n  /**\n   * Returns the currently active database endpoint.\n   * <p>\n   * The active endpoint is the one currently being used for all operations. It can change at any\n   * time due to health checks, failover, failback, or manual switching.\n   * </p>\n   * @return the active database endpoint\n   */\n  public Endpoint getActiveDatabaseEndpoint() {\n    return getMultiDbConnectionProvider().getDatabase().getEndpoint();\n  }\n\n  /**\n   * Fluent builder for {@link MultiDbClient}.\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  public static class Builder extends MultiDbClientBuilder<MultiDbClient> {\n\n    @Override\n    protected MultiDbClient createClient() {\n      return new MultiDbClient(commandExecutor, connectionProvider, commandObjects,\n          clientConfig.getRedisProtocol(), cache);\n    }\n  }\n\n  /**\n   * Create a new builder for configuring MultiDbClient instances.\n   * @return a new {@link MultiDbClient.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/MultiDbConfig.java",
    "content": "package redis.clients.jedis;\n\nimport io.github.resilience4j.circuitbreaker.CallNotPermittedException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisValidationException;\nimport redis.clients.jedis.mcf.ConnectionFailoverException;\nimport redis.clients.jedis.mcf.PingStrategy;\nimport redis.clients.jedis.util.JedisAsserts;\nimport redis.clients.jedis.mcf.HealthCheckStrategy;\nimport redis.clients.jedis.mcf.InitializationPolicy;\n\n/**\n * Configuration class for multi-database Redis deployments with automatic failover and failback\n * capabilities.\n * <p>\n * This configuration enables seamless failover between multiple Redis databases endpoints by\n * providing comprehensive settings for retry logic, circuit breaker behavior, health checks, and\n * failback mechanisms. It is designed to work with\n * {@link redis.clients.jedis.mcf.MultiDbConnectionProvider} to provide high availability and\n * disaster recovery capabilities.\n * </p>\n * <p>\n * <strong>Key Features:</strong>\n * </p>\n * <ul>\n * <li><strong>Multi-Database Support:</strong> Configure multiple Redis endpoints with individual\n * weights and health checks</li>\n * <li><strong>Circuit Breaker Pattern:</strong> Automatic failure detection and circuit opening\n * based on configurable thresholds</li>\n * <li><strong>Retry Logic:</strong> Configurable retry attempts with exponential backoff for\n * transient failures</li>\n * <li><strong>Health Check Integration:</strong> Pluggable health check strategies for proactive\n * monitoring</li>\n * <li><strong>Automatic Failback:</strong> Intelligent failback to higher-priority databases when\n * they recover</li>\n * <li><strong>Weight-Based Routing:</strong> Priority-based database selection using configurable\n * weights</li>\n * </ul>\n * <p>\n * <strong>Usage Example:</strong>\n * </p>\n *\n * <pre>\n * {\n *   &#64;code\n *   // Configure individual databases\n *   DatabaseConfig primary = DatabaseConfig.builder(primaryEndpoint, clientConfig).weight(1.0f)\n *       .build();\n *\n *   DatabaseConfig secondary = DatabaseConfig.builder(secondaryEndpoint, clientConfig).weight(0.5f)\n *       .healthCheckEnabled(true).build();\n *\n *   // Build multi-database configuration\n *   MultiDbConfig config = MultiDbConfig.builder(primary, secondary)\n *       .failureDetector(CircuitBreakerConfig.builder().failureRateThreshold(10.0f).build())\n *       .commandRetry(RetryConfig.builder().maxAttempts(3).build()).failbackSupported(true)\n *       .gracePeriod(10000).build();\n *\n *   // Use with connection provider\n *   MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config);\n * }\n * </pre>\n * <p>\n * The configuration leverages <a href=\"https://resilience4j.readme.io/docs\">Resilience4j</a> for\n * circuit breaker and retry implementations, providing battle-tested fault tolerance patterns.\n * </p>\n * @see redis.clients.jedis.mcf.MultiDbConnectionProvider\n * @see redis.clients.jedis.mcf.HealthCheckStrategy\n * @see redis.clients.jedis.mcf.PingStrategy\n * @see redis.clients.jedis.mcf.LagAwareStrategy\n * @since 7.0\n */\n// TODO: move\n@Experimental\npublic final class MultiDbConfig {\n\n  /**\n   * Functional interface for creating {@link HealthCheckStrategy} instances for specific Redis\n   * endpoints.\n   * <p>\n   * This supplier pattern allows for flexible health check strategy creation, enabling different\n   * strategies for different endpoints or dynamic configuration based on endpoint characteristics.\n   * </p>\n   * <p>\n   * <strong>Common Implementations:</strong>\n   * </p>\n   * <ul>\n   * <li>{@link redis.clients.jedis.mcf.PingStrategy#DEFAULT} - Uses Redis PING command for health\n   * checks</li>\n   * <li>Custom implementations for specific monitoring requirements</li>\n   * <li>Redis Enterprise implementations using REST API monitoring</li>\n   * </ul>\n   * @see redis.clients.jedis.mcf.HealthCheckStrategy\n   * @see redis.clients.jedis.mcf.PingStrategy\n   * @see redis.clients.jedis.mcf.LagAwareStrategy\n   */\n  public static interface StrategySupplier {\n    /**\n     * Creates a {@link HealthCheckStrategy} instance for the specified Redis endpoint.\n     * @param hostAndPort the Redis endpoint (host and port) to create a health check strategy for\n     * @param jedisClientConfig the Jedis client configuration containing connection settings,\n     *          authentication, and other client parameters. May be null for implementations that\n     *          don't require client configuration\n     * @return a configured {@link HealthCheckStrategy} instance for monitoring the specified\n     *         endpoint\n     * @throws IllegalArgumentException if the hostAndPort is null or invalid\n     */\n    HealthCheckStrategy get(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig);\n  }\n\n  /**\n   * Configuration for command retry behavior.\n   * <p>\n   * This class encapsulates all retry-related settings including maximum attempts, wait duration,\n   * exponential backoff, and exception handling. It provides a clean separation of retry concerns\n   * from other configuration aspects.\n   * </p>\n   * @since 7.0\n   */\n  public static final class RetryConfig {\n\n    private final int maxAttempts;\n    private final Duration waitDuration;\n    private final int exponentialBackoffMultiplier;\n    private final List<Class> includedExceptionList;\n    private final List<Class> ignoreExceptionList;\n\n    private RetryConfig(Builder builder) {\n      this.maxAttempts = builder.maxAttempts;\n      this.waitDuration = Duration.ofMillis(builder.waitDuration);\n      this.exponentialBackoffMultiplier = builder.exponentialBackoffMultiplier;\n      this.includedExceptionList = builder.includedExceptionList;\n      this.ignoreExceptionList = builder.ignoreExceptionList;\n    }\n\n    /**\n     * Returns the maximum number of retry attempts including the initial call.\n     * @return maximum retry attempts\n     */\n    public int getMaxAttempts() {\n      return maxAttempts;\n    }\n\n    /**\n     * Returns the base wait duration between retry attempts.\n     * @return wait duration between retries\n     */\n    public Duration getWaitDuration() {\n      return waitDuration;\n    }\n\n    /**\n     * Returns the exponential backoff multiplier for retry wait duration.\n     * @return exponential backoff multiplier\n     */\n    public int getExponentialBackoffMultiplier() {\n      return exponentialBackoffMultiplier;\n    }\n\n    /**\n     * Returns the list of exception classes that trigger retry attempts.\n     * @return list of exception classes that are retried, never null\n     */\n    public List<Class> getIncludedExceptionList() {\n      return includedExceptionList;\n    }\n\n    /**\n     * Returns the list of exception classes that are ignored for retry purposes.\n     * @return list of exception classes to ignore for retries, may be null\n     */\n    public List<Class> getIgnoreExceptionList() {\n      return ignoreExceptionList;\n    }\n\n    /**\n     * Creates a new Builder instance for configuring RetryConfig.\n     * @return new Builder instance with default values\n     */\n    public static Builder builder() {\n      return new Builder();\n    }\n\n    /**\n     * Builder for {@link RetryConfig}.\n     */\n    public static final class Builder {\n\n      private int maxAttempts = RETRY_MAX_ATTEMPTS_DEFAULT;\n      private int waitDuration = RETRY_WAIT_DURATION_DEFAULT;\n      private int exponentialBackoffMultiplier = RETRY_WAIT_DURATION_EXPONENTIAL_BACKOFF_MULTIPLIER_DEFAULT;\n      private List<Class> includedExceptionList = RETRY_INCLUDED_EXCEPTIONS_DEFAULT;\n      private List<Class> ignoreExceptionList = null;\n\n      /**\n       * Sets the maximum number of retry attempts including the initial call.\n       * @param maxAttempts maximum number of attempts (must be &gt;= 1)\n       * @return this builder instance for method chaining\n       */\n      public Builder maxAttempts(int maxAttempts) {\n        this.maxAttempts = maxAttempts;\n        return this;\n      }\n\n      /**\n       * Sets the base wait duration between retry attempts in milliseconds.\n       * @param waitDuration wait duration in milliseconds (must be &gt;= 0)\n       * @return this builder instance for method chaining\n       */\n      public Builder waitDuration(int waitDuration) {\n        this.waitDuration = waitDuration;\n        return this;\n      }\n\n      /**\n       * Sets the exponential backoff multiplier for retry wait duration.\n       * @param exponentialBackoffMultiplier exponential backoff multiplier (must be &gt;= 1)\n       * @return this builder instance for method chaining\n       */\n      public Builder exponentialBackoffMultiplier(int exponentialBackoffMultiplier) {\n        this.exponentialBackoffMultiplier = exponentialBackoffMultiplier;\n        return this;\n      }\n\n      /**\n       * Sets the list of exception classes that trigger retry attempts.\n       * @param includedExceptionList list of exception classes that should be retried\n       * @return this builder instance for method chaining\n       */\n      public Builder includedExceptionList(List<Class> includedExceptionList) {\n        this.includedExceptionList = includedExceptionList;\n        return this;\n      }\n\n      /**\n       * Sets the list of exception classes that are ignored for retry purposes.\n       * @param ignoreExceptionList list of exception classes to ignore for retries\n       * @return this builder instance for method chaining\n       */\n      public Builder ignoreExceptionList(List<Class> ignoreExceptionList) {\n        this.ignoreExceptionList = ignoreExceptionList;\n        return this;\n      }\n\n      /**\n       * Builds and returns a new RetryConfig instance.\n       * @return new RetryConfig instance with configured settings\n       */\n      public RetryConfig build() {\n        return new RetryConfig(this);\n      }\n    }\n  }\n\n  /**\n   * Configuration for circuit breaker failure detection.\n   * <p>\n   * This class encapsulates all circuit breaker-related settings including failure rate threshold,\n   * sliding window size, minimum failures, and exception handling.\n   * </p>\n   * @since 7.0\n   */\n  public static final class CircuitBreakerConfig {\n\n    private final float failureRateThreshold;\n    private final int slidingWindowSize;\n    private final int minNumOfFailures;\n    private final List<Class> includedExceptionList;\n    private final List<Class> ignoreExceptionList;\n\n    private CircuitBreakerConfig(Builder builder) {\n      this.failureRateThreshold = builder.failureRateThreshold;\n      this.slidingWindowSize = builder.slidingWindowSize;\n      this.minNumOfFailures = builder.minNumOfFailures;\n      this.includedExceptionList = builder.includedExceptionList;\n      this.ignoreExceptionList = builder.ignoreExceptionList;\n    }\n\n    /**\n     * Returns the failure rate threshold percentage for circuit breaker activation.\n     * <p>\n     * 0.0f means failure rate is ignored, and only minimum number of failures is considered.\n     * </p>\n     * <p>\n     * When the failure rate exceeds both this threshold and the minimum number of failures, the\n     * circuit breaker transitions to the OPEN state and starts short-circuiting calls, immediately\n     * failing them without attempting to reach the Redis database. This prevents cascading failures\n     * and allows the system to fail over to the next available database.\n     * </p>\n     * <p>\n     * <strong>Range:</strong> 0.0 to 100.0 (percentage)\n     * </p>\n     * @return failure rate threshold as a percentage (0.0 to 100.0)\n     * @see #getMinNumOfFailures()\n     */\n    public float getFailureRateThreshold() {\n      return failureRateThreshold;\n    }\n\n    /**\n     * Returns the size of the sliding window used to record call outcomes when the circuit breaker\n     * is CLOSED.\n     * <p>\n     * <strong>Default:</strong> {@value #CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT}\n     * </p>\n     * @return sliding window size (calls or seconds depending on window type)\n     */\n    public int getSlidingWindowSize() {\n      return slidingWindowSize;\n    }\n\n    /**\n     * Returns the minimum number of failures before circuit breaker is tripped.\n     * <p>\n     * 0 means minimum number of failures is ignored, and only failure rate is considered.\n     * </p>\n     * <p>\n     * When the number of failures exceeds both this threshold and the failure rate threshold, the\n     * circuit breaker will trip and prevent further requests from being sent to the database until\n     * it has recovered.\n     * </p>\n     * @return minimum number of failures before circuit breaker is tripped\n     * @see #getFailureRateThreshold()\n     */\n    public int getMinNumOfFailures() {\n      return minNumOfFailures;\n    }\n\n    /**\n     * Returns the list of exception classes that are recorded as circuit breaker failures and\n     * increase the failure rate.\n     * <p>\n     * Any exception that matches or inherits from the classes in this list counts as a failure for\n     * circuit breaker calculations, unless explicitly ignored via\n     * {@link #getIgnoreExceptionList()}. If you specify this list, all other exceptions count as\n     * successes unless they are explicitly ignored.\n     * </p>\n     * <p>\n     * <strong>Default:</strong> {@link JedisConnectionException}\n     * </p>\n     * @return list of exception classes that count as failures, never null\n     * @see #getIgnoreExceptionList()\n     */\n    public List<Class> getIncludedExceptionList() {\n      return includedExceptionList;\n    }\n\n    /**\n     * Returns the list of exception classes that are ignored by the circuit breaker and neither\n     * count as failures nor successes.\n     * <p>\n     * Any exception that matches or inherits from the classes in this list will not affect circuit\n     * breaker failure rate calculations, even if the exception is included in\n     * {@link #getIncludedExceptionList()}.\n     * </p>\n     * <p>\n     * <strong>Default:</strong> null (no exceptions ignored)\n     * </p>\n     * @return list of exception classes to ignore for circuit breaker calculations, may be null\n     * @see #getIncludedExceptionList()\n     */\n    public List<Class> getIgnoreExceptionList() {\n      return ignoreExceptionList;\n    }\n\n    /**\n     * Creates a new Builder instance for configuring CircuitBreakerConfig.\n     * @return new Builder instance with default values\n     */\n    public static Builder builder() {\n      return new Builder();\n    }\n\n    /**\n     * Builder for {@link CircuitBreakerConfig}.\n     */\n    public static final class Builder {\n\n      private float failureRateThreshold = CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD_DEFAULT;\n      private int slidingWindowSize = CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT;\n      private int minNumOfFailures = CIRCUITBREAKER_THRESHOLD_MIN_NUM_OF_FAILURES_DEFAULT;\n      private List<Class> includedExceptionList = CIRCUIT_BREAKER_INCLUDED_EXCEPTIONS_DEFAULT;\n      private List<Class> ignoreExceptionList = null;\n\n      /**\n       * Sets the failure rate threshold percentage that triggers circuit breaker activation.\n       * @param failureRateThreshold failure rate threshold as percentage (0.0 to 100.0)\n       * @return this builder instance for method chaining\n       */\n      public Builder failureRateThreshold(float failureRateThreshold) {\n        this.failureRateThreshold = failureRateThreshold;\n        return this;\n      }\n\n      /**\n       * Sets the size of the sliding window for circuit breaker calculations.\n       * @param slidingWindowSize sliding window size\n       * @return this builder instance for method chaining\n       */\n      public Builder slidingWindowSize(int slidingWindowSize) {\n        this.slidingWindowSize = slidingWindowSize;\n        return this;\n      }\n\n      /**\n       * Sets the minimum number of failures before circuit breaker is tripped.\n       * @param minNumOfFailures minimum number of failures\n       * @return this builder instance for method chaining\n       */\n      public Builder minNumOfFailures(int minNumOfFailures) {\n        this.minNumOfFailures = minNumOfFailures;\n        return this;\n      }\n\n      /**\n       * Sets the list of exception classes that are recorded as circuit breaker failures.\n       * @param includedExceptionList list of exception classes that count as failures\n       * @return this builder instance for method chaining\n       */\n      public Builder includedExceptionList(List<Class> includedExceptionList) {\n        this.includedExceptionList = includedExceptionList;\n        return this;\n      }\n\n      /**\n       * Sets the list of exception classes that are ignored by the circuit breaker.\n       * @param ignoreExceptionList list of exception classes to ignore\n       * @return this builder instance for method chaining\n       */\n      public Builder ignoreExceptionList(List<Class> ignoreExceptionList) {\n        this.ignoreExceptionList = ignoreExceptionList;\n        return this;\n      }\n\n      /**\n       * Builds and returns a new CircuitBreakerConfig instance.\n       * @return new CircuitBreakerConfig instance with configured settings\n       */\n      public CircuitBreakerConfig build() {\n        return new CircuitBreakerConfig(this);\n      }\n    }\n  }\n\n  // ============ Default Configuration Constants ============\n\n  /** Default maximum number of retry attempts including the initial call. */\n  private static final int RETRY_MAX_ATTEMPTS_DEFAULT = 3;\n\n  /** Default wait duration between retry attempts in milliseconds. */\n  private static final int RETRY_WAIT_DURATION_DEFAULT = 500;\n\n  /** Default exponential backoff multiplier for retry wait duration. */\n  private static final int RETRY_WAIT_DURATION_EXPONENTIAL_BACKOFF_MULTIPLIER_DEFAULT = 2;\n\n  /** Default list of exceptions that trigger retry attempts. */\n  private static final List<Class> RETRY_INCLUDED_EXCEPTIONS_DEFAULT = Arrays\n      .asList(JedisConnectionException.class);\n\n  /** Default failure rate threshold percentage for circuit breaker activation. */\n  private static final float CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD_DEFAULT = 10.0f;\n\n  /** Minimum number of failures before circuit breaker is tripped. */\n  private static final int CIRCUITBREAKER_THRESHOLD_MIN_NUM_OF_FAILURES_DEFAULT = 1000;\n\n  /** Default sliding window size for circuit breaker failure tracking. */\n  private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT = 2;\n\n  /** Default list of exceptions that are recorded as circuit breaker failures. */\n  private static final List<Class> CIRCUIT_BREAKER_INCLUDED_EXCEPTIONS_DEFAULT = Arrays\n      .asList(JedisConnectionException.class);\n\n  /** Default list of exceptions that trigger fallback to next available database. */\n  private static final List<Class<? extends Throwable>> FALLBACK_EXCEPTIONS_DEFAULT = Arrays\n      .asList(CallNotPermittedException.class, ConnectionFailoverException.class);\n\n  /** Default interval in milliseconds for checking if failed databases have recovered. */\n  private static final long FAILBACK_CHECK_INTERVAL_DEFAULT = 120000;\n\n  /**\n   * Default grace period in milliseconds to keep databases disabled after they become unhealthy.\n   */\n  private static final long GRACE_PERIOD_DEFAULT = 60000;\n\n  /** Default maximum number of failover attempts. */\n  private static final int MAX_NUM_FAILOVER_ATTEMPTS_DEFAULT = 10;\n\n  /** Default delay in milliseconds between failover attempts. */\n  private static final int DELAY_IN_BETWEEN_FAILOVER_ATTEMPTS_DEFAULT = 12000;\n\n  /** Array of database configurations defining the available Redis endpoints and their settings. */\n  private final DatabaseConfig[] databaseConfigs;\n\n  // ============ Retry Configuration ============\n  // Based on Resilience4j Retry: https://resilience4j.readme.io/docs/retry\n\n  /**\n   * Encapsulated retry configuration for command execution.\n   * <p>\n   * This provides a cleaner API for configuring retry behavior by grouping all retry-related\n   * settings into a single configuration object.\n   * </p>\n   * @see RetryConfig\n   */\n  private RetryConfig commandRetry;\n\n  // ============ Circuit Breaker Configuration ============\n\n  /**\n   * Encapsulated circuit breaker configuration for failure detection.\n   * <p>\n   * This provides a cleaner API for configuring circuit breaker behavior by grouping all circuit\n   * breaker-related settings into a single configuration object.\n   * </p>\n   * @see CircuitBreakerConfig\n   */\n  private CircuitBreakerConfig failureDetector;\n\n  /**\n   * List of exception classes that trigger fallback to the next available database.\n   * <p>\n   * When these exceptions occur, the system will attempt to failover to the next available database\n   * based on weight priority. This enables immediate failover for specific error conditions without\n   * waiting for circuit breaker thresholds.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> {@link CallNotPermittedException},\n   * {@link ConnectionFailoverException}\n   * </p>\n   * @see #getFallbackExceptionList()\n   */\n  private List<Class<? extends Throwable>> fallbackExceptionList;\n\n  // ============ Failover Configuration ============\n\n  /**\n   * Whether to retry failed commands during the failover process.\n   * <p>\n   * When enabled, commands that fail during failover will be retried according to the configured\n   * retry settings. When disabled, failed commands during failover will immediately return the\n   * failure to the caller.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> false\n   * </p>\n   * @see #isRetryOnFailover()\n   * @see #commandRetry\n   */\n  private boolean retryOnFailover;\n\n  /**\n   * Whether automatic failback to higher-priority databases is supported.\n   * <p>\n   * When enabled, the system will automatically monitor failed databases using health checks and\n   * failback to higher-priority (higher weight) databases when they recover. When disabled, manual\n   * intervention is required to failback.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> true\n   * </p>\n   * @see #isFailbackSupported()\n   * @see #failbackCheckInterval\n   * @see #gracePeriod\n   */\n  private boolean isFailbackSupported;\n\n  /**\n   * Interval in milliseconds between checks for failback opportunities to recovered databases.\n   * <p>\n   * This setting controls how frequently the system checks if a higher-priority database has\n   * recovered and is available for failback. Lower values provide faster failback but increase\n   * monitoring overhead.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> {@value #FAILBACK_CHECK_INTERVAL_DEFAULT} milliseconds (5 seconds)\n   * </p>\n   * @see #getFailbackCheckInterval()\n   * @see #isFailbackSupported\n   * @see #gracePeriod\n   */\n  private long failbackCheckInterval;\n\n  /**\n   * Grace period in milliseconds to keep databases disabled after they become unhealthy.\n   * <p>\n   * After a database is marked as unhealthy, it remains disabled for this grace period before being\n   * eligible for failback, even if health checks indicate recovery. This prevents rapid oscillation\n   * between databases during intermittent failures.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> {@value #GRACE_PERIOD_DEFAULT} milliseconds (10 seconds)\n   * </p>\n   * @see #getGracePeriod()\n   * @see #isFailbackSupported\n   * @see #failbackCheckInterval\n   */\n  private long gracePeriod;\n\n  /**\n   * Whether to forcefully terminate connections during failover for faster database switching.\n   * <p>\n   * When enabled, existing connections to the failed database are immediately closed during\n   * failover, potentially reducing failover time but may cause some in-flight operations to fail.\n   * When disabled, connections are closed gracefully.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> false\n   * </p>\n   * @see #isFastFailover()\n   */\n  private boolean fastFailover;\n\n  /**\n   * Maximum number of failover attempts.\n   * <p>\n   * This setting controls how many times the system will attempt to failover to a different\n   * database before giving up. For example, if set to 3, the system will make 1 initial attempt\n   * plus 2 failover attempts for a total of 3 attempts.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> {@value #MAX_NUM_FAILOVER_ATTEMPTS_DEFAULT}\n   * </p>\n   * @see #getMaxNumFailoverAttempts()\n   */\n  private int maxNumFailoverAttempts;\n\n  /**\n   * Delay in milliseconds between failover attempts.\n   * <p>\n   * This setting controls how long the system will wait before attempting to failover to a\n   * different database. For example, if set to 1000, the system will wait 1 second before\n   * attempting to failover to a different database.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> {@value #DELAY_IN_BETWEEN_FAILOVER_ATTEMPTS_DEFAULT} milliseconds\n   * </p>\n   * @see #getDelayInBetweenFailoverAttempts()\n   */\n  private int delayInBetweenFailoverAttempts;\n\n  /**\n   * Initialization policy that determines when the multi-database connection is ready to be\n   * returned based on the availability of individual database connections.\n   * <p>\n   * The policy is evaluated based on the completion status of database health checks, and the\n   * decision to continue waiting, succeed, or fail is based on the number of available, pending,\n   * and failed connections.\n   * </p>\n   * <p>\n   * <strong>Default:</strong> {@link InitializationPolicy.BuiltIn#MAJORITY_AVAILABLE}\n   * </p>\n   * @see InitializationPolicy\n   * @see #getInitializationPolicy()\n   */\n  private InitializationPolicy initializationPolicy;\n\n  /**\n   * Constructs a new MultiDbConfig with the specified database configurations.\n   * <p>\n   * This constructor validates that at least one database configuration is provided and that all\n   * configurations are non-null. Use the {@link Builder} class for more convenient configuration\n   * with default values.\n   * </p>\n   * @param databaseConfigs array of database configurations defining the available Redis endpoints\n   * @throws JedisValidationException if databaseConfigs is null or empty\n   * @throws IllegalArgumentException if any database configuration is null\n   * @see Builder#Builder(DatabaseConfig[])\n   */\n  public MultiDbConfig(DatabaseConfig[] databaseConfigs) {\n\n    if (databaseConfigs == null || databaseConfigs.length < 1) throw new JedisValidationException(\n        \"DatabaseClientConfigs are required for MultiDbConnectionProvider\");\n\n    for (DatabaseConfig databaseConfig : databaseConfigs) {\n      if (databaseConfig == null)\n        throw new IllegalArgumentException(\"DatabaseClientConfigs must not contain null elements\");\n    }\n    this.databaseConfigs = databaseConfigs;\n  }\n\n  /**\n   * Returns the array of database configurations defining available Redis endpoints.\n   * @return array of database configurations, never null or empty\n   */\n  public DatabaseConfig[] getDatabaseConfigs() {\n    return databaseConfigs;\n  }\n\n  /**\n   * Returns the encapsulated retry configuration for command execution.\n   * <p>\n   * This provides access to all retry-related settings through a single configuration object.\n   * </p>\n   * @return retry configuration, never null\n   * @see RetryConfig\n   */\n  public RetryConfig getCommandRetry() {\n    return commandRetry;\n  }\n\n  /**\n   * Returns the encapsulated circuit breaker configuration for failure detection.\n   * <p>\n   * This provides access to all circuit breaker-related settings through a single configuration\n   * object.\n   * </p>\n   * @return circuit breaker configuration, never null\n   * @see CircuitBreakerConfig\n   */\n  public CircuitBreakerConfig getFailureDetector() {\n    return failureDetector;\n  }\n\n  /**\n   * Returns the list of exception classes that trigger immediate fallback to next database.\n   * @return list of exception classes that trigger fallback, never null\n   * @see #fallbackExceptionList\n   */\n  public List<Class<? extends Throwable>> getFallbackExceptionList() {\n    return fallbackExceptionList;\n  }\n\n  /**\n   * Returns whether failed commands are retried during failover.\n   * @return true if commands are retried during failover, false otherwise\n   * @see #retryOnFailover\n   */\n  public boolean isRetryOnFailover() {\n    return retryOnFailover;\n  }\n\n  /**\n   * Returns whether automatic failback to higher-priority databases is supported.\n   * @return true if automatic failback is enabled, false if manual failback is required\n   * @see #isFailbackSupported\n   */\n  public boolean isFailbackSupported() {\n    return isFailbackSupported;\n  }\n\n  /**\n   * Returns the interval between checks for failback opportunities.\n   * @return failback check interval in milliseconds\n   * @see #failbackCheckInterval\n   */\n  public long getFailbackCheckInterval() {\n    return failbackCheckInterval;\n  }\n\n  /**\n   * Returns the grace period to keep databases disabled after they become unhealthy.\n   * @return grace period in milliseconds\n   * @see #gracePeriod\n   */\n  public long getGracePeriod() {\n    return gracePeriod;\n  }\n\n  /**\n   * Returns the maximum number of failover attempts.\n   * @return maximum number of failover attempts\n   * @see #maxNumFailoverAttempts\n   */\n  public int getMaxNumFailoverAttempts() {\n    return maxNumFailoverAttempts;\n\n  }\n\n  /**\n   * Returns the delay in milliseconds between failover attempts.\n   * @return delay in milliseconds between failover attempts\n   * @see #delayInBetweenFailoverAttempts\n   */\n  public int getDelayInBetweenFailoverAttempts() {\n    return delayInBetweenFailoverAttempts;\n  }\n\n  /**\n   * Returns whether connections are forcefully terminated during failover.\n   * @return true if fast failover is enabled, false for graceful failover\n   * @see #fastFailover\n   */\n  public boolean isFastFailover() {\n    return fastFailover;\n  }\n\n  /**\n   * Returns the initialization policy that determines when the multi-database connection is ready.\n   * <p>\n   * The policy is evaluated based on the completion status of database health checks, and the\n   * decision to continue waiting, succeed, or fail is based on the number of available, pending,\n   * and failed connections.\n   * </p>\n   * @return the initialization policy, defaults to\n   *         {@link InitializationPolicy.BuiltIn#MAJORITY_AVAILABLE}\n   * @see InitializationPolicy\n   * @see #initializationPolicy\n   */\n  public InitializationPolicy getInitializationPolicy() {\n    return initializationPolicy;\n  }\n\n  /**\n   * Creates a new Builder instance for configuring MultiDbConfig.\n   * <p>\n   * At least one database configuration must be added to the builder before calling build(). Use\n   * the endpoint() methods to add database configurations.\n   * </p>\n   * @return new Builder instance\n   * @throws JedisValidationException if databaseConfigs is null or empty\n   * @see Builder#Builder(DatabaseConfig[])\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Creates a new Builder instance for configuring MultiDbConfig.\n   * @param databaseConfigs array of database configurations defining available Redis endpoints\n   * @return new Builder instance\n   * @throws JedisValidationException if databaseConfigs is null or empty\n   * @see Builder#Builder(DatabaseConfig[])\n   */\n  public static Builder builder(DatabaseConfig[] databaseConfigs) {\n    return new Builder(databaseConfigs);\n  }\n\n  /**\n   * Creates a new Builder instance for configuring MultiDbConfig.\n   * @param databaseConfigs list of database configurations defining available Redis endpoints\n   * @return new Builder instance\n   * @throws JedisValidationException if databaseConfigs is null or empty\n   * @see Builder#Builder(List)\n   */\n  public static Builder builder(List<DatabaseConfig> databaseConfigs) {\n    return new Builder(databaseConfigs);\n  }\n\n  /**\n   * Configuration class for individual Redis database endpoints within a multi-database setup.\n   * <p>\n   * Each DatabaseConfig represents a single Redis endpoint that can participate in the\n   * multi-database failover system. It encapsulates the connection details, weight for\n   * priority-based selection, and health check configuration for that endpoint.\n   * </p>\n   * @see Builder\n   * @see StrategySupplier\n   * @see redis.clients.jedis.mcf.HealthCheckStrategy\n   */\n  public static class DatabaseConfig {\n\n    /** The Redis endpoint (host and port) for this database. */\n    private final Endpoint endpoint;\n\n    /** Jedis client configuration containing connection settings and authentication. */\n    private final JedisClientConfig jedisClientConfig;\n\n    /** Optional connection pool configuration for managing connections to this database. */\n    private GenericObjectPoolConfig<Connection> connectionPoolConfig;\n\n    /**\n     * Weight value for database selection priority. Higher weights indicate higher priority.\n     * Default value is 1.0f.\n     */\n    private float weight = 1.0f;\n\n    /** Health check enabled. Default: true */\n    private boolean healthCheckEnabled = true;\n\n    /**\n     * Strategy supplier for creating health check instances for this database. Default is\n     * PingStrategy.DEFAULT.\n     */\n    private StrategySupplier healthCheckStrategySupplier = PingStrategy.DEFAULT;\n\n    /**\n     * Constructs a DatabaseConfig with basic endpoint and client configuration.\n     * <p>\n     * This constructor creates a database configuration with default settings: weight of 1.0f and\n     * PingStrategy for health checks. Use the {@link Builder} for more advanced configuration\n     * options.\n     * </p>\n     * @param endpoint the Redis endpoint (host and port)\n     * @param clientConfig the Jedis client configuration\n     * @throws IllegalArgumentException if endpoint or clientConfig is null\n     */\n    public DatabaseConfig(Endpoint endpoint, JedisClientConfig clientConfig) {\n      this.endpoint = endpoint;\n      this.jedisClientConfig = clientConfig;\n    }\n\n    /**\n     * Private constructor used by the Builder to create configured instances.\n     * @param builder the builder containing configuration values\n     */\n    private DatabaseConfig(Builder builder) {\n      this.endpoint = builder.endpoint;\n      this.jedisClientConfig = builder.jedisClientConfig;\n      this.connectionPoolConfig = builder.connectionPoolConfig;\n      this.weight = builder.weight;\n      this.healthCheckEnabled = builder.healthCheckEnabled;\n      this.healthCheckStrategySupplier = builder.healthCheckStrategySupplier;\n    }\n\n    /**\n     * Returns the Redis endpoint (host and port) for this database.\n     * @return the host and port information\n     */\n    public Endpoint getEndpoint() {\n      return endpoint;\n    }\n\n    /**\n     * Creates a new Builder instance for configuring a DatabaseConfig.\n     * @param endpoint the Redis endpoint (host and port)\n     * @param clientConfig the Jedis client configuration\n     * @return new Builder instance\n     * @throws IllegalArgumentException if endpoint or clientConfig is null\n     */\n\n    public static Builder builder(Endpoint endpoint, JedisClientConfig clientConfig) {\n      return new Builder(endpoint, clientConfig);\n    }\n\n    /**\n     * Returns the Jedis client configuration for this database.\n     * @return the client configuration containing connection settings and authentication\n     */\n    public JedisClientConfig getJedisClientConfig() {\n      return jedisClientConfig;\n    }\n\n    /**\n     * Returns the connection pool configuration for this database.\n     * @return the connection pool configuration, may be null if not specified\n     */\n    public GenericObjectPoolConfig<Connection> getConnectionPoolConfig() {\n      return connectionPoolConfig;\n    }\n\n    /**\n     * Returns the weight value used for database selection priority.\n     * <p>\n     * Higher weight values indicate higher priority. During failover, databases are selected in\n     * descending order of weight (highest weight first).\n     * </p>\n     * @return the weight value, default is 1.0f\n     */\n    public float getWeight() {\n      return weight;\n    }\n\n    /**\n     * Returns the health check strategy supplier for this database.\n     * <p>\n     * The strategy supplier is used to create health check instances that monitor this database's\n     * availability. Returns null if health checks are disabled.\n     * </p>\n     * @return the health check strategy supplier, or null if health checks are disabled\n     * @see StrategySupplier\n     * @see redis.clients.jedis.mcf.HealthCheckStrategy\n     */\n    public StrategySupplier getHealthCheckStrategySupplier() {\n      if (!healthCheckEnabled) {\n        return null;\n      }\n      return healthCheckStrategySupplier;\n    }\n\n    /**\n     * Returns whether health checks are enabled for this database.\n     * @return true if health checks are enabled, false otherwise\n     */\n    public boolean isHealthCheckEnabled() {\n      return healthCheckEnabled;\n    }\n\n    /**\n     * Builder class for creating DatabaseConfig instances with fluent configuration API.\n     * <p>\n     * The Builder provides a convenient way to configure database settings including connection\n     * pooling, weight-based priority, and health check strategies. All configuration methods return\n     * the builder instance for method chaining.\n     * </p>\n     * <p>\n     * <strong>Default Values:</strong>\n     * </p>\n     * <ul>\n     * <li><strong>Weight:</strong> 1.0f (standard priority)</li>\n     * <li><strong>Health Check:</strong> {@link redis.clients.jedis.mcf.PingStrategy#DEFAULT}</li>\n     * <li><strong>Connection Pool:</strong> null (uses default pooling)</li>\n     * </ul>\n     */\n    public static class Builder {\n      /** The Redis endpoint for this database configuration. */\n      private Endpoint endpoint;\n\n      /** The Jedis client configuration. */\n      private JedisClientConfig jedisClientConfig;\n\n      /** Optional connection pool configuration. */\n      private GenericObjectPoolConfig<Connection> connectionPoolConfig;\n\n      /** Weight for database selection priority. Default: 1.0f */\n      private float weight = 1.0f;\n\n      /** Health check enabled. Default: true */\n      private boolean healthCheckEnabled = true;\n\n      /** Health check strategy supplier. Default: PingStrategy.DEFAULT */\n      private StrategySupplier healthCheckStrategySupplier = PingStrategy.DEFAULT;\n\n      /**\n       * Constructs a new Builder with required endpoint and client configuration.\n       * @param endpoint the Redis endpoint (host and port)\n       * @param clientConfig the Jedis client configuration\n       * @throws IllegalArgumentException if endpoint or clientConfig is null\n       */\n      public Builder(Endpoint endpoint, JedisClientConfig clientConfig) {\n        this.endpoint = endpoint;\n        this.jedisClientConfig = clientConfig;\n      }\n\n      /**\n       * Sets the connection pool configuration for this database.\n       * <p>\n       * Connection pooling helps manage connections efficiently and provides better performance\n       * under load. If not specified, default pooling behavior will be used.\n       * </p>\n       * @param connectionPoolConfig the connection pool configuration\n       * @return this builder instance for method chaining\n       */\n      public Builder connectionPoolConfig(\n          GenericObjectPoolConfig<Connection> connectionPoolConfig) {\n        this.connectionPoolConfig = connectionPoolConfig;\n        return this;\n      }\n\n      /**\n       * Sets the weight value for database selection priority.\n       * <p>\n       * Weight determines the priority order for database selection during failover. Databases with\n       * higher weights are preferred over those with lower weights. The system will attempt to use\n       * the highest-weight healthy database available.\n       * </p>\n       * <p>\n       * <strong>Examples:</strong>\n       * </p>\n       * <ul>\n       * <li><strong>1.0f:</strong> Standard priority (default)</li>\n       * <li><strong>0.8f:</strong> Lower priority (secondary database)</li>\n       * <li><strong>0.1f:</strong> Lowest priority (backup database)</li>\n       * </ul>\n       * @param weight the weight value for priority-based selection\n       * @return this builder instance for method chaining\n       */\n      public Builder weight(float weight) {\n        JedisAsserts.isTrue(weight > 0, \"Database weight must be greater than 0\");\n        this.weight = weight;\n        return this;\n      }\n\n      /**\n       * Sets a custom health check strategy supplier for this database.\n       * <p>\n       * The strategy supplier creates health check instances that monitor this database's\n       * availability. Different databases can use different health check strategies based on their\n       * specific requirements.\n       * </p>\n       * @param healthCheckStrategySupplier the health check strategy supplier\n       * @return this builder instance for method chaining\n       * @throws IllegalArgumentException if healthCheckStrategySupplier is null\n       * @see StrategySupplier\n       * @see redis.clients.jedis.mcf.HealthCheckStrategy\n       */\n      public Builder healthCheckStrategySupplier(StrategySupplier healthCheckStrategySupplier) {\n        if (healthCheckStrategySupplier == null) {\n          throw new IllegalArgumentException(\"healthCheckStrategySupplier must not be null\");\n        }\n        this.healthCheckStrategySupplier = healthCheckStrategySupplier;\n        return this;\n      }\n\n      /**\n       * Sets a specific health check strategy instance for this database.\n       * <p>\n       * This is a convenience method that wraps the provided strategy in a supplier that always\n       * returns the same instance. Use this when you have a pre-configured strategy instance.\n       * </p>\n       * <p>\n       * <strong>Note:</strong> The same strategy instance will be reused, so ensure it's\n       * thread-safe if multiple databases might use it.\n       * </p>\n       * @param healthCheckStrategy the health check strategy instance\n       * @return this builder instance for method chaining\n       * @throws IllegalArgumentException if healthCheckStrategy is null\n       * @see #healthCheckStrategySupplier(StrategySupplier)\n       */\n      public Builder healthCheckStrategy(HealthCheckStrategy healthCheckStrategy) {\n        if (healthCheckStrategy == null) {\n          throw new IllegalArgumentException(\"healthCheckStrategy must not be null\");\n        }\n        this.healthCheckStrategySupplier = (hostAndPort, jedisClientConfig) -> healthCheckStrategy;\n        return this;\n      }\n\n      /**\n       * Enables or disables health checks for this database.\n       * <p>\n       * When health checks are disabled (false), the database will not be proactively monitored for\n       * availability. This means:\n       * </p>\n       * <ul>\n       * <li>No background health check threads will be created</li>\n       * <li>Failback to this database must be triggered manually</li>\n       * <li>The database is assumed to be healthy unless circuit breaker opens</li>\n       * </ul>\n       * <p>\n       * When health checks are enabled (true) and no strategy supplier was previously set, the\n       * default {@link redis.clients.jedis.mcf.PingStrategy#DEFAULT} will be used.\n       * </p>\n       * @param healthCheckEnabled true to enable health checks, false to disable\n       * @return this builder instance for method chaining\n       */\n      public Builder healthCheckEnabled(boolean healthCheckEnabled) {\n        this.healthCheckEnabled = healthCheckEnabled;\n        return this;\n      }\n\n      /**\n       * Builds and returns a new DatabaseConfig instance with the configured settings.\n       * @return a new DatabaseConfig instance\n       */\n      public DatabaseConfig build() {\n        return new DatabaseConfig(this);\n      }\n    }\n  }\n\n  /**\n   * Builder class for creating MultiDbConfig instances with comprehensive configuration options.\n   * <p>\n   * The Builder provides a fluent API for configuring all aspects of multi-database failover\n   * behavior, including retry logic, circuit breaker settings, and failback mechanisms. It uses\n   * sensible defaults based on production best practices while allowing fine-tuning for specific\n   * requirements.\n   * </p>\n   * @see MultiDbConfig\n   * @see DatabaseConfig\n   */\n  public static class Builder {\n\n    /** Array of database configurations defining available Redis endpoints. */\n    private final List<DatabaseConfig> databaseConfigs = new ArrayList<>();\n\n    // ============ Retry Configuration Fields ============\n    /** Encapsulated retry configuration for command execution. */\n    private RetryConfig commandRetry = RetryConfig.builder().build();\n\n    // ============ Circuit Breaker Configuration Fields ============\n    /** Encapsulated circuit breaker configuration for failure detection. */\n    private CircuitBreakerConfig failureDetector = CircuitBreakerConfig.builder().build();\n\n    /** List of exception classes that trigger immediate fallback to next database. */\n    private List<Class<? extends Throwable>> fallbackExceptionList = FALLBACK_EXCEPTIONS_DEFAULT;\n\n    // ============ Failover Configuration Fields ============\n    /** Whether to retry failed commands during failover. */\n    private boolean retryOnFailover = false;\n\n    /** Whether automatic failback to higher-priority databases is supported. */\n    private boolean isFailbackSupported = true;\n\n    /** Interval between checks for failback opportunities in milliseconds. */\n    private long failbackCheckInterval = FAILBACK_CHECK_INTERVAL_DEFAULT;\n\n    /** Grace period to keep databases disabled after they become unhealthy in milliseconds. */\n    private long gracePeriod = GRACE_PERIOD_DEFAULT;\n\n    /** Whether to forcefully terminate connections during failover. */\n    private boolean fastFailover = false;\n\n    /** Maximum number of failover attempts. */\n    private int maxNumFailoverAttempts = MAX_NUM_FAILOVER_ATTEMPTS_DEFAULT;\n\n    /** Delay in milliseconds between failover attempts. */\n    private int delayInBetweenFailoverAttempts = DELAY_IN_BETWEEN_FAILOVER_ATTEMPTS_DEFAULT;\n\n    /** Initialization policy for determining when the multi-database connection is ready. */\n    private InitializationPolicy initializationPolicy = InitializationPolicy.BuiltIn.MAJORITY_AVAILABLE;\n\n    /**\n     * Constructs a new Builder with the specified database configurations.\n     */\n    public Builder() {\n    }\n\n    /**\n     * Constructs a new Builder with the specified database configurations.\n     * @param databaseConfigs array of database configurations defining available Redis endpoints\n     * @throws JedisValidationException if databaseConfigs is null or empty\n     */\n    public Builder(DatabaseConfig[] databaseConfigs) {\n\n      this(Arrays.asList(databaseConfigs));\n    }\n\n    /**\n     * Constructs a new Builder with the specified database configurations.\n     * @param databaseConfigs list of database configurations defining available Redis endpoints\n     * @throws JedisValidationException if databaseConfigs is null or empty\n     */\n    public Builder(List<DatabaseConfig> databaseConfigs) {\n      this.databaseConfigs.addAll(databaseConfigs);\n    }\n\n    /**\n     * Adds a pre-configured database configuration.\n     * <p>\n     * This method allows adding a fully configured DatabaseConfig instance, providing maximum\n     * flexibility for advanced configurations including custom health check strategies, connection\n     * pool settings, etc.\n     * </p>\n     * @param databaseConfig the pre-configured database configuration\n     * @return this builder\n     */\n    public Builder database(DatabaseConfig databaseConfig) {\n      this.databaseConfigs.add(databaseConfig);\n      return this;\n    }\n\n    /**\n     * Adds a database endpoint with custom client configuration.\n     * <p>\n     * This method allows specifying database-specific configuration such as authentication, SSL\n     * settings, timeouts, etc. This configuration will override the default client configuration\n     * for this specific database endpoint.\n     * </p>\n     * @param endpoint the Redis server endpoint\n     * @param weight the weight for this endpoint (higher values = higher priority)\n     * @param clientConfig the client configuration for this endpoint\n     * @return this builder\n     */\n    public Builder database(Endpoint endpoint, float weight, JedisClientConfig clientConfig) {\n\n      DatabaseConfig databaseConfig = DatabaseConfig.builder(endpoint, clientConfig).weight(weight)\n          .build();\n\n      this.databaseConfigs.add(databaseConfig);\n      return this;\n    }\n\n    // ============ Retry Configuration Methods ============\n\n    /**\n     * Sets the encapsulated retry configuration for command execution.\n     * <p>\n     * This provides a cleaner API for configuring retry behavior by using a dedicated\n     * {@link RetryConfig} object.\n     * </p>\n     * @param commandRetry the retry configuration\n     * @return this builder instance for method chaining\n     * @see RetryConfig\n     */\n    public Builder commandRetry(RetryConfig commandRetry) {\n      this.commandRetry = commandRetry;\n      return this;\n    }\n\n    // ============ Circuit Breaker Configuration Methods ============\n\n    /**\n     * Sets the encapsulated circuit breaker configuration for failure detection.\n     * <p>\n     * This provides a cleaner API for configuring circuit breaker behavior by using a dedicated\n     * {@link CircuitBreakerConfig} object.\n     * </p>\n     * @param failureDetector the circuit breaker configuration\n     * @return this builder instance for method chaining\n     * @see CircuitBreakerConfig\n     */\n    public Builder failureDetector(CircuitBreakerConfig failureDetector) {\n      this.failureDetector = failureDetector;\n      return this;\n    }\n\n    /**\n     * Sets the list of exception classes that trigger immediate fallback to the next available\n     * database.\n     * <p>\n     * When these exceptions occur, the system will immediately attempt to failover to the next\n     * available database without waiting for circuit breaker thresholds. This enables fast failover\n     * for specific error conditions.\n     * </p>\n     * <p>\n     * <strong>Default exceptions:</strong>\n     * </p>\n     * <ul>\n     * <li>{@link CallNotPermittedException} - Circuit breaker is open</li>\n     * <li>{@link redis.clients.jedis.mcf.ConnectionFailoverException} - Connection-level failover\n     * required</li>\n     * </ul>\n     * @param fallbackExceptionList list of exception classes that trigger immediate fallback\n     * @return this builder instance for method chaining\n     */\n    public Builder fallbackExceptionList(List<Class<? extends Throwable>> fallbackExceptionList) {\n      this.fallbackExceptionList = fallbackExceptionList;\n      return this;\n    }\n\n    // ============ Failover Configuration Methods ============\n\n    /**\n     * Sets whether failed commands should be retried during the failover process.\n     * <p>\n     * When enabled, commands that fail during failover will be retried according to the configured\n     * retry settings on the new database. When disabled, failed commands during failover will\n     * immediately return the failure to the caller.\n     * </p>\n     * <p>\n     * <strong>Trade-offs:</strong>\n     * </p>\n     * <ul>\n     * <li><strong>Enabled:</strong> Better resilience, potentially longer response times</li>\n     * <li><strong>Disabled:</strong> Faster failover, some operations may fail (default)</li>\n     * </ul>\n     * @param retryOnFailover true to retry failed commands during failover, false otherwise\n     * @return this builder instance for method chaining\n     */\n    public Builder retryOnFailover(boolean retryOnFailover) {\n      this.retryOnFailover = retryOnFailover;\n      return this;\n    }\n\n    /**\n     * Sets whether automatic failback to higher-priority databases is supported.\n     * <p>\n     * When enabled, the system will automatically monitor failed da using health checks and\n     * failback to higher-priority (higher weight) databases when they recover. When disabled,\n     * failback must be triggered manually.\n     * </p>\n     * <p>\n     * <strong>Requirements for automatic failback:</strong>\n     * </p>\n     * <ul>\n     * <li>Health checks must be enabled on database configurations</li>\n     * <li>Grace period must elapse after database becomes unhealthy</li>\n     * <li>Higher-priority database must pass health checks</li>\n     * </ul>\n     * @param supported true to enable automatic failback, false for manual failback only\n     * @return this builder instance for method chaining\n     */\n    public Builder failbackSupported(boolean supported) {\n      this.isFailbackSupported = supported;\n      return this;\n    }\n\n    /**\n     * Sets the interval between checks for failback opportunities to recovered databases.\n     * <p>\n     * This controls how frequently the system checks if a higher-priority database has recovered\n     * and is available for failback. Lower values provide faster failback response but increase\n     * monitoring overhead.\n     * </p>\n     * <p>\n     * <strong>Typical Values:</strong>\n     * </p>\n     * <ul>\n     * <li><strong>1-2 seconds:</strong> Fast failback for critical applications</li>\n     * <li><strong>5 seconds:</strong> Balanced approach (default)</li>\n     * <li><strong>10-30 seconds:</strong> Conservative monitoring for stable environments</li>\n     * </ul>\n     * @param failbackCheckInterval interval in milliseconds between failback checks\n     * @return this builder instance for method chaining\n     */\n    public Builder failbackCheckInterval(long failbackCheckInterval) {\n      this.failbackCheckInterval = failbackCheckInterval;\n      return this;\n    }\n\n    /**\n     * Sets the grace period to keep databases disabled after they become unhealthy.\n     * <p>\n     * After a database is marked as unhealthy, it remains disabled for this grace period before\n     * being eligible for failback, even if health checks indicate recovery. This prevents rapid\n     * oscillation between databases during intermittent failures.\n     * </p>\n     * <p>\n     * <strong>Considerations:</strong>\n     * </p>\n     * <ul>\n     * <li><strong>Short periods (5-10s):</strong> Faster recovery, risk of oscillation</li>\n     * <li><strong>Medium periods (10-30s):</strong> Balanced stability (default: 10s)</li>\n     * <li><strong>Long periods (60s+):</strong> Maximum stability, slower recovery</li>\n     * </ul>\n     * @param gracePeriod grace period in milliseconds\n     * @return this builder instance for method chaining\n     */\n    public Builder gracePeriod(long gracePeriod) {\n      this.gracePeriod = gracePeriod;\n      return this;\n    }\n\n    /**\n     * Sets whether to forcefully terminate connections during failover for faster database\n     * switching.\n     * <p>\n     * When enabled, existing connections to the failed database are immediately closed during\n     * failover, potentially reducing failover time but may cause some in-flight operations to fail.\n     * When disabled, connections are closed gracefully.\n     * </p>\n     * <p>\n     * <strong>Trade-offs:</strong>\n     * </p>\n     * <ul>\n     * <li><strong>Enabled:</strong> Faster failover, potential operation failures</li>\n     * <li><strong>Disabled:</strong> Graceful failover, potentially slower (default)</li>\n     * </ul>\n     * @param fastFailover true for fast failover, false for graceful failover\n     * @return this builder instance for method chaining\n     */\n    public Builder fastFailover(boolean fastFailover) {\n      this.fastFailover = fastFailover;\n      return this;\n    }\n\n    /**\n     * Sets the maximum number of failover attempts.\n     * <p>\n     * This setting controls how many times the system will attempt to failover to a different\n     * database before giving up. For example, if set to 3, the system will make 1 initial attempt\n     * plus 2 failover attempts for a total of 3 attempts.\n     * </p>\n     * <p>\n     * <strong>Default:</strong> {@value #MAX_NUM_FAILOVER_ATTEMPTS_DEFAULT}\n     * </p>\n     * @param maxNumFailoverAttempts maximum number of failover attempts\n     * @return this builder instance for method chaining\n     */\n    public Builder maxNumFailoverAttempts(int maxNumFailoverAttempts) {\n      this.maxNumFailoverAttempts = maxNumFailoverAttempts;\n      return this;\n    }\n\n    /**\n     * Sets the delay in milliseconds between failover attempts.\n     * <p>\n     * This setting controls how long the system will wait before attempting to failover to a\n     * different database. For example, if set to 1000, the system will wait 1 second before\n     * attempting to failover to a different database.\n     * </p>\n     * <p>\n     * <strong>Default:</strong> {@value #DELAY_IN_BETWEEN_FAILOVER_ATTEMPTS_DEFAULT} milliseconds\n     * </p>\n     * @param delayInBetweenFailoverAttempts delay in milliseconds between failover attempts\n     * @return this builder instance for method chaining\n     */\n    public Builder delayInBetweenFailoverAttempts(int delayInBetweenFailoverAttempts) {\n      this.delayInBetweenFailoverAttempts = delayInBetweenFailoverAttempts;\n      return this;\n    }\n\n    /**\n     * Sets the initialization policy that determines when the multi-database connection is ready.\n     * <p>\n     * The policy is evaluated based on the completion status of database health checks, and the\n     * decision to continue waiting, succeed, or fail is based on the number of available, pending,\n     * and failed connections.\n     * </p>\n     * <p>\n     * <strong>Built-in policies:</strong>\n     * </p>\n     * <ul>\n     * <li>{@link InitializationPolicy.BuiltIn#ALL_AVAILABLE} - All databases need to be\n     * available</li>\n     * <li>{@link InitializationPolicy.BuiltIn#MAJORITY_AVAILABLE} - Majority of databases need to\n     * be available (default)</li>\n     * <li>{@link InitializationPolicy.BuiltIn#ONE_AVAILABLE} - At least one database needs to be\n     * available</li>\n     * </ul>\n     * @param initializationPolicy the initialization policy to use\n     * @return this builder instance for method chaining\n     */\n    public Builder initializationPolicy(InitializationPolicy initializationPolicy) {\n      JedisAsserts.notNull(initializationPolicy, \"initializationPolicy must not be null\");\n      this.initializationPolicy = initializationPolicy;\n      return this;\n    }\n\n    /**\n     * Builds and returns a new MultiDbConfig instance with all configured settings.\n     * <p>\n     * This method creates the final configuration object by copying all builder settings to the\n     * configuration instance. The builder can be reused after calling build() to create additional\n     * configurations with different settings.\n     * </p>\n     * @return a new MultiDbConfig instance with the configured settings\n     */\n    public MultiDbConfig build() {\n\n      MultiDbConfig config = new MultiDbConfig(this.databaseConfigs.toArray(new DatabaseConfig[0]));\n\n      // Copy retry configuration\n      config.commandRetry = this.commandRetry;\n\n      // Copy circuit breaker configuration\n      config.failureDetector = this.failureDetector;\n\n      // Copy fallback and failover configuration\n      config.fallbackExceptionList = this.fallbackExceptionList;\n      config.retryOnFailover = this.retryOnFailover;\n      config.isFailbackSupported = this.isFailbackSupported;\n      config.failbackCheckInterval = this.failbackCheckInterval;\n      config.gracePeriod = this.gracePeriod;\n      config.fastFailover = this.fastFailover;\n      config.maxNumFailoverAttempts = this.maxNumFailoverAttempts;\n      config.delayInBetweenFailoverAttempts = this.delayInBetweenFailoverAttempts;\n      config.initializationPolicy = this.initializationPolicy;\n\n      return config;\n    }\n\n  }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/MultiNodePipelineBase.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.Set;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.util.IOUtils;\n\npublic abstract class MultiNodePipelineBase extends AbstractPipeline {\n\n  private final Logger log = LoggerFactory.getLogger(getClass());\n\n  /**\n   * The number of processes for {@code sync()}. If you have enough cores for client (and you have\n   * more than 3 cluster nodes), you may increase this number of workers.\n   * Suggestion:&nbsp;&le;&nbsp;cluster&nbsp;nodes.\n   */\n  public static volatile int MULTI_NODE_PIPELINE_SYNC_WORKERS = 3;\n\n  private final Map<HostAndPort, Queue<Response<?>>> pipelinedResponses;\n  private final Map<HostAndPort, Connection> connections;\n  private volatile boolean syncing = false;\n  protected final CommandFlagsRegistry commandFlagsRegistry;\n\n  public MultiNodePipelineBase(CommandObjects commandObjects) {\n    this(commandObjects, StaticCommandFlagsRegistry.registry());\n  }\n\n  protected MultiNodePipelineBase(CommandObjects commandObjects, CommandFlagsRegistry commandFlagsRegistry) {\n    super(commandObjects);\n    this.commandFlagsRegistry = commandFlagsRegistry;\n    pipelinedResponses = new LinkedHashMap<>();\n    connections = new LinkedHashMap<>();\n  }\n\n  protected abstract HostAndPort getNodeKey(CommandArguments args);\n\n  protected abstract Connection getConnection(HostAndPort nodeKey);\n\n  @Override\n  protected final <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n    // Validate that the command is supported in pipeline mode\n    validatePipelineCommand(commandObject.getArguments());\n\n    HostAndPort nodeKey = getNodeKey(commandObject.getArguments());\n\n    Queue<Response<?>> queue;\n    Connection connection;\n    if (pipelinedResponses.containsKey(nodeKey)) {\n      queue = pipelinedResponses.get(nodeKey);\n      connection = connections.get(nodeKey);\n    } else {\n      Connection newOne = getConnection(nodeKey);\n      connections.putIfAbsent(nodeKey, newOne);\n      connection = connections.get(nodeKey);\n      if (connection != newOne) {\n        log.debug(\"Duplicate connection to {}, closing it.\", nodeKey);\n        IOUtils.closeQuietly(newOne);\n      }\n\n      pipelinedResponses.putIfAbsent(nodeKey, new LinkedList<>());\n      queue = pipelinedResponses.get(nodeKey);\n    }\n\n    connection.sendCommand(commandObject.getArguments());\n    Response<T> response = new Response<>(commandObject.getBuilder());\n    queue.add(response);\n    return response;\n  }\n\n  @Override\n  public void close() {\n    try {\n      sync();\n    } finally {\n      connections.values().forEach(IOUtils::closeQuietly);\n    }\n  }\n\n  @Override\n  public final void sync() {\n    if (syncing) {\n      return;\n    }\n    syncing = true;\n\n    boolean multiNode = pipelinedResponses.size() > 1;\n    Executor executor;\n    ExecutorService executorService = null;\n    if (multiNode) {\n      executorService = Executors.newFixedThreadPool(MULTI_NODE_PIPELINE_SYNC_WORKERS);\n      executor = executorService;\n    } else {\n      executor = Runnable::run;\n    }\n    CountDownLatch countDownLatch = multiNode\n        ? new CountDownLatch(pipelinedResponses.size())\n        : null;\n\n    Iterator<Map.Entry<HostAndPort, Queue<Response<?>>>> pipelinedResponsesIterator = pipelinedResponses.entrySet()\n        .iterator();\n    while (pipelinedResponsesIterator.hasNext()) {\n      Map.Entry<HostAndPort, Queue<Response<?>>> entry = pipelinedResponsesIterator.next();\n      HostAndPort nodeKey = entry.getKey();\n      Queue<Response<?>> queue = entry.getValue();\n      Connection connection = connections.get(nodeKey);\n      executor.execute(() -> {\n        try {\n          List<Object> unformatted = connection.getMany(queue.size());\n          for (Object o : unformatted) {\n            queue.poll().set(o);\n          }\n        } catch (JedisConnectionException jce) {\n          log.error(\"Error with connection to \" + nodeKey, jce);\n          // cleanup the connection\n          // TODO these operations not thread-safe and when executed here, the iter may moved\n          pipelinedResponsesIterator.remove();\n          connections.remove(nodeKey);\n          IOUtils.closeQuietly(connection);\n        } finally {\n          if (multiNode) {\n            countDownLatch.countDown();\n          }\n        }\n      });\n    }\n\n    if (multiNode) {\n      try {\n        countDownLatch.await();\n      } catch (InterruptedException e) {\n        log.error(\"Thread is interrupted during sync.\", e);\n      }\n\n      executorService.shutdownNow();\n    }\n\n    syncing = false;\n  }\n\n  /**\n   * Validates that a command can be executed in a multi-node pipeline.\n   * <p>\n   * Commands with multi-node request policies (ALL_SHARDS, MULTI_SHARD, ALL_NODES, SPECIAL)\n   * are rejected UNLESS they have keys that route to a single slot, in which case they can\n   * be executed on that single node.\n   * </p>\n   *\n   * @param args the command arguments\n   * @throws UnsupportedOperationException if the command requires multi-node execution\n   */\n  private void validatePipelineCommand(CommandArguments args) {\n    CommandFlagsRegistry.RequestPolicy policy =\n        commandFlagsRegistry.getRequestPolicy(args);\n\n    // For multi-node policies, check if the command can be routed to a single slot\n    switch (policy) {\n      case ALL_SHARDS:\n      case MULTI_SHARD:\n      case ALL_NODES:\n      case SPECIAL:\n        // If the command has keys that route to a single slot, allow it\n        Set<Integer> slots = args.getKeyHashSlots();\n        if (slots.size() == 1) {\n          // Command can be routed to a single slot - allow it\n          return;\n        }\n\n        // Command cannot be routed to a single slot - reject it\n        String policyName = policy.name();\n        throw new UnsupportedOperationException(\n            \"Command '\" + args.getCommand() + \"' with \" + policyName + \" request policy \"\n                + \"cannot be executed in pipeline mode because it cannot be routed to a single slot. \"\n                + (slots.isEmpty()\n                    ? \"This command has no keys to determine routing. \"\n                    : \"This command's keys map to multiple slots (\" + slots.size() + \" slots). \")\n                + \"Use non-pipeline cluster client for this command.\");\n\n      case DEFAULT:\n      default:\n        // DEFAULT policy and unknown policies - allow standard command execution\n        // Routes to single node based on key hash\n        break;\n    }\n  }\n\n  @Deprecated\n  public Response<Long> waitReplicas(int replicas, long timeout) {\n    return appendCommand(commandObjects.waitReplicas(replicas, timeout));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Pipeline.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.Closeable;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\n\nimport redis.clients.jedis.commands.DatabasePipelineCommands;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class Pipeline extends AbstractPipeline implements DatabasePipelineCommands, Closeable {\n\n  private final Queue<Response<?>> pipelinedResponses = new LinkedList<>();\n  protected final Connection connection;\n  private final boolean closeConnection;\n  //private final CommandObjects commandObjects;\n\n  public Pipeline(Jedis jedis) {\n    this(jedis.getConnection(), false);\n  }\n\n  public Pipeline(Connection connection) {\n    this(connection, false);\n  }\n\n  public Pipeline(Connection connection, boolean closeConnection) {\n    this(connection, closeConnection, createCommandObjects(connection));\n  }\n\n  private static CommandObjects createCommandObjects(Connection connection) {\n    CommandObjects commandObjects = new CommandObjects();\n    RedisProtocol proto = connection.getRedisProtocol();\n    if (proto != null) commandObjects.setProtocol(proto);\n    return commandObjects;\n  }\n\n  Pipeline(Connection connection, boolean closeConnection, CommandObjects commandObjects) {\n    super(commandObjects);\n    this.connection = connection;\n    this.closeConnection = closeConnection;\n  }\n\n  @Override\n  public final <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n    connection.sendCommand(commandObject.getArguments());\n    Response<T> response = new Response<>(commandObject.getBuilder());\n    pipelinedResponses.add(response);\n    return response;\n  }\n\n  @Override\n  public void close() {\n    try {\n      sync();\n    } finally {\n      if (closeConnection) {\n        IOUtils.closeQuietly(connection);\n      }\n    }\n  }\n\n  /**\n   * Synchronize pipeline by reading all responses. This operation close the pipeline. In order to\n   * get return values from pipelined commands, capture the different Response&lt;?&gt; of the\n   * commands you execute.\n   */\n  @Override\n  public void sync() {\n    if (!hasPipelinedResponse()) return;\n    List<Object> unformatted = connection.getMany(pipelinedResponses.size());\n    for (Object rawReply : unformatted) {\n      pipelinedResponses.poll().set(rawReply);\n    }\n  }\n\n  /**\n   * Synchronize pipeline by reading all responses. This operation close the pipeline. Whenever\n   * possible try to avoid using this version and use Pipeline.sync() as it won't go through all the\n   * responses and generate the right response type (usually it is a waste of time).\n   * @return A list of all the responses in the order you executed them.\n   */\n  public List<Object> syncAndReturnAll() {\n    if (hasPipelinedResponse()) {\n      List<Object> unformatted = connection.getMany(pipelinedResponses.size());\n      List<Object> formatted = new ArrayList<>();\n      for (Object rawReply : unformatted) {\n        try {\n          Response<?> response = pipelinedResponses.poll();\n          response.set(rawReply);\n          formatted.add(response.get());\n        } catch (JedisDataException e) {\n          formatted.add(e);\n        }\n      }\n      return formatted;\n    } else {\n      return java.util.Collections.<Object> emptyList();\n    }\n  }\n\n  public final boolean hasPipelinedResponse() {\n    return pipelinedResponses.size() > 0;\n  }\n\n  public Response<Long> waitReplicas(int replicas, long timeout) {\n    return appendCommand(commandObjects.waitReplicas(replicas, timeout));\n  }\n\n  public Response<KeyValue<Long, Long>> waitAOF(long numLocal, long numReplicas, long timeout) {\n    return appendCommand(commandObjects.waitAOF(numLocal, numReplicas, timeout));\n  }\n\n  public Response<List<String>> time() {\n    return appendCommand(new CommandObject<>(commandObjects.commandArguments(Protocol.Command.TIME), BuilderFactory.STRING_LIST));\n  }\n\n  @Override\n  public Response<String> select(final int index) {\n    return appendCommand(new CommandObject<>(commandObjects.commandArguments(Protocol.Command.SELECT).add(index), BuilderFactory.STRING));\n  }\n\n  @Override\n  public Response<Long> dbSize() {\n    return appendCommand(new CommandObject<>(commandObjects.commandArguments(Protocol.Command.DBSIZE), BuilderFactory.LONG));\n  }\n\n  @Override\n  public Response<String> swapDB(final int index1, final int index2) {\n    return appendCommand(new CommandObject<>(commandObjects.commandArguments(Protocol.Command.SWAPDB)\n        .add(index1).add(index2), BuilderFactory.STRING));\n  }\n\n  @Override\n  public Response<Long> move(String key, int dbIndex) {\n    return appendCommand(new CommandObject<>(commandObjects.commandArguments(Protocol.Command.MOVE)\n        .key(key).add(dbIndex), BuilderFactory.LONG));\n  }\n\n  @Override\n  public Response<Long> move(final byte[] key, final int dbIndex) {\n    return appendCommand(new CommandObject<>(commandObjects.commandArguments(Protocol.Command.MOVE)\n        .key(key).add(dbIndex), BuilderFactory.LONG));\n  }\n\n  @Override\n  public Response<Boolean> copy(String srcKey, String dstKey, int db, boolean replace) {\n    return appendCommand(commandObjects.copy(srcKey, dstKey, db, replace));\n  }\n\n  @Override\n  public Response<Boolean> copy(byte[] srcKey, byte[] dstKey, int db, boolean replace) {\n    return appendCommand(commandObjects.copy(srcKey, dstKey, db, replace));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, byte[] key, int destinationDB, int timeout) {\n    return appendCommand(commandObjects.migrate(host, port, key, destinationDB, timeout));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, String key, int destinationDB, int timeout) {\n    return appendCommand(commandObjects.migrate(host, port, key, destinationDB, timeout));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, int destinationDB, int timeout, MigrateParams params, byte[]... keys) {\n    return appendCommand(commandObjects.migrate(host, port, destinationDB, timeout, params, keys));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, int destinationDB, int timeout, MigrateParams params, String... keys) {\n    return appendCommand(commandObjects.migrate(host, port, destinationDB, timeout, params, keys));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/PipeliningBase.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.json.JSONArray;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.args.*;\nimport redis.clients.jedis.bloom.*;\nimport redis.clients.jedis.commands.PipelineBinaryCommands;\nimport redis.clients.jedis.commands.PipelineCommands;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.commands.RedisModulePipelineCommands;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.hybrid.FTHybridParams;\nimport redis.clients.jedis.search.hybrid.HybridResult;\nimport redis.clients.jedis.search.schemafields.SchemaField;\nimport redis.clients.jedis.timeseries.*;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.util.KeyValue;\n\npublic abstract class PipeliningBase\n    implements PipelineCommands, PipelineBinaryCommands, RedisModulePipelineCommands {\n\n  protected final CommandObjects commandObjects;\n\n  protected PipeliningBase(CommandObjects commandObjects) {\n    this.commandObjects = commandObjects;\n  }\n\n  protected abstract <T> Response<T> appendCommand(CommandObject<T> commandObject);\n\n  @Override\n  public Response<Boolean> exists(String key) {\n    return appendCommand(commandObjects.exists(key));\n  }\n\n  @Override\n  public Response<Long> exists(String... keys) {\n    return appendCommand(commandObjects.exists(keys));\n  }\n\n  @Override\n  public Response<Long> persist(String key) {\n    return appendCommand(commandObjects.persist(key));\n  }\n\n  @Override\n  public Response<String> type(String key) {\n    return appendCommand(commandObjects.type(key));\n  }\n\n  @Override\n  public Response<byte[]> dump(String key) {\n    return appendCommand(commandObjects.dump(key));\n  }\n\n  @Override\n  public Response<String> restore(String key, long ttl, byte[] serializedValue) {\n    return appendCommand(commandObjects.restore(key, ttl, serializedValue));\n  }\n\n  @Override\n  public Response<String> restore(String key, long ttl, byte[] serializedValue, RestoreParams params) {\n    return appendCommand(commandObjects.restore(key, ttl, serializedValue, params));\n  }\n\n  @Override\n  public Response<Long> expire(String key, long seconds) {\n    return appendCommand(commandObjects.expire(key, seconds));\n  }\n\n  @Override\n  public Response<Long> expire(String key, long seconds, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.expire(key, seconds, expiryOption));\n  }\n\n  @Override\n  public Response<Long> pexpire(String key, long milliseconds) {\n    return appendCommand(commandObjects.pexpire(key, milliseconds));\n  }\n\n  @Override\n  public Response<Long> pexpire(String key, long milliseconds, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.pexpire(key, milliseconds, expiryOption));\n  }\n\n  @Override\n  public Response<Long> expireTime(String key) {\n    return appendCommand(commandObjects.expireTime(key));\n  }\n\n  @Override\n  public Response<Long> pexpireTime(String key) {\n    return appendCommand(commandObjects.pexpireTime(key));\n  }\n\n  @Override\n  public Response<Long> expireAt(String key, long unixTime) {\n    return appendCommand(commandObjects.expireAt(key, unixTime));\n  }\n\n  @Override\n  public Response<Long> expireAt(String key, long unixTime, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.expireAt(key, unixTime, expiryOption));\n  }\n\n  @Override\n  public Response<Long> pexpireAt(String key, long millisecondsTimestamp) {\n    return appendCommand(commandObjects.pexpireAt(key, millisecondsTimestamp));\n  }\n\n  @Override\n  public Response<Long> pexpireAt(String key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption));\n  }\n\n  @Override\n  public Response<Long> ttl(String key) {\n    return appendCommand(commandObjects.ttl(key));\n  }\n\n  @Override\n  public Response<Long> pttl(String key) {\n    return appendCommand(commandObjects.pttl(key));\n  }\n\n  @Override\n  public Response<Long> touch(String key) {\n    return appendCommand(commandObjects.touch(key));\n  }\n\n  @Override\n  public Response<Long> touch(String... keys) {\n    return appendCommand(commandObjects.touch(keys));\n  }\n\n  @Override\n  public Response<List<String>> sort(String key) {\n    return appendCommand(commandObjects.sort(key));\n  }\n\n  @Override\n  public Response<Long> sort(String key, String dstKey) {\n    return appendCommand(commandObjects.sort(key, dstKey));\n  }\n\n  @Override\n  public Response<List<String>> sort(String key, SortingParams sortingParams) {\n    return appendCommand(commandObjects.sort(key, sortingParams));\n  }\n\n  @Override\n  public Response<Long> sort(String key, SortingParams sortingParams, String dstKey) {\n    return appendCommand(commandObjects.sort(key, sortingParams, dstKey));\n  }\n\n  @Override\n  public Response<List<String>> sortReadonly(String key, SortingParams sortingParams) {\n    return appendCommand(commandObjects.sortReadonly(key, sortingParams));\n  }\n\n  @Override\n  public Response<Long> del(String key) {\n    return appendCommand(commandObjects.del(key));\n  }\n\n  @Override\n  public Response<Long> del(String... keys) {\n    return appendCommand(commandObjects.del(keys));\n  }\n\n  @Override\n  public Response<Long> unlink(String key) {\n    return appendCommand(commandObjects.unlink(key));\n  }\n\n  @Override\n  public Response<Long> unlink(String... keys) {\n    return appendCommand(commandObjects.unlink(keys));\n  }\n\n  @Override\n  public Response<Boolean> copy(String srcKey, String dstKey, boolean replace) {\n    return appendCommand(commandObjects.copy(srcKey, dstKey, replace));\n  }\n\n  @Override\n  public Response<String> rename(String oldkey, String newkey) {\n    return appendCommand(commandObjects.rename(oldkey, newkey));\n  }\n\n  @Override\n  public Response<Long> renamenx(String oldkey, String newkey) {\n    return appendCommand(commandObjects.renamenx(oldkey, newkey));\n  }\n\n  @Override\n  public Response<Long> memoryUsage(String key) {\n    return appendCommand(commandObjects.memoryUsage(key));\n  }\n\n  @Override\n  public Response<Long> memoryUsage(String key, int samples) {\n    return appendCommand(commandObjects.memoryUsage(key, samples));\n  }\n\n  @Override\n  public Response<Long> objectRefcount(String key) {\n    return appendCommand(commandObjects.objectRefcount(key));\n  }\n\n  @Override\n  public Response<String> objectEncoding(String key) {\n    return appendCommand(commandObjects.objectEncoding(key));\n  }\n\n  @Override\n  public Response<Long> objectIdletime(String key) {\n    return appendCommand(commandObjects.objectIdletime(key));\n  }\n\n  @Override\n  public Response<Long> objectFreq(String key) {\n    return appendCommand(commandObjects.objectFreq(key));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, String key, int timeout) {\n    return appendCommand(commandObjects.migrate(host, port, key, timeout));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, int timeout, MigrateParams params, String... keys) {\n    return appendCommand(commandObjects.migrate(host, port, timeout, params, keys));\n  }\n\n  @Override\n  public Response<Set<String>> keys(String pattern) {\n    return appendCommand(commandObjects.keys(pattern));\n  }\n\n  @Override\n  public Response<ScanResult<String>> scan(String cursor) {\n    return appendCommand(commandObjects.scan(cursor));\n  }\n\n  @Override\n  public Response<ScanResult<String>> scan(String cursor, ScanParams params) {\n    return appendCommand(commandObjects.scan(cursor, params));\n  }\n\n  @Override\n  public Response<ScanResult<String>> scan(String cursor, ScanParams params, String type) {\n    return appendCommand(commandObjects.scan(cursor, params, type));\n  }\n\n  @Override\n  public Response<String> randomKey() {\n    return appendCommand(commandObjects.randomKey());\n  }\n\n  @Override\n  public Response<String> get(String key) {\n    return appendCommand(commandObjects.get(key));\n  }\n\n  @Override\n  public Response<String> setGet(String key, String value) {\n    return appendCommand(commandObjects.setGet(key, value));\n  }\n\n  @Override\n  public Response<String> setGet(String key, String value, SetParams params) {\n    return appendCommand(commandObjects.setGet(key, value, params));\n  }\n\n  @Override\n  public Response<String> getDel(String key) {\n    return appendCommand(commandObjects.getDel(key));\n  }\n\n  @Override\n  public Response<String> getEx(String key, GetExParams params) {\n    return appendCommand(commandObjects.getEx(key, params));\n  }\n\n  @Override\n  public Response<Boolean> setbit(String key, long offset, boolean value) {\n    return appendCommand(commandObjects.setbit(key, offset, value));\n  }\n\n  @Override\n  public Response<Boolean> getbit(String key, long offset) {\n    return appendCommand(commandObjects.getbit(key, offset));\n  }\n\n  @Override\n  public Response<Long> setrange(String key, long offset, String value) {\n    return appendCommand(commandObjects.setrange(key, offset, value));\n  }\n\n  @Override\n  public Response<String> getrange(String key, long startOffset, long endOffset) {\n    return appendCommand(commandObjects.getrange(key, startOffset, endOffset));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#setGet(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  @Override\n  public Response<String> getSet(String key, String value) {\n    return appendCommand(commandObjects.getSet(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public Response<Long> setnx(String key, String value) {\n    return appendCommand(commandObjects.setnx(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public Response<String> setex(String key, long seconds, String value) {\n    return appendCommand(commandObjects.setex(key, seconds, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public Response<String> psetex(String key, long milliseconds, String value) {\n    return appendCommand(commandObjects.psetex(key, milliseconds, value));\n  }\n\n  @Override\n  public Response<List<String>> mget(String... keys) {\n    return appendCommand(commandObjects.mget(keys));\n  }\n\n  @Override\n  public Response<String> mset(String... keysvalues) {\n    return appendCommand(commandObjects.mset(keysvalues));\n  }\n\n  @Override\n  public Response<Long> msetnx(String... keysvalues) {\n    return appendCommand(commandObjects.msetnx(keysvalues));\n  }\n\n  @Override\n  public Response<Boolean> msetex(MSetExParams params, String... keysvalues) {\n    return appendCommand(commandObjects.msetex(params, keysvalues));\n  }\n\n  @Override\n  public Response<Long> incr(String key) {\n    return appendCommand(commandObjects.incr(key));\n  }\n\n  @Override\n  public Response<Long> incrBy(String key, long increment) {\n    return appendCommand(commandObjects.incrBy(key, increment));\n  }\n\n  @Override\n  public Response<Double> incrByFloat(String key, double increment) {\n    return appendCommand(commandObjects.incrByFloat(key, increment));\n  }\n\n  @Override\n  public Response<Long> decr(String key) {\n    return appendCommand(commandObjects.decr(key));\n  }\n\n  @Override\n  public Response<Long> decrBy(String key, long decrement) {\n    return appendCommand(commandObjects.decrBy(key, decrement));\n  }\n\n  @Override\n  public Response<Long> append(String key, String value) {\n    return appendCommand(commandObjects.append(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#getrange(String, long, long)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  @Override\n  public Response<String> substr(String key, int start, int end) {\n    return appendCommand(commandObjects.substr(key, start, end));\n  }\n\n  @Override\n  public Response<Long> strlen(String key) {\n    return appendCommand(commandObjects.strlen(key));\n  }\n\n  @Override\n  public Response<Long> bitcount(String key) {\n    return appendCommand(commandObjects.bitcount(key));\n  }\n\n  @Override\n  public Response<Long> bitcount(String key, long start, long end) {\n    return appendCommand(commandObjects.bitcount(key, start, end));\n  }\n\n  @Override\n  public Response<Long> bitcount(String key, long start, long end, BitCountOption option) {\n    return appendCommand(commandObjects.bitcount(key, start, end, option));\n  }\n\n  @Override\n  public Response<Long> bitpos(String key, boolean value) {\n    return appendCommand(commandObjects.bitpos(key, value));\n  }\n\n  @Override\n  public Response<Long> bitpos(String key, boolean value, BitPosParams params) {\n    return appendCommand(commandObjects.bitpos(key, value, params));\n  }\n\n  @Override\n  public Response<List<Long>> bitfield(String key, String... arguments) {\n    return appendCommand(commandObjects.bitfield(key, arguments));\n  }\n\n  @Override\n  public Response<List<Long>> bitfieldReadonly(String key, String... arguments) {\n    return appendCommand(commandObjects.bitfieldReadonly(key, arguments));\n  }\n\n  @Override\n  public Response<Long> bitop(BitOP op, String destKey, String... srcKeys) {\n    return appendCommand(commandObjects.bitop(op, destKey, srcKeys));\n  }\n\n  @Override\n  public Response<LCSMatchResult> lcs(String keyA, String keyB, LCSParams params) {\n    return appendCommand(commandObjects.lcs(keyA, keyB, params));\n  }\n\n  @Override\n  public Response<String> set(String key, String value) {\n    return appendCommand(commandObjects.set(key, value));\n  }\n\n  @Override\n  public Response<String> set(String key, String value, SetParams params) {\n    return appendCommand(commandObjects.set(key, value, params));\n  }\n\n  @Override\n  public Response<Long> rpush(String key, String... string) {\n    return appendCommand(commandObjects.rpush(key, string));\n\n  }\n\n  @Override\n  public Response<Long> lpush(String key, String... string) {\n    return appendCommand(commandObjects.lpush(key, string));\n  }\n\n  @Override\n  public Response<Long> llen(String key) {\n    return appendCommand(commandObjects.llen(key));\n  }\n\n  @Override\n  public Response<List<String>> lrange(String key, long start, long stop) {\n    return appendCommand(commandObjects.lrange(key, start, stop));\n  }\n\n  @Override\n  public Response<String> ltrim(String key, long start, long stop) {\n    return appendCommand(commandObjects.ltrim(key, start, stop));\n  }\n\n  @Override\n  public Response<String> lindex(String key, long index) {\n    return appendCommand(commandObjects.lindex(key, index));\n  }\n\n  @Override\n  public Response<String> lset(String key, long index, String value) {\n    return appendCommand(commandObjects.lset(key, index, value));\n  }\n\n  @Override\n  public Response<Long> lrem(String key, long count, String value) {\n    return appendCommand(commandObjects.lrem(key, count, value));\n  }\n\n  @Override\n  public Response<String> lpop(String key) {\n    return appendCommand(commandObjects.lpop(key));\n  }\n\n  @Override\n  public Response<List<String>> lpop(String key, int count) {\n    return appendCommand(commandObjects.lpop(key, count));\n  }\n\n  @Override\n  public Response<Long> lpos(String key, String element) {\n    return appendCommand(commandObjects.lpos(key, element));\n  }\n\n  @Override\n  public Response<Long> lpos(String key, String element, LPosParams params) {\n    return appendCommand(commandObjects.lpos(key, element, params));\n  }\n\n  @Override\n  public Response<List<Long>> lpos(String key, String element, LPosParams params, long count) {\n    return appendCommand(commandObjects.lpos(key, element, params, count));\n  }\n\n  @Override\n  public Response<String> rpop(String key) {\n    return appendCommand(commandObjects.rpop(key));\n  }\n\n  @Override\n  public Response<List<String>> rpop(String key, int count) {\n    return appendCommand(commandObjects.rpop(key, count));\n  }\n\n  @Override\n  public Response<Long> linsert(String key, ListPosition where, String pivot, String value) {\n    return appendCommand(commandObjects.linsert(key, where, pivot, value));\n  }\n\n  @Override\n  public Response<Long> lpushx(String key, String... strings) {\n    return appendCommand(commandObjects.lpushx(key, strings));\n  }\n\n  @Override\n  public Response<Long> rpushx(String key, String... strings) {\n    return appendCommand(commandObjects.rpushx(key, strings));\n  }\n\n  @Override\n  public Response<List<String>> blpop(int timeout, String key) {\n    return appendCommand(commandObjects.blpop(timeout, key));\n  }\n\n  @Override\n  public Response<KeyValue<String, String>> blpop(double timeout, String key) {\n    return appendCommand(commandObjects.blpop(timeout, key));\n  }\n\n  @Override\n  public Response<List<String>> brpop(int timeout, String key) {\n    return appendCommand(commandObjects.brpop(timeout, key));\n  }\n\n  @Override\n  public Response<KeyValue<String, String>> brpop(double timeout, String key) {\n    return appendCommand(commandObjects.brpop(timeout, key));\n  }\n\n  @Override\n  public Response<List<String>> blpop(int timeout, String... keys) {\n    return appendCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, String>> blpop(double timeout, String... keys) {\n    return appendCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public Response<List<String>> brpop(int timeout, String... keys) {\n    return appendCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, String>> brpop(double timeout, String... keys) {\n    return appendCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#lmove(String, String, ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public Response<String> rpoplpush(String srcKey, String dstKey) {\n    return appendCommand(commandObjects.rpoplpush(srcKey, dstKey));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#blmove(String, String, ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public Response<String> brpoplpush(String source, String destination, int timeout) {\n    return appendCommand(commandObjects.brpoplpush(source, destination, timeout));\n  }\n\n  @Override\n  public Response<String> lmove(String srcKey, String dstKey, ListDirection from, ListDirection to) {\n    return appendCommand(commandObjects.lmove(srcKey, dstKey, from, to));\n  }\n\n  @Override\n  public Response<String> blmove(String srcKey, String dstKey, ListDirection from, ListDirection to, double timeout) {\n    return appendCommand(commandObjects.blmove(srcKey, dstKey, from, to, timeout));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<String>>> lmpop(ListDirection direction, String... keys) {\n    return appendCommand(commandObjects.lmpop(direction, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<String>>> lmpop(ListDirection direction, int count, String... keys) {\n    return appendCommand(commandObjects.lmpop(direction, count, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<String>>> blmpop(double timeout, ListDirection direction, String... keys) {\n    return appendCommand(commandObjects.blmpop(timeout, direction, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<String>>> blmpop(double timeout, ListDirection direction, int count, String... keys) {\n    return appendCommand(commandObjects.blmpop(timeout, direction, count, keys));\n  }\n\n  @Override\n  public Response<Long> hset(String key, String field, String value) {\n    return appendCommand(commandObjects.hset(key, field, value));\n  }\n\n  @Override\n  public Response<Long> hset(String key, Map<String, String> hash) {\n    return appendCommand(commandObjects.hset(key, hash));\n  }\n\n  /**\n   * Sets the specified field in the hash stored at key to the specified value with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   *\n   * @param key the key of the hash\n   * @param params additional parameters for the HSETEX command\n   * @param field the field in the hash to set\n   * @param value the value to set in the specified field\n   * @return 0 if no fields were set, 1 if all the fields were set\n   *\n   * @see HSetExParams\n   */\n  @Override\n  public Response<Long> hsetex(String key, HSetExParams params, String field, String value) {\n    return appendCommand(commandObjects.hsetex(key, params, field, value));\n  }\n\n  /**\n   * Sets the specified fields in the hash stored at key to the specified values with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   *\n   * @param key the key of the hash\n   * @param params the parameters for the HSetEx command\n   * @param hash the map containing field-value pairs to set in the hash\n   * @return 0 if no fields were set, 1 if all the fields were set\n   *\n   * @see HSetExParams\n   */\n  @Override\n  public Response<Long> hsetex(String key, HSetExParams params, Map<String, String> hash) {\n    return appendCommand(commandObjects.hsetex(key, params, hash));\n  }\n\n  @Override\n  public Response<String> hget(String key, String field) {\n    return appendCommand(commandObjects.hget(key, field));\n  }\n\n  /**\n   * Retrieves the values associated with the specified fields in a hash stored at the given key\n   * and optionally sets their expiration. Use `HGetExParams` object to specify expiration parameters.\n   *\n   * @param key the key of the hash\n   * @param params additional parameters for the HGETEX command\n   * @param fields the fields whose values are to be retrieved\n   * @return a list of the value associated with each field or nil if the field doesn’t exist.\n   *\n   * @see HGetExParams\n   */\n  @Override\n  public Response<List<String>> hgetex(String key, HGetExParams params, String... fields) {\n    return appendCommand(commandObjects.hgetex(key, params, fields));\n  }\n\n  /**\n   * Retrieves the values associated with the specified fields in the hash stored at the given key\n   * and then deletes those fields from the hash.\n   *\n   * @param key the key of the hash\n   * @param fields the fields whose values are to be retrieved and then deleted\n   * @return a list of values associated with the specified fields before they were deleted\n   */\n  @Override\n  public Response<List<String>> hgetdel(String key, String... fields) {\n    return appendCommand(commandObjects.hgetdel(key, fields));\n  }\n\n  @Override\n  public Response<Long> hsetnx(String key, String field, String value) {\n    return appendCommand(commandObjects.hsetnx(key, field, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#hset(String, Map)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  @Override\n  public Response<String> hmset(String key, Map<String, String> hash) {\n    return appendCommand(commandObjects.hmset(key, hash));\n  }\n\n  @Override\n  public Response<List<String>> hmget(String key, String... fields) {\n    return appendCommand(commandObjects.hmget(key, fields));\n  }\n\n  @Override\n  public Response<Long> hincrBy(String key, String field, long value) {\n    return appendCommand(commandObjects.hincrBy(key, field, value));\n  }\n\n  @Override\n  public Response<Double> hincrByFloat(String key, String field, double value) {\n    return appendCommand(commandObjects.hincrByFloat(key, field, value));\n  }\n\n  @Override\n  public Response<Boolean> hexists(String key, String field) {\n    return appendCommand(commandObjects.hexists(key, field));\n  }\n\n  @Override\n  public Response<Long> hdel(String key, String... field) {\n    return appendCommand(commandObjects.hdel(key, field));\n  }\n\n  @Override\n  public Response<Long> hlen(String key) {\n    return appendCommand(commandObjects.hlen(key));\n  }\n\n  @Override\n  public Response<Set<String>> hkeys(String key) {\n    return appendCommand(commandObjects.hkeys(key));\n  }\n\n  @Override\n  public Response<List<String>> hvals(String key) {\n    return appendCommand(commandObjects.hvals(key));\n  }\n\n  @Override\n  public Response<Map<String, String>> hgetAll(String key) {\n    return appendCommand(commandObjects.hgetAll(key));\n  }\n\n  @Override\n  public Response<String> hrandfield(String key) {\n    return appendCommand(commandObjects.hrandfield(key));\n  }\n\n  @Override\n  public Response<List<String>> hrandfield(String key, long count) {\n    return appendCommand(commandObjects.hrandfield(key, count));\n  }\n\n  @Override\n  public Response<List<Map.Entry<String, String>>> hrandfieldWithValues(String key, long count) {\n    return appendCommand(commandObjects.hrandfieldWithValues(key, count));\n  }\n\n  @Override\n  public Response<ScanResult<Map.Entry<String, String>>> hscan(String key, String cursor, ScanParams params) {\n    return appendCommand(commandObjects.hscan(key, cursor, params));\n  }\n\n  @Override\n  public Response<ScanResult<String>> hscanNoValues(String key, String cursor, ScanParams params) {\n    return appendCommand(commandObjects.hscanNoValues(key, cursor, params));\n  }\n\n  @Override\n  public Response<Long> hstrlen(String key, String field) {\n    return appendCommand(commandObjects.hstrlen(key, field));\n  }\n\n  @Override\n  public Response<List<Long>> hexpire(String key, long seconds, String... fields) {\n    return appendCommand(commandObjects.hexpire(key, seconds, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpire(String key, long seconds, ExpiryOption condition, String... fields) {\n    return appendCommand(commandObjects.hexpire(key, seconds, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpire(String key, long milliseconds, String... fields) {\n    return appendCommand(commandObjects.hpexpire(key, milliseconds, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) {\n    return appendCommand(commandObjects.hpexpire(key, milliseconds, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpireAt(String key, long unixTimeSeconds, String... fields) {\n    return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) {\n    return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpireAt(String key, long unixTimeMillis, String... fields) {\n    return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) {\n    return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpireTime(String key, String... fields) {\n    return appendCommand(commandObjects.hexpireTime(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpireTime(String key, String... fields) {\n    return appendCommand(commandObjects.hpexpireTime(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> httl(String key, String... fields) {\n    return appendCommand(commandObjects.httl(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpttl(String key, String... fields) {\n    return appendCommand(commandObjects.hpttl(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpersist(String key, String... fields) {\n    return appendCommand(commandObjects.hpersist(key, fields));\n  }\n\n  @Override\n  public Response<Long> sadd(String key, String... members) {\n    return appendCommand(commandObjects.sadd(key, members));\n  }\n\n  @Override\n  public Response<Set<String>> smembers(String key) {\n    return appendCommand(commandObjects.smembers(key));\n  }\n\n  @Override\n  public Response<Long> srem(String key, String... members) {\n    return appendCommand(commandObjects.srem(key, members));\n  }\n\n  @Override\n  public Response<String> spop(String key) {\n    return appendCommand(commandObjects.spop(key));\n  }\n\n  @Override\n  public Response<Set<String>> spop(String key, long count) {\n    return appendCommand(commandObjects.spop(key, count));\n  }\n\n  @Override\n  public Response<Long> scard(String key) {\n    return appendCommand(commandObjects.scard(key));\n  }\n\n  @Override\n  public Response<Boolean> sismember(String key, String member) {\n    return appendCommand(commandObjects.sismember(key, member));\n  }\n\n  @Override\n  public Response<List<Boolean>> smismember(String key, String... members) {\n    return appendCommand(commandObjects.smismember(key, members));\n  }\n\n  @Override\n  public Response<String> srandmember(String key) {\n    return appendCommand(commandObjects.srandmember(key));\n  }\n\n  @Override\n  public Response<List<String>> srandmember(String key, int count) {\n    return appendCommand(commandObjects.srandmember(key, count));\n  }\n\n  @Override\n  public Response<ScanResult<String>> sscan(String key, String cursor, ScanParams params) {\n    return appendCommand(commandObjects.sscan(key, cursor, params));\n  }\n\n  @Override\n  public Response<Set<String>> sdiff(String... keys) {\n    return appendCommand(commandObjects.sdiff(keys));\n  }\n\n  @Override\n  public Response<Long> sdiffstore(String dstKey, String... keys) {\n    return appendCommand(commandObjects.sdiffstore(dstKey, keys));\n  }\n\n  @Override\n  public Response<Set<String>> sinter(String... keys) {\n    return appendCommand(commandObjects.sinter(keys));\n  }\n\n  @Override\n  public Response<Long> sinterstore(String dstKey, String... keys) {\n    return appendCommand(commandObjects.sinterstore(dstKey, keys));\n  }\n\n  @Override\n  public Response<Long> sintercard(String... keys) {\n    return appendCommand(commandObjects.sintercard(keys));\n  }\n\n  @Override\n  public Response<Long> sintercard(int limit, String... keys) {\n    return appendCommand(commandObjects.sintercard(limit, keys));\n  }\n\n  @Override\n  public Response<Set<String>> sunion(String... keys) {\n    return appendCommand(commandObjects.sunion(keys));\n  }\n\n  @Override\n  public Response<Long> sunionstore(String dstKey, String... keys) {\n    return appendCommand(commandObjects.sunionstore(dstKey, keys));\n  }\n\n  @Override\n  public Response<Long> smove(String srcKey, String dstKey, String member) {\n    return appendCommand(commandObjects.smove(srcKey, dstKey, member));\n  }\n\n  @Override\n  public Response<Long> zadd(String key, double score, String member) {\n    return appendCommand(commandObjects.zadd(key, score, member));\n  }\n\n  @Override\n  public Response<Long> zadd(String key, double score, String member, ZAddParams params) {\n    return appendCommand(commandObjects.zadd(key, score, member, params));\n  }\n\n  @Override\n  public Response<Long> zadd(String key, Map<String, Double> scoreMembers) {\n    return appendCommand(commandObjects.zadd(key, scoreMembers));\n  }\n\n  @Override\n  public Response<Long> zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {\n    return appendCommand(commandObjects.zadd(key, scoreMembers, params));\n  }\n\n  @Override\n  public Response<Double> zaddIncr(String key, double score, String member, ZAddParams params) {\n    return appendCommand(commandObjects.zaddIncr(key, score, member, params));\n  }\n\n  @Override\n  public Response<Long> zrem(String key, String... members) {\n    return appendCommand(commandObjects.zrem(key, members));\n  }\n\n  @Override\n  public Response<Double> zincrby(String key, double increment, String member) {\n    return appendCommand(commandObjects.zincrby(key, increment, member));\n  }\n\n  @Override\n  public Response<Double> zincrby(String key, double increment, String member, ZIncrByParams params) {\n    return appendCommand(commandObjects.zincrby(key, increment, member, params));\n  }\n\n  @Override\n  public Response<Long> zrank(String key, String member) {\n    return appendCommand(commandObjects.zrank(key, member));\n  }\n\n  @Override\n  public Response<Long> zrevrank(String key, String member) {\n    return appendCommand(commandObjects.zrevrank(key, member));\n  }\n\n  @Override\n  public Response<KeyValue<Long, Double>> zrankWithScore(String key, String member) {\n    return appendCommand(commandObjects.zrankWithScore(key, member));\n  }\n\n  @Override\n  public Response<KeyValue<Long, Double>> zrevrankWithScore(String key, String member) {\n    return appendCommand(commandObjects.zrevrankWithScore(key, member));\n  }\n\n  @Override\n  public Response<List<String>> zrange(String key, long start, long stop) {\n    return appendCommand(commandObjects.zrange(key, start, stop));\n  }\n\n  @Override\n  public Response<List<String>> zrevrange(String key, long start, long stop) {\n    return appendCommand(commandObjects.zrevrange(key, start, stop));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeWithScores(String key, long start, long stop) {\n    return appendCommand(commandObjects.zrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeWithScores(String key, long start, long stop) {\n    return appendCommand(commandObjects.zrevrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public Response<String> zrandmember(String key) {\n    return appendCommand(commandObjects.zrandmember(key));\n  }\n\n  @Override\n  public Response<List<String>> zrandmember(String key, long count) {\n    return appendCommand(commandObjects.zrandmember(key, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrandmemberWithScores(String key, long count) {\n    return appendCommand(commandObjects.zrandmemberWithScores(key, count));\n  }\n\n  @Override\n  public Response<Long> zcard(String key) {\n    return appendCommand(commandObjects.zcard(key));\n  }\n\n  @Override\n  public Response<Double> zscore(String key, String member) {\n    return appendCommand(commandObjects.zscore(key, member));\n  }\n\n  @Override\n  public Response<List<Double>> zmscore(String key, String... members) {\n    return appendCommand(commandObjects.zmscore(key, members));\n  }\n\n  @Override\n  public Response<Tuple> zpopmax(String key) {\n    return appendCommand(commandObjects.zpopmax(key));\n  }\n\n  @Override\n  public Response<List<Tuple>> zpopmax(String key, int count) {\n    return appendCommand(commandObjects.zpopmax(key, count));\n  }\n\n  @Override\n  public Response<Tuple> zpopmin(String key) {\n    return appendCommand(commandObjects.zpopmin(key));\n  }\n\n  @Override\n  public Response<List<Tuple>> zpopmin(String key, int count) {\n    return appendCommand(commandObjects.zpopmin(key, count));\n  }\n\n  @Override\n  public Response<Long> zcount(String key, double min, double max) {\n    return appendCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public Response<Long> zcount(String key, String min, String max) {\n    return appendCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public Response<List<String>> zrangeByScore(String key, double min, double max) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<List<String>> zrangeByScore(String key, String min, String max) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<List<String>> zrevrangeByScore(String key, double max, double min) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  @Override\n  public Response<List<String>> zrangeByScore(String key, double min, double max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<String>> zrevrangeByScore(String key, String max, String min) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  @Override\n  public Response<List<String>> zrangeByScore(String key, String min, String max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<String>> zrevrangeByScore(String key, double max, double min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(String key, double min, double max) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(String key, double max, double min) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(String key, double min, double max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<String>> zrevrangeByScore(String key, String max, String min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(String key, String min, String max) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(String key, String max, String min) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(String key, String min, String max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<String>> zrange(String key, ZRangeParams zRangeParams) {\n    return appendCommand(commandObjects.zrange(key, zRangeParams));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeWithScores(String key, ZRangeParams zRangeParams) {\n    return appendCommand(commandObjects.zrangeWithScores(key, zRangeParams));\n  }\n\n  @Override\n  public Response<Long> zrangestore(String dest, String src, ZRangeParams zRangeParams) {\n    return appendCommand(commandObjects.zrangestore(dest, src, zRangeParams));\n  }\n\n  @Override\n  public Response<Long> zremrangeByRank(String key, long start, long stop) {\n    return appendCommand(commandObjects.zremrangeByRank(key, start, stop));\n  }\n\n  @Override\n  public Response<Long> zremrangeByScore(String key, double min, double max) {\n    return appendCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<Long> zremrangeByScore(String key, String min, String max) {\n    return appendCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<Long> zlexcount(String key, String min, String max) {\n    return appendCommand(commandObjects.zlexcount(key, min, max));\n  }\n\n  @Override\n  public Response<List<String>> zrangeByLex(String key, String min, String max) {\n    return appendCommand(commandObjects.zrangeByLex(key, min, max));\n  }\n\n  @Override\n  public Response<List<String>> zrangeByLex(String key, String min, String max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByLex(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<String>> zrevrangeByLex(String key, String max, String min) {\n    return appendCommand(commandObjects.zrevrangeByLex(key, max, min));\n  }\n\n  @Override\n  public Response<List<String>> zrevrangeByLex(String key, String max, String min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<Long> zremrangeByLex(String key, String min, String max) {\n    return appendCommand(commandObjects.zremrangeByLex(key, min, max));\n  }\n\n  @Override\n  public Response<ScanResult<Tuple>> zscan(String key, String cursor, ScanParams params) {\n    return appendCommand(commandObjects.zscan(key, cursor, params));\n  }\n\n  @Override\n  public Response<KeyValue<String, Tuple>> bzpopmax(double timeout, String... keys) {\n    return appendCommand(commandObjects.bzpopmax(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, Tuple>> bzpopmin(double timeout, String... keys) {\n    return appendCommand(commandObjects.bzpopmin(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<Tuple>>> zmpop(SortedSetOption option, String... keys) {\n    return appendCommand(commandObjects.zmpop(option, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<Tuple>>> zmpop(SortedSetOption option, int count, String... keys) {\n    return appendCommand(commandObjects.zmpop(option, count, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<Tuple>>> bzmpop(double timeout, SortedSetOption option, String... keys) {\n    return appendCommand(commandObjects.bzmpop(timeout, option, keys));\n  }\n\n  @Override\n  public Response<KeyValue<String, List<Tuple>>> bzmpop(double timeout, SortedSetOption option, int count, String... keys) {\n    return appendCommand(commandObjects.bzmpop(timeout, option, count, keys));\n  }\n\n  @Override\n  public Response<List<String>> zdiff(String... keys) {\n    return appendCommand(commandObjects.zdiff(keys));\n  }\n\n  @Override\n  public Response<List<Tuple>> zdiffWithScores(String... keys) {\n    return appendCommand(commandObjects.zdiffWithScores(keys));\n  }\n\n  @Override\n  @Deprecated\n  public Response<Long> zdiffStore(String dstKey, String... keys) {\n    return appendCommand(commandObjects.zdiffStore(dstKey, keys));\n  }\n\n  @Override\n  public Response<Long> zdiffstore(String dstKey, String... keys) {\n    return appendCommand(commandObjects.zdiffstore(dstKey, keys));\n  }\n\n  @Override\n  public Response<Long> zinterstore(String dstKey, String... sets) {\n    return appendCommand(commandObjects.zinterstore(dstKey, sets));\n  }\n\n  @Override\n  public Response<Long> zinterstore(String dstKey, ZParams params, String... sets) {\n    return appendCommand(commandObjects.zinterstore(dstKey, params, sets));\n  }\n\n  @Override\n  public Response<List<String>> zinter(ZParams params, String... keys) {\n    return appendCommand(commandObjects.zinter(params, keys));\n  }\n\n  @Override\n  public Response<List<Tuple>> zinterWithScores(ZParams params, String... keys) {\n    return appendCommand(commandObjects.zinterWithScores(params, keys));\n  }\n\n  @Override\n  public Response<Long> zintercard(String... keys) {\n    return appendCommand(commandObjects.zintercard(keys));\n  }\n\n  @Override\n  public Response<Long> zintercard(long limit, String... keys) {\n    return appendCommand(commandObjects.zintercard(limit, keys));\n  }\n\n  @Override\n  public Response<List<String>> zunion(ZParams params, String... keys) {\n    return appendCommand(commandObjects.zunion(params, keys));\n  }\n\n  @Override\n  public Response<List<Tuple>> zunionWithScores(ZParams params, String... keys) {\n    return appendCommand(commandObjects.zunionWithScores(params, keys));\n  }\n\n  @Override\n  public Response<Long> zunionstore(String dstKey, String... sets) {\n    return appendCommand(commandObjects.zunionstore(dstKey, sets));\n  }\n\n  @Override\n  public Response<Long> zunionstore(String dstKey, ZParams params, String... sets) {\n    return appendCommand(commandObjects.zunionstore(dstKey, params, sets));\n  }\n\n  @Override\n  public Response<Long> geoadd(String key, double longitude, double latitude, String member) {\n    return appendCommand(commandObjects.geoadd(key, longitude, latitude, member));\n  }\n\n  @Override\n  public Response<Long> geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {\n    return appendCommand(commandObjects.geoadd(key, memberCoordinateMap));\n  }\n\n  @Override\n  public Response<Long> geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap) {\n    return appendCommand(commandObjects.geoadd(key, params, memberCoordinateMap));\n  }\n\n  @Override\n  public Response<Double> geodist(String key, String member1, String member2) {\n    return appendCommand(commandObjects.geodist(key, member1, member2));\n  }\n\n  @Override\n  public Response<Double> geodist(String key, String member1, String member2, GeoUnit unit) {\n    return appendCommand(commandObjects.geodist(key, member1, member2, unit));\n  }\n\n  @Override\n  public Response<List<String>> geohash(String key, String... members) {\n    return appendCommand(commandObjects.geohash(key, members));\n  }\n\n  @Override\n  public Response<List<GeoCoordinate>> geopos(String key, String... members) {\n    return appendCommand(commandObjects.geopos(key, members));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadius(String key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadius(key, longitude, latitude, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadius(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMember(String key, String member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadiusByMember(key, member, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMember(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadiusByMember(key, member, radius, unit, param));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));\n  }\n\n  @Override\n  public Response<Long> georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return appendCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public Response<Long> georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return appendCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(String key, String member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, member, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, coord, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(String key, String member, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, member, width, height, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, coord, width, height, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(String key, GeoSearchParam params) {\n    return appendCommand(commandObjects.geosearch(key, params));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(String dest, String src, String member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, member, radius, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(String dest, String src, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, coord, radius, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(String dest, String src, String member, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, member, width, height, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(String dest, String src, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, coord, width, height, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(String dest, String src, GeoSearchParam params) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, params));\n  }\n\n  @Override\n  public Response<Long> geosearchStoreStoreDist(String dest, String src, GeoSearchParam params) {\n    return appendCommand(commandObjects.geosearchStoreStoreDist(dest, src, params));\n  }\n\n  @Override\n  public Response<Long> pfadd(String key, String... elements) {\n    return appendCommand(commandObjects.pfadd(key, elements));\n  }\n\n  @Override\n  public Response<String> pfmerge(String destkey, String... sourcekeys) {\n    return appendCommand(commandObjects.pfmerge(destkey, sourcekeys));\n  }\n\n  @Override\n  public Response<Long> pfcount(String key) {\n    return appendCommand(commandObjects.pfcount(key));\n  }\n\n  @Override\n  public Response<Long> pfcount(String... keys) {\n    return appendCommand(commandObjects.pfcount(keys));\n  }\n\n  @Override\n  public Response<StreamEntryID> xadd(String key, StreamEntryID id, Map<String, String> hash) {\n    return appendCommand(commandObjects.xadd(key, id, hash));\n  }\n\n  @Override\n  public Response<StreamEntryID> xadd(String key, XAddParams params, Map<String, String> hash) {\n    return appendCommand(commandObjects.xadd(key, params, hash));\n  }\n\n  @Override\n  public Response<Long> xlen(String key) {\n    return appendCommand(commandObjects.xlen(key));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrange(String key, StreamEntryID start, StreamEntryID end) {\n    return appendCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrange(String key, StreamEntryID start, StreamEntryID end, int count) {\n    return appendCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrevrange(String key, StreamEntryID end, StreamEntryID start) {\n    return appendCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrevrange(String key, StreamEntryID end, StreamEntryID start, int count) {\n    return appendCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrange(String key, String start, String end) {\n    return appendCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrange(String key, String start, String end, int count) {\n    return appendCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrevrange(String key, String end, String start) {\n    return appendCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xrevrange(String key, String end, String start, int count) {\n    return appendCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public Response<Long> xack(String key, String group, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xack(key, group, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xackdel(String key, String group, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xackdel(key, group, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xackdel(key, group, trimMode, ids));\n  }\n\n  @Override\n  public Response<String> xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream) {\n    return appendCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream));\n  }\n\n  @Override\n  public Response<String> xgroupSetID(String key, String groupName, StreamEntryID id) {\n    return appendCommand(commandObjects.xgroupSetID(key, groupName, id));\n  }\n\n  @Override\n  public Response<Long> xgroupDestroy(String key, String groupName) {\n    return appendCommand(commandObjects.xgroupDestroy(key, groupName));\n  }\n\n  @Override\n  public Response<Boolean> xgroupCreateConsumer(String key, String groupName, String consumerName) {\n    return appendCommand(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public Response<Long> xgroupDelConsumer(String key, String groupName, String consumerName) {\n    return appendCommand(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public Response<StreamPendingSummary> xpending(String key, String groupName) {\n    return appendCommand(commandObjects.xpending(key, groupName));\n  }\n\n  @Override\n  public Response<List<StreamPendingEntry>> xpending(String key, String groupName, XPendingParams params) {\n    return appendCommand(commandObjects.xpending(key, groupName, params));\n  }\n\n  @Override\n  public Response<Long> xdel(String key, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xdel(key, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xdelex(String key, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xdelex(key, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xdelex(key, trimMode, ids));\n  }\n\n  @Override\n  public Response<Long> xtrim(String key, long maxLen, boolean approximate) {\n    return appendCommand(commandObjects.xtrim(key, maxLen, approximate));\n  }\n\n  @Override\n  public Response<Long> xtrim(String key, XTrimParams params) {\n    return appendCommand(commandObjects.xtrim(key, params));\n  }\n\n  @Override\n  public Response<List<StreamEntry>> xclaim(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryID>> xclaimJustId(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    return appendCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public Response<Map.Entry<StreamEntryID, List<StreamEntry>>> xautoclaim(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) {\n    return appendCommand(commandObjects.xautoclaim(key, group, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Response<Map.Entry<StreamEntryID, List<StreamEntryID>>> xautoclaimJustId(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) {\n    return appendCommand(commandObjects.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Response<StreamInfo> xinfoStream(String key) {\n    return appendCommand(commandObjects.xinfoStream(key));\n  }\n\n  @Override\n  public Response<StreamFullInfo> xinfoStreamFull(String key) {\n    return appendCommand(commandObjects.xinfoStreamFull(key));\n  }\n\n  @Override\n  public Response<StreamFullInfo> xinfoStreamFull(String key, int count) {\n    return appendCommand(commandObjects.xinfoStreamFull(key, count));\n  }\n\n  @Override\n  public Response<List<StreamGroupInfo>> xinfoGroups(String key) {\n    return appendCommand(commandObjects.xinfoGroups(key));\n  }\n\n  @Override\n  public Response<List<StreamConsumersInfo>> xinfoConsumers(String key, String group) {\n    return appendCommand(commandObjects.xinfoConsumers(key, group));\n  }\n\n  @Override\n  public Response<List<StreamConsumerInfo>> xinfoConsumers2(String key, String group) {\n    return appendCommand(commandObjects.xinfoConsumers2(key, group));\n  }\n\n  @Override\n  public Response<List<Map.Entry<String, List<StreamEntry>>>> xread(XReadParams xReadParams, Map<String, StreamEntryID> streams) {\n    return appendCommand(commandObjects.xread(xReadParams, streams));\n  }\n\n  @Override\n  public Response<Map<String, List<StreamEntry>>> xreadAsMap(XReadParams xReadParams, Map<String, StreamEntryID> streams) {\n    return appendCommand(commandObjects.xreadAsMap(xReadParams, streams));\n  }\n\n  @Override\n  public Response<List<Map.Entry<String, List<StreamEntry>>>> xreadGroup(String groupName, String consumer, XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams) {\n    return appendCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Response<Map<String, List<StreamEntry>>> xreadGroupAsMap(String groupName, String consumer, XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams) {\n    return appendCommand(commandObjects.xreadGroupAsMap(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Response<String> xcfgset(String key, XCfgSetParams params) {\n    return appendCommand(commandObjects.xcfgset(key, params));\n  }\n\n  @Override\n  public Response<Object> eval(String script) {\n    return appendCommand(commandObjects.eval(script));\n  }\n\n  @Override\n  public Response<Object> eval(String script, int keyCount, String... params) {\n    return appendCommand(commandObjects.eval(script, keyCount, params));\n  }\n\n  @Override\n  public Response<Object> eval(String script, List<String> keys, List<String> args) {\n    return appendCommand(commandObjects.eval(script, keys, args));\n  }\n\n  @Override\n  public Response<Object> evalReadonly(String script, List<String> keys, List<String> args) {\n    return appendCommand(commandObjects.evalReadonly(script, keys, args));\n  }\n\n  @Override\n  public Response<Object> evalsha(String sha1) {\n    return appendCommand(commandObjects.evalsha(sha1));\n  }\n\n  @Override\n  public Response<Object> evalsha(String sha1, int keyCount, String... params) {\n    return appendCommand(commandObjects.evalsha(sha1, keyCount, params));\n  }\n\n  @Override\n  public Response<Object> evalsha(String sha1, List<String> keys, List<String> args) {\n    return appendCommand(commandObjects.evalsha(sha1, keys, args));\n  }\n\n  @Override\n  public Response<Object> evalshaReadonly(String sha1, List<String> keys, List<String> args) {\n    return appendCommand(commandObjects.evalshaReadonly(sha1, keys, args));\n  }\n\n  @Override\n  public Response<Long> waitReplicas(String sampleKey, int replicas, long timeout) {\n    return appendCommand(commandObjects.waitReplicas(sampleKey, replicas, timeout));\n  }\n\n  @Override\n  public Response<KeyValue<Long, Long>> waitAOF(String sampleKey, long numLocal, long numReplicas, long timeout) {\n    return appendCommand(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout));\n  }\n\n  @Override\n  public Response<Object> eval(String script, String sampleKey) {\n    return appendCommand(commandObjects.eval(script, sampleKey));\n  }\n\n  @Override\n  public Response<Object> evalsha(String sha1, String sampleKey) {\n    return appendCommand(commandObjects.evalsha(sha1, sampleKey));\n  }\n\n  @Override\n  public Response<List<Boolean>> scriptExists(String sampleKey, String... sha1) {\n    return appendCommand(commandObjects.scriptExists(sampleKey, sha1));\n  }\n\n  @Override\n  public Response<String> scriptLoad(String script, String sampleKey) {\n    return appendCommand(commandObjects.scriptLoad(script, sampleKey));\n  }\n\n  @Override\n  public Response<String> scriptFlush(String sampleKey) {\n    return appendCommand(commandObjects.scriptFlush(sampleKey));\n  }\n\n  @Override\n  public Response<String> scriptFlush(String sampleKey, FlushMode flushMode) {\n    return appendCommand(commandObjects.scriptFlush(sampleKey, flushMode));\n  }\n\n  @Override\n  public Response<String> scriptKill(String sampleKey) {\n    return appendCommand(commandObjects.scriptKill(sampleKey));\n  }\n\n  @Override\n  public Response<Object> fcall(byte[] name, List<byte[]> keys, List<byte[]> args) {\n    return appendCommand(commandObjects.fcall(name, keys, args));\n  }\n\n  @Override\n  public Response<Object> fcall(String name, List<String> keys, List<String> args) {\n    return appendCommand(commandObjects.fcall(name, keys, args));\n  }\n\n  @Override\n  public Response<Object> fcallReadonly(byte[] name, List<byte[]> keys, List<byte[]> args) {\n    return appendCommand(commandObjects.fcallReadonly(name, keys, args));\n  }\n\n  @Override\n  public Response<Object> fcallReadonly(String name, List<String> keys, List<String> args) {\n    return appendCommand(commandObjects.fcallReadonly(name, keys, args));\n  }\n\n  @Override\n  public Response<String> functionDelete(byte[] libraryName) {\n    return appendCommand(commandObjects.functionDelete(libraryName));\n  }\n\n  @Override\n  public Response<String> functionDelete(String libraryName) {\n    return appendCommand(commandObjects.functionDelete(libraryName));\n  }\n\n  @Override\n  public Response<byte[]> functionDump() {\n    return appendCommand(commandObjects.functionDump());\n  }\n\n  @Override\n  public Response<List<LibraryInfo>> functionList(String libraryNamePattern) {\n    return appendCommand(commandObjects.functionList(libraryNamePattern));\n  }\n\n  @Override\n  public Response<List<LibraryInfo>> functionList() {\n    return appendCommand(commandObjects.functionList());\n  }\n\n  @Override\n  public Response<List<LibraryInfo>> functionListWithCode(String libraryNamePattern) {\n    return appendCommand(commandObjects.functionListWithCode(libraryNamePattern));\n  }\n\n  @Override\n  public Response<List<LibraryInfo>> functionListWithCode() {\n    return appendCommand(commandObjects.functionListWithCode());\n  }\n\n  @Override\n  public Response<List<Object>> functionListBinary() {\n    return appendCommand(commandObjects.functionListBinary());\n  }\n\n  @Override\n  public Response<List<Object>> functionList(final byte[] libraryNamePattern) {\n    return appendCommand(commandObjects.functionList(libraryNamePattern));\n  }\n\n  @Override\n  public Response<List<Object>> functionListWithCodeBinary() {\n    return appendCommand(commandObjects.functionListWithCodeBinary());\n  }\n\n  @Override\n  public Response<List<Object>> functionListWithCode(final byte[] libraryNamePattern) {\n    return appendCommand(commandObjects.functionListWithCode(libraryNamePattern));\n  }\n\n  @Override\n  public Response<String> functionLoad(byte[] functionCode) {\n    return appendCommand(commandObjects.functionLoad(functionCode));\n  }\n\n  @Override\n  public Response<String> functionLoad(String functionCode) {\n    return appendCommand(commandObjects.functionLoad(functionCode));\n  }\n\n  @Override\n  public Response<String> functionLoadReplace(byte[] functionCode) {\n    return appendCommand(commandObjects.functionLoadReplace(functionCode));\n  }\n\n  @Override\n  public Response<String> functionLoadReplace(String functionCode) {\n    return appendCommand(commandObjects.functionLoadReplace(functionCode));\n  }\n\n  @Override\n  public Response<String> functionRestore(byte[] serializedValue) {\n    return appendCommand(commandObjects.functionRestore(serializedValue));\n  }\n\n  @Override\n  public Response<String> functionRestore(byte[] serializedValue, FunctionRestorePolicy policy) {\n    return appendCommand(commandObjects.functionRestore(serializedValue, policy));\n  }\n\n  @Override\n  public Response<String> functionFlush() {\n    return appendCommand(commandObjects.functionFlush());\n  }\n\n  @Override\n  public Response<String> functionFlush(FlushMode mode) {\n    return appendCommand(commandObjects.functionFlush(mode));\n  }\n\n  @Override\n  public Response<String> functionKill() {\n    return appendCommand(commandObjects.functionKill());\n  }\n\n  @Override\n  public Response<FunctionStats> functionStats() {\n    return appendCommand(commandObjects.functionStats());\n  }\n\n  @Override\n  public Response<Object> functionStatsBinary() {\n    return appendCommand(commandObjects.functionStatsBinary());\n  }\n\n  @Override\n  public Response<Long> geoadd(byte[] key, double longitude, double latitude, byte[] member) {\n    return appendCommand(commandObjects.geoadd(key, longitude, latitude, member));\n  }\n\n  @Override\n  public Response<Long> geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    return appendCommand(commandObjects.geoadd(key, memberCoordinateMap));\n  }\n\n  @Override\n  public Response<Long> geoadd(byte[] key, GeoAddParams params, Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    return appendCommand(commandObjects.geoadd(key, params, memberCoordinateMap));\n  }\n\n  @Override\n  public Response<Double> geodist(byte[] key, byte[] member1, byte[] member2) {\n    return appendCommand(commandObjects.geodist(key, member1, member2));\n  }\n\n  @Override\n  public Response<Double> geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) {\n    return appendCommand(commandObjects.geodist(key, member1, member2, unit));\n  }\n\n  @Override\n  public Response<List<byte[]>> geohash(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.geohash(key, members));\n  }\n\n  @Override\n  public Response<List<GeoCoordinate>> geopos(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.geopos(key, members));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadius(key, longitude, latitude, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadiusByMember(key, member, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadiusByMember(key, member, radius, unit, param));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return appendCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));\n  }\n\n  @Override\n  public Response<Long> georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return appendCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public Response<Long> georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return appendCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, member, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, coord, radius, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, member, width, height, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearch(key, coord, width, height, unit));\n  }\n\n  @Override\n  public Response<List<GeoRadiusResponse>> geosearch(byte[] key, GeoSearchParam params) {\n    return appendCommand(commandObjects.geosearch(key, params));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(byte[] dest, byte[] src, byte[] member, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, member, radius, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, coord, radius, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(byte[] dest, byte[] src, byte[] member, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, member, width, height, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, coord, width, height, unit));\n  }\n\n  @Override\n  public Response<Long> geosearchStore(byte[] dest, byte[] src, GeoSearchParam params) {\n    return appendCommand(commandObjects.geosearchStore(dest, src, params));\n  }\n\n  @Override\n  public Response<Long> geosearchStoreStoreDist(byte[] dest, byte[] src, GeoSearchParam params) {\n    return appendCommand(commandObjects.geosearchStoreStoreDist(dest, src, params));\n  }\n\n  @Override\n  public Response<Long> hset(byte[] key, byte[] field, byte[] value) {\n    return appendCommand(commandObjects.hset(key, field, value));\n  }\n\n  @Override\n  public Response<Long> hset(byte[] key, Map<byte[], byte[]> hash) {\n    return appendCommand(commandObjects.hset(key, hash));\n  }\n\n  /**\n   * Sets the specified field in the hash stored at key to the specified value with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   *\n   * @param key the key of the hash\n   * @param params the parameters for the HSetEx command\n   * @param field the field in the hash to set\n   * @param value the value to set in the specified field\n   * @return 0 if no fields were set, 1 if all the fields were set\n   *\n   * @see HSetExParams\n   */\n  @Override\n  public Response<Long> hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) {\n    return appendCommand(commandObjects.hsetex(key, params, field, value));\n  }\n\n  /**\n   * Sets the specified fields in the hash stored at key to the specified values with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   *\n   * @param key the key of the hash\n   * @param params the parameters for the HSetEx command\n   * @param hash the map containing field-value pairs to set in the hash\n   * @return 0 if no fields were set, 1 if all the fields were set\n   *\n   * @see HSetExParams\n   */\n  @Override\n  public Response<Long> hsetex(byte[] key, HSetExParams params, Map<byte[], byte[]> hash) {\n    return appendCommand(commandObjects.hsetex(key, params, hash));\n  }\n\n  @Override\n  public Response<byte[]> hget(byte[] key, byte[] field) {\n    return appendCommand(commandObjects.hget(key, field));\n  }\n\n  /**\n   * Retrieves the values associated with the specified fields in a hash stored at the given key\n   * and optionally sets their expiration. Use `HGetExParams` object to specify expiration parameters.\n   *\n   * @param key the key of the hash\n   * @param params additional parameters for the HGETEX command\n   * @param fields the fields whose values are to be retrieved\n   * @return a list of the value associated with each field or nil if the field doesn’t exist.\n   *\n   * @see HGetExParams\n   */\n  @Override\n  public Response<List<byte[]>> hgetex(byte[] key, HGetExParams params, byte[]... fields) {\n    return appendCommand(commandObjects.hgetex(key, params, fields));\n  }\n\n  /**\n   * Retrieves the values associated with the specified fields in the hash stored at the given key\n   * and then deletes those fields from the hash.\n   *\n   * @param key the key of the hash\n   * @param fields the fields whose values are to be retrieved and then deleted\n   * @return a list of values associated with the specified fields before they were deleted\n   */\n  @Override\n  public Response<List<byte[]>> hgetdel(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.hgetdel(key, fields));\n  }\n\n  @Override\n  public Response<Long> hsetnx(byte[] key, byte[] field, byte[] value) {\n    return appendCommand(commandObjects.hsetnx(key, field, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#hset(byte[], Map)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  @Override\n  public Response<String> hmset(byte[] key, Map<byte[], byte[]> hash) {\n    return appendCommand(commandObjects.hmset(key, hash));\n  }\n\n  @Override\n  public Response<List<byte[]>> hmget(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.hmget(key, fields));\n  }\n\n  @Override\n  public Response<Long> hincrBy(byte[] key, byte[] field, long value) {\n    return appendCommand(commandObjects.hincrBy(key, field, value));\n  }\n\n  @Override\n  public Response<Double> hincrByFloat(byte[] key, byte[] field, double value) {\n    return appendCommand(commandObjects.hincrByFloat(key, field, value));\n  }\n\n  @Override\n  public Response<Boolean> hexists(byte[] key, byte[] field) {\n    return appendCommand(commandObjects.hexists(key, field));\n  }\n\n  @Override\n  public Response<Long> hdel(byte[] key, byte[]... field) {\n    return appendCommand(commandObjects.hdel(key, field));\n  }\n\n  @Override\n  public Response<Long> hlen(byte[] key) {\n    return appendCommand(commandObjects.hlen(key));\n  }\n\n  @Override\n  public Response<Set<byte[]>> hkeys(byte[] key) {\n    return appendCommand(commandObjects.hkeys(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> hvals(byte[] key) {\n    return appendCommand(commandObjects.hvals(key));\n  }\n\n  @Override\n  public Response<Map<byte[], byte[]>> hgetAll(byte[] key) {\n    return appendCommand(commandObjects.hgetAll(key));\n  }\n\n  @Override\n  public Response<byte[]> hrandfield(byte[] key) {\n    return appendCommand(commandObjects.hrandfield(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> hrandfield(byte[] key, long count) {\n    return appendCommand(commandObjects.hrandfield(key, count));\n  }\n\n  @Override\n  public Response<List<Map.Entry<byte[], byte[]>>> hrandfieldWithValues(byte[] key, long count) {\n    return appendCommand(commandObjects.hrandfieldWithValues(key, count));\n  }\n\n  @Override\n  public Response<ScanResult<Map.Entry<byte[], byte[]>>> hscan(byte[] key, byte[] cursor, ScanParams params) {\n    return appendCommand(commandObjects.hscan(key, cursor, params));\n  }\n\n  @Override\n  public Response<ScanResult<byte[]>> hscanNoValues(byte[] key, byte[] cursor, ScanParams params) {\n    return appendCommand(commandObjects.hscanNoValues(key, cursor, params));\n  }\n\n  @Override\n  public Response<Long> hstrlen(byte[] key, byte[] field) {\n    return appendCommand(commandObjects.hstrlen(key, field));\n  }\n\n  @Override\n  public Response<List<Long>> hexpire(byte[] key, long seconds, byte[]... fields) {\n    return appendCommand(commandObjects.hexpire(key, seconds, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) {\n    return appendCommand(commandObjects.hexpire(key, seconds, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpire(byte[] key, long milliseconds, byte[]... fields) {\n    return appendCommand(commandObjects.hpexpire(key, milliseconds, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) {\n    return appendCommand(commandObjects.hpexpire(key, milliseconds, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) {\n    return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) {\n    return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) {\n    return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) {\n    return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hexpireTime(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.hexpireTime(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpexpireTime(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.hpexpireTime(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> httl(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.httl(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpttl(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.hpttl(key, fields));\n  }\n\n  @Override\n  public Response<List<Long>> hpersist(byte[] key, byte[]... fields) {\n    return appendCommand(commandObjects.hpersist(key, fields));\n  }\n\n  @Override\n  public Response<Long> pfadd(byte[] key, byte[]... elements) {\n    return appendCommand(commandObjects.pfadd(key, elements));\n  }\n\n  @Override\n  public Response<String> pfmerge(byte[] destkey, byte[]... sourcekeys) {\n    return appendCommand(commandObjects.pfmerge(destkey, sourcekeys));\n  }\n\n  @Override\n  public Response<Long> pfcount(byte[] key) {\n    return appendCommand(commandObjects.pfcount(key));\n  }\n\n  @Override\n  public Response<Long> pfcount(byte[]... keys) {\n    return appendCommand(commandObjects.pfcount(keys));\n  }\n\n  @Override\n  public Response<Boolean> exists(byte[] key) {\n    return appendCommand(commandObjects.exists(key));\n  }\n\n  @Override\n  public Response<Long> exists(byte[]... keys) {\n    return appendCommand(commandObjects.exists(keys));\n  }\n\n  @Override\n  public Response<Long> persist(byte[] key) {\n    return appendCommand(commandObjects.persist(key));\n  }\n\n  @Override\n  public Response<String> type(byte[] key) {\n    return appendCommand(commandObjects.type(key));\n  }\n\n  @Override\n  public Response<byte[]> dump(byte[] key) {\n    return appendCommand(commandObjects.dump(key));\n  }\n\n  @Override\n  public Response<String> restore(byte[] key, long ttl, byte[] serializedValue) {\n    return appendCommand(commandObjects.restore(key, ttl, serializedValue));\n  }\n\n  @Override\n  public Response<String> restore(byte[] key, long ttl, byte[] serializedValue, RestoreParams params) {\n    return appendCommand(commandObjects.restore(key, ttl, serializedValue, params));\n  }\n\n  @Override\n  public Response<Long> expire(byte[] key, long seconds) {\n    return appendCommand(commandObjects.expire(key, seconds));\n  }\n\n  @Override\n  public Response<Long> expire(byte[] key, long seconds, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.expire(key, seconds, expiryOption));\n  }\n\n  @Override\n  public Response<Long> pexpire(byte[] key, long milliseconds) {\n    return appendCommand(commandObjects.pexpire(key, milliseconds));\n  }\n\n  @Override\n  public Response<Long> pexpire(byte[] key, long milliseconds, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.pexpire(key, milliseconds, expiryOption));\n  }\n\n  @Override\n  public Response<Long> expireTime(byte[] key) {\n    return appendCommand(commandObjects.expireTime(key));\n  }\n\n  @Override\n  public Response<Long> pexpireTime(byte[] key) {\n    return appendCommand(commandObjects.pexpireTime(key));\n  }\n\n  @Override\n  public Response<Long> expireAt(byte[] key, long unixTime) {\n    return appendCommand(commandObjects.expireAt(key, unixTime));\n  }\n\n  @Override\n  public Response<Long> expireAt(byte[] key, long unixTime, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.expireAt(key, unixTime, expiryOption));\n  }\n\n  @Override\n  public Response<Long> pexpireAt(byte[] key, long millisecondsTimestamp) {\n    return appendCommand(commandObjects.pexpireAt(key, millisecondsTimestamp));\n  }\n\n  @Override\n  public Response<Long> pexpireAt(byte[] key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    return appendCommand(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption));\n  }\n\n  @Override\n  public Response<Long> ttl(byte[] key) {\n    return appendCommand(commandObjects.ttl(key));\n  }\n\n  @Override\n  public Response<Long> pttl(byte[] key) {\n    return appendCommand(commandObjects.pttl(key));\n  }\n\n  @Override\n  public Response<Long> touch(byte[] key) {\n    return appendCommand(commandObjects.touch(key));\n  }\n\n  @Override\n  public Response<Long> touch(byte[]... keys) {\n    return appendCommand(commandObjects.touch(keys));\n  }\n\n  @Override\n  public Response<List<byte[]>> sort(byte[] key) {\n    return appendCommand(commandObjects.sort(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> sort(byte[] key, SortingParams sortingParams) {\n    return appendCommand(commandObjects.sort(key, sortingParams));\n  }\n\n  @Override\n  public Response<List<byte[]>> sortReadonly(byte[] key, SortingParams sortingParams) {\n    return appendCommand(commandObjects.sortReadonly(key, sortingParams));\n  }\n\n  @Override\n  public Response<Long> del(byte[] key) {\n    return appendCommand(commandObjects.del(key));\n  }\n\n  @Override\n  public Response<Long> del(byte[]... keys) {\n    return appendCommand(commandObjects.del(keys));\n  }\n\n  @Override\n  public Response<Long> delex(byte[] key, CompareCondition condition) {\n    return appendCommand(commandObjects.delex(key, condition));\n  }\n\n  @Override\n  public Response<Long> delex(String key, CompareCondition condition) {\n    return appendCommand(commandObjects.delex(key, condition));\n  }\n\n  @Override\n  public Response<byte[]> digestKey(byte[] key) {\n    return appendCommand(commandObjects.digestKey(key));\n  }\n\n  @Override\n  public Response<String> digestKey(String key) {\n    return appendCommand(commandObjects.digestKey(key));\n  }\n\n  @Override\n  public Response<Long> unlink(byte[] key) {\n    return appendCommand(commandObjects.unlink(key));\n  }\n\n  @Override\n  public Response<Long> unlink(byte[]... keys) {\n    return appendCommand(commandObjects.unlink(keys));\n  }\n\n  @Override\n  public Response<Boolean> copy(byte[] srcKey, byte[] dstKey, boolean replace) {\n    return appendCommand(commandObjects.copy(srcKey, dstKey, replace));\n  }\n\n  @Override\n  public Response<String> rename(byte[] oldkey, byte[] newkey) {\n    return appendCommand(commandObjects.rename(oldkey, newkey));\n  }\n\n  @Override\n  public Response<Long> renamenx(byte[] oldkey, byte[] newkey) {\n    return appendCommand(commandObjects.renamenx(oldkey, newkey));\n  }\n\n  @Override\n  public Response<Long> sort(byte[] key, SortingParams sortingParams, byte[] dstkey) {\n    return appendCommand(commandObjects.sort(key, sortingParams, dstkey));\n  }\n\n  @Override\n  public Response<Long> sort(byte[] key, byte[] dstkey) {\n    return appendCommand(commandObjects.sort(key, dstkey));\n  }\n\n  @Override\n  public Response<Long> memoryUsage(byte[] key) {\n    return appendCommand(commandObjects.memoryUsage(key));\n  }\n\n  @Override\n  public Response<Long> memoryUsage(byte[] key, int samples) {\n    return appendCommand(commandObjects.memoryUsage(key, samples));\n  }\n\n  @Override\n  public Response<Long> objectRefcount(byte[] key) {\n    return appendCommand(commandObjects.objectRefcount(key));\n  }\n\n  @Override\n  public Response<byte[]> objectEncoding(byte[] key) {\n    return appendCommand(commandObjects.objectEncoding(key));\n  }\n\n  @Override\n  public Response<Long> objectIdletime(byte[] key) {\n    return appendCommand(commandObjects.objectIdletime(key));\n  }\n\n  @Override\n  public Response<Long> objectFreq(byte[] key) {\n    return appendCommand(commandObjects.objectFreq(key));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, byte[] key, int timeout) {\n    return appendCommand(commandObjects.migrate(host, port, key, timeout));\n  }\n\n  @Override\n  public Response<String> migrate(String host, int port, int timeout, MigrateParams params, byte[]... keys) {\n    return appendCommand(commandObjects.migrate(host, port, timeout, params, keys));\n  }\n\n  @Override\n  public Response<Set<byte[]>> keys(byte[] pattern) {\n    return appendCommand(commandObjects.keys(pattern));\n  }\n\n  @Override\n  public Response<ScanResult<byte[]>> scan(byte[] cursor) {\n    return appendCommand(commandObjects.scan(cursor));\n  }\n\n  @Override\n  public Response<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params) {\n    return appendCommand(commandObjects.scan(cursor, params));\n  }\n\n  @Override\n  public Response<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params, byte[] type) {\n    return appendCommand(commandObjects.scan(cursor, params, type));\n  }\n\n  @Override\n  public Response<byte[]> randomBinaryKey() {\n    return appendCommand(commandObjects.randomBinaryKey());\n  }\n\n  @Override\n  public Response<Long> rpush(byte[] key, byte[]... args) {\n    return appendCommand(commandObjects.rpush(key, args));\n  }\n\n  @Override\n  public Response<Long> lpush(byte[] key, byte[]... args) {\n    return appendCommand(commandObjects.lpush(key, args));\n  }\n\n  @Override\n  public Response<Long> llen(byte[] key) {\n    return appendCommand(commandObjects.llen(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> lrange(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.lrange(key, start, stop));\n  }\n\n  @Override\n  public Response<String> ltrim(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.ltrim(key, start, stop));\n  }\n\n  @Override\n  public Response<byte[]> lindex(byte[] key, long index) {\n    return appendCommand(commandObjects.lindex(key, index));\n  }\n\n  @Override\n  public Response<String> lset(byte[] key, long index, byte[] value) {\n    return appendCommand(commandObjects.lset(key, index, value));\n  }\n\n  @Override\n  public Response<Long> lrem(byte[] key, long count, byte[] value) {\n    return appendCommand(commandObjects.lrem(key, count, value));\n  }\n\n  @Override\n  public Response<byte[]> lpop(byte[] key) {\n    return appendCommand(commandObjects.lpop(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> lpop(byte[] key, int count) {\n    return appendCommand(commandObjects.lpop(key, count));\n  }\n\n  @Override\n  public Response<Long> lpos(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.lpos(key, element));\n  }\n\n  @Override\n  public Response<Long> lpos(byte[] key, byte[] element, LPosParams params) {\n    return appendCommand(commandObjects.lpos(key, element, params));\n  }\n\n  @Override\n  public Response<List<Long>> lpos(byte[] key, byte[] element, LPosParams params, long count) {\n    return appendCommand(commandObjects.lpos(key, element, params, count));\n  }\n\n  @Override\n  public Response<byte[]> rpop(byte[] key) {\n    return appendCommand(commandObjects.rpop(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> rpop(byte[] key, int count) {\n    return appendCommand(commandObjects.rpop(key, count));\n  }\n\n  @Override\n  public Response<Long> linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value) {\n    return appendCommand(commandObjects.linsert(key, where, pivot, value));\n  }\n\n  @Override\n  public Response<Long> lpushx(byte[] key, byte[]... args) {\n    return appendCommand(commandObjects.lpushx(key, args));\n  }\n\n  @Override\n  public Response<Long> rpushx(byte[] key, byte[]... args) {\n    return appendCommand(commandObjects.rpushx(key, args));\n  }\n\n  @Override\n  public Response<List<byte[]>> blpop(int timeout, byte[]... keys) {\n    return appendCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], byte[]>> blpop(double timeout, byte[]... keys) {\n    return appendCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public Response<List<byte[]>> brpop(int timeout, byte[]... keys) {\n    return appendCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], byte[]>> brpop(double timeout, byte[]... keys) {\n    return appendCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#lmove(byte[], byte[], ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public Response<byte[]> rpoplpush(byte[] srckey, byte[] dstkey) {\n    return appendCommand(commandObjects.rpoplpush(srckey, dstkey));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#blmove(byte[], byte[], ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public Response<byte[]> brpoplpush(byte[] source, byte[] destination, int timeout) {\n    return appendCommand(commandObjects.brpoplpush(source, destination, timeout));\n  }\n\n  @Override\n  public Response<byte[]> lmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to) {\n    return appendCommand(commandObjects.lmove(srcKey, dstKey, from, to));\n  }\n\n  @Override\n  public Response<byte[]> blmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to, double timeout) {\n    return appendCommand(commandObjects.blmove(srcKey, dstKey, from, to, timeout));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<byte[]>>> lmpop(ListDirection direction, byte[]... keys) {\n    return appendCommand(commandObjects.lmpop(direction, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<byte[]>>> lmpop(ListDirection direction, int count, byte[]... keys) {\n    return appendCommand(commandObjects.lmpop(direction, count, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<byte[]>>> blmpop(double timeout, ListDirection direction, byte[]... keys) {\n    return appendCommand(commandObjects.blmpop(timeout, direction, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<byte[]>>> blmpop(double timeout, ListDirection direction, int count, byte[]... keys) {\n    return appendCommand(commandObjects.blmpop(timeout, direction, count, keys));\n  }\n\n  @Override\n  public Response<Long> waitReplicas(byte[] sampleKey, int replicas, long timeout) {\n    return appendCommand(commandObjects.waitReplicas(sampleKey, replicas, timeout));\n  }\n\n  @Override\n  public Response<KeyValue<Long, Long>> waitAOF(byte[] sampleKey, long numLocal, long numReplicas, long timeout) {\n    return appendCommand(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout));\n  }\n\n  @Override\n  public Response<Object> eval(byte[] script, byte[] sampleKey) {\n    return appendCommand(commandObjects.eval(script, sampleKey));\n  }\n\n  @Override\n  public Response<Object> evalsha(byte[] sha1, byte[] sampleKey) {\n    return appendCommand(commandObjects.evalsha(sha1, sampleKey));\n  }\n\n  @Override\n  public Response<List<Boolean>> scriptExists(byte[] sampleKey, byte[]... sha1s) {\n    return appendCommand(commandObjects.scriptExists(sampleKey, sha1s));\n  }\n\n  @Override\n  public Response<byte[]> scriptLoad(byte[] script, byte[] sampleKey) {\n    return appendCommand(commandObjects.scriptLoad(script, sampleKey));\n  }\n\n  @Override\n  public Response<String> scriptFlush(byte[] sampleKey) {\n    return appendCommand(commandObjects.scriptFlush(sampleKey));\n  }\n\n  @Override\n  public Response<String> scriptFlush(byte[] sampleKey, FlushMode flushMode) {\n    return appendCommand(commandObjects.scriptFlush(sampleKey, flushMode));\n  }\n\n  @Override\n  public Response<String> scriptKill(byte[] sampleKey) {\n    return appendCommand(commandObjects.scriptKill(sampleKey));\n  }\n\n  @Override\n  public Response<Object> eval(byte[] script) {\n    return appendCommand(commandObjects.eval(script));\n  }\n\n  @Override\n  public Response<Object> eval(byte[] script, int keyCount, byte[]... params) {\n    return appendCommand(commandObjects.eval(script, keyCount, params));\n  }\n\n  @Override\n  public Response<Object> eval(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    return appendCommand(commandObjects.eval(script, keys, args));\n  }\n\n  @Override\n  public Response<Object> evalReadonly(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    return appendCommand(commandObjects.evalReadonly(script, keys, args));\n  }\n\n  @Override\n  public Response<Object> evalsha(byte[] sha1) {\n    return appendCommand(commandObjects.evalsha(sha1));\n  }\n\n  @Override\n  public Response<Object> evalsha(byte[] sha1, int keyCount, byte[]... params) {\n    return appendCommand(commandObjects.evalsha(sha1, keyCount, params));\n  }\n\n  @Override\n  public Response<Object> evalsha(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    return appendCommand(commandObjects.evalsha(sha1, keys, args));\n  }\n\n  @Override\n  public Response<Object> evalshaReadonly(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    return appendCommand(commandObjects.evalshaReadonly(sha1, keys, args));\n  }\n\n  @Override\n  public Response<Long> sadd(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.sadd(key, members));\n  }\n\n  @Override\n  public Response<Set<byte[]>> smembers(byte[] key) {\n    return appendCommand(commandObjects.smembers(key));\n  }\n\n  @Override\n  public Response<Long> srem(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.srem(key, members));\n  }\n\n  @Override\n  public Response<byte[]> spop(byte[] key) {\n    return appendCommand(commandObjects.spop(key));\n  }\n\n  @Override\n  public Response<Set<byte[]>> spop(byte[] key, long count) {\n    return appendCommand(commandObjects.spop(key, count));\n  }\n\n  @Override\n  public Response<Long> scard(byte[] key) {\n    return appendCommand(commandObjects.scard(key));\n  }\n\n  @Override\n  public Response<Boolean> sismember(byte[] key, byte[] member) {\n    return appendCommand(commandObjects.sismember(key, member));\n  }\n\n  @Override\n  public Response<List<Boolean>> smismember(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.smismember(key, members));\n  }\n\n  @Override\n  public Response<byte[]> srandmember(byte[] key) {\n    return appendCommand(commandObjects.srandmember(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> srandmember(byte[] key, int count) {\n    return appendCommand(commandObjects.srandmember(key, count));\n  }\n\n  @Override\n  public Response<ScanResult<byte[]>> sscan(byte[] key, byte[] cursor, ScanParams params) {\n    return appendCommand(commandObjects.sscan(key, cursor, params));\n  }\n\n  @Override\n  public Response<Set<byte[]>> sdiff(byte[]... keys) {\n    return appendCommand(commandObjects.sdiff(keys));\n  }\n\n  @Override\n  public Response<Long> sdiffstore(byte[] dstkey, byte[]... keys) {\n    return appendCommand(commandObjects.sdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public Response<Set<byte[]>> sinter(byte[]... keys) {\n    return appendCommand(commandObjects.sinter(keys));\n  }\n\n  @Override\n  public Response<Long> sinterstore(byte[] dstkey, byte[]... keys) {\n    return appendCommand(commandObjects.sinterstore(dstkey, keys));\n  }\n\n  @Override\n  public Response<Long> sintercard(byte[]... keys) {\n    return appendCommand(commandObjects.sintercard(keys));\n  }\n\n  @Override\n  public Response<Long> sintercard(int limit, byte[]... keys) {\n    return appendCommand(commandObjects.sintercard(limit, keys));\n  }\n\n  @Override\n  public Response<Set<byte[]>> sunion(byte[]... keys) {\n    return appendCommand(commandObjects.sunion(keys));\n  }\n\n  @Override\n  public Response<Long> sunionstore(byte[] dstkey, byte[]... keys) {\n    return appendCommand(commandObjects.sunionstore(dstkey, keys));\n  }\n\n  @Override\n  public Response<Long> smove(byte[] srckey, byte[] dstkey, byte[] member) {\n    return appendCommand(commandObjects.smove(srckey, dstkey, member));\n  }\n\n  @Override\n  public Response<Long> zadd(byte[] key, double score, byte[] member) {\n    return appendCommand(commandObjects.zadd(key, score, member));\n  }\n\n  @Override\n  public Response<Long> zadd(byte[] key, double score, byte[] member, ZAddParams params) {\n    return appendCommand(commandObjects.zadd(key, score, member, params));\n  }\n\n  @Override\n  public Response<Long> zadd(byte[] key, Map<byte[], Double> scoreMembers) {\n    return appendCommand(commandObjects.zadd(key, scoreMembers));\n  }\n\n  @Override\n  public Response<Long> zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params) {\n    return appendCommand(commandObjects.zadd(key, scoreMembers, params));\n  }\n\n  @Override\n  public Response<Double> zaddIncr(byte[] key, double score, byte[] member, ZAddParams params) {\n    return appendCommand(commandObjects.zaddIncr(key, score, member, params));\n  }\n\n  @Override\n  public Response<Long> zrem(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.zrem(key, members));\n  }\n\n  @Override\n  public Response<Double> zincrby(byte[] key, double increment, byte[] member) {\n    return appendCommand(commandObjects.zincrby(key, increment, member));\n  }\n\n  @Override\n  public Response<Double> zincrby(byte[] key, double increment, byte[] member, ZIncrByParams params) {\n    return appendCommand(commandObjects.zincrby(key, increment, member, params));\n  }\n\n  @Override\n  public Response<Long> zrank(byte[] key, byte[] member) {\n    return appendCommand(commandObjects.zrank(key, member));\n  }\n\n  @Override\n  public Response<Long> zrevrank(byte[] key, byte[] member) {\n    return appendCommand(commandObjects.zrevrank(key, member));\n  }\n\n  @Override\n  public Response<KeyValue<Long, Double>> zrankWithScore(byte[] key, byte[] member) {\n    return appendCommand(commandObjects.zrankWithScore(key, member));\n  }\n\n  @Override\n  public Response<KeyValue<Long, Double>> zrevrankWithScore(byte[] key, byte[] member) {\n    return appendCommand(commandObjects.zrevrankWithScore(key, member));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrange(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.zrange(key, start, stop));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrange(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.zrevrange(key, start, stop));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeWithScores(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.zrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeWithScores(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.zrevrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public Response<byte[]> zrandmember(byte[] key) {\n    return appendCommand(commandObjects.zrandmember(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrandmember(byte[] key, long count) {\n    return appendCommand(commandObjects.zrandmember(key, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrandmemberWithScores(byte[] key, long count) {\n    return appendCommand(commandObjects.zrandmemberWithScores(key, count));\n  }\n\n  @Override\n  public Response<Long> zcard(byte[] key) {\n    return appendCommand(commandObjects.zcard(key));\n  }\n\n  @Override\n  public Response<Double> zscore(byte[] key, byte[] member) {\n    return appendCommand(commandObjects.zscore(key, member));\n  }\n\n  @Override\n  public Response<List<Double>> zmscore(byte[] key, byte[]... members) {\n    return appendCommand(commandObjects.zmscore(key, members));\n  }\n\n  @Override\n  public Response<Tuple> zpopmax(byte[] key) {\n    return appendCommand(commandObjects.zpopmax(key));\n  }\n\n  @Override\n  public Response<List<Tuple>> zpopmax(byte[] key, int count) {\n    return appendCommand(commandObjects.zpopmax(key, count));\n  }\n\n  @Override\n  public Response<Tuple> zpopmin(byte[] key) {\n    return appendCommand(commandObjects.zpopmin(key));\n  }\n\n  @Override\n  public Response<List<Tuple>> zpopmin(byte[] key, int count) {\n    return appendCommand(commandObjects.zpopmin(key, count));\n  }\n\n  @Override\n  public Response<Long> zcount(byte[] key, double min, double max) {\n    return appendCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public Response<Long> zcount(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrangeByScore(byte[] key, double min, double max) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrangeByScore(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrangeByScore(byte[] key, double max, double min) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrangeByScore(byte[] key, double min, double max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrangeByScore(byte[] key, byte[] max, byte[] min) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrangeByScore(byte[] key, double max, double min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, double min, double max) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, double max, double min) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<Long> zremrangeByRank(byte[] key, long start, long stop) {\n    return appendCommand(commandObjects.zremrangeByRank(key, start, stop));\n  }\n\n  @Override\n  public Response<Long> zremrangeByScore(byte[] key, double min, double max) {\n    return appendCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<Long> zremrangeByScore(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public Response<Long> zlexcount(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zlexcount(key, min, max));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrangeByLex(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zrangeByLex(key, min, max));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return appendCommand(commandObjects.zrangeByLex(key, min, max, offset, count));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrangeByLex(byte[] key, byte[] max, byte[] min) {\n    return appendCommand(commandObjects.zrevrangeByLex(key, max, min));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return appendCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n  }\n\n  @Override\n  public Response<List<byte[]>> zrange(byte[] key, ZRangeParams zRangeParams) {\n    return appendCommand(commandObjects.zrange(key, zRangeParams));\n  }\n\n  @Override\n  public Response<List<Tuple>> zrangeWithScores(byte[] key, ZRangeParams zRangeParams) {\n    return appendCommand(commandObjects.zrangeWithScores(key, zRangeParams));\n  }\n\n  @Override\n  public Response<Long> zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams) {\n    return appendCommand(commandObjects.zrangestore(dest, src, zRangeParams));\n  }\n\n  @Override\n  public Response<Long> zremrangeByLex(byte[] key, byte[] min, byte[] max) {\n    return appendCommand(commandObjects.zremrangeByLex(key, min, max));\n  }\n\n  @Override\n  public Response<ScanResult<Tuple>> zscan(byte[] key, byte[] cursor, ScanParams params) {\n    return appendCommand(commandObjects.zscan(key, cursor, params));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], Tuple>> bzpopmax(double timeout, byte[]... keys) {\n    return appendCommand(commandObjects.bzpopmax(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], Tuple>> bzpopmin(double timeout, byte[]... keys) {\n    return appendCommand(commandObjects.bzpopmin(timeout, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<Tuple>>> zmpop(SortedSetOption option, byte[]... keys) {\n    return appendCommand(commandObjects.zmpop(option, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<Tuple>>> zmpop(SortedSetOption option, int count, byte[]... keys) {\n    return appendCommand(commandObjects.zmpop(option, count, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<Tuple>>> bzmpop(double timeout, SortedSetOption option, byte[]... keys) {\n    return appendCommand(commandObjects.bzmpop(timeout, option, keys));\n  }\n\n  @Override\n  public Response<KeyValue<byte[], List<Tuple>>> bzmpop(double timeout, SortedSetOption option, int count, byte[]... keys) {\n    return appendCommand(commandObjects.bzmpop(timeout, option, count, keys));\n  }\n\n  @Override\n  public Response<List<byte[]>> zdiff(byte[]... keys) {\n    return appendCommand(commandObjects.zdiff(keys));\n  }\n\n  @Override\n  public Response<List<Tuple>> zdiffWithScores(byte[]... keys) {\n    return appendCommand(commandObjects.zdiffWithScores(keys));\n  }\n\n  @Override\n  @Deprecated\n  public Response<Long> zdiffStore(byte[] dstkey, byte[]... keys) {\n    return appendCommand(commandObjects.zdiffStore(dstkey, keys));\n  }\n\n  @Override\n  public Response<Long> zdiffstore(byte[] dstkey, byte[]... keys) {\n    return appendCommand(commandObjects.zdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public Response<List<byte[]>> zinter(ZParams params, byte[]... keys) {\n    return appendCommand(commandObjects.zinter(params, keys));\n  }\n\n  @Override\n  public Response<List<Tuple>> zinterWithScores(ZParams params, byte[]... keys) {\n    return appendCommand(commandObjects.zinterWithScores(params, keys));\n  }\n\n  @Override\n  public Response<Long> zinterstore(byte[] dstkey, byte[]... sets) {\n    return appendCommand(commandObjects.zinterstore(dstkey, sets));\n  }\n\n  @Override\n  public Response<Long> zinterstore(byte[] dstkey, ZParams params, byte[]... sets) {\n    return appendCommand(commandObjects.zinterstore(dstkey, params, sets));\n  }\n\n  @Override\n  public Response<Long> zintercard(byte[]... keys) {\n    return appendCommand(commandObjects.zintercard(keys));\n  }\n\n  @Override\n  public Response<Long> zintercard(long limit, byte[]... keys) {\n    return appendCommand(commandObjects.zintercard(limit, keys));\n  }\n\n  @Override\n  public Response<List<byte[]>> zunion(ZParams params, byte[]... keys) {\n    return appendCommand(commandObjects.zunion(params, keys));\n  }\n\n  @Override\n  public Response<List<Tuple>> zunionWithScores(ZParams params, byte[]... keys) {\n    return appendCommand(commandObjects.zunionWithScores(params, keys));\n  }\n\n  @Override\n  public Response<Long> zunionstore(byte[] dstkey, byte[]... sets) {\n    return appendCommand(commandObjects.zunionstore(dstkey, sets));\n  }\n\n  @Override\n  public Response<Long> zunionstore(byte[] dstkey, ZParams params, byte[]... sets) {\n    return appendCommand(commandObjects.zunionstore(dstkey, params, sets));\n  }\n\n  @Override\n  public Response<byte[]> xadd(byte[] key, XAddParams params, Map<byte[], byte[]> hash) {\n    return appendCommand(commandObjects.xadd(key, params, hash));\n  }\n\n  @Override\n  public Response<Long> xlen(byte[] key) {\n    return appendCommand(commandObjects.xlen(key));\n  }\n\n  @Override\n  public Response<List<Object>> xrange(byte[] key, byte[] start, byte[] end) {\n    return appendCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public Response<List<Object>> xrange(byte[] key, byte[] start, byte[] end, int count) {\n    return appendCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public Response<List<Object>> xrevrange(byte[] key, byte[] end, byte[] start) {\n    return appendCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public Response<List<Object>> xrevrange(byte[] key, byte[] end, byte[] start, int count) {\n    return appendCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public Response<Long> xack(byte[] key, byte[] group, byte[]... ids) {\n    return appendCommand(commandObjects.xack(key, group, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xackdel(byte[] key, byte[] group, byte[]... ids) {\n    return appendCommand(commandObjects.xackdel(key, group, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) {\n    return appendCommand(commandObjects.xackdel(key, group, trimMode, ids));\n  }\n\n  @Override\n  public Response<String> xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream) {\n    return appendCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream));\n  }\n\n  @Override\n  public Response<String> xgroupSetID(byte[] key, byte[] groupName, byte[] id) {\n    return appendCommand(commandObjects.xgroupSetID(key, groupName, id));\n  }\n\n  @Override\n  public Response<Long> xgroupDestroy(byte[] key, byte[] groupName) {\n    return appendCommand(commandObjects.xgroupDestroy(key, groupName));\n  }\n\n  @Override\n  public Response<Boolean> xgroupCreateConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    return appendCommand(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public Response<Long> xgroupDelConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    return appendCommand(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public Response<Long> xdel(byte[] key, byte[]... ids) {\n    return appendCommand(commandObjects.xdel(key, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xdelex(byte[] key, byte[]... ids) {\n    return appendCommand(commandObjects.xdelex(key, ids));\n  }\n\n  @Override\n  public Response<List<StreamEntryDeletionResult>> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) {\n    return appendCommand(commandObjects.xdelex(key, trimMode, ids));\n  }\n\n  @Override\n  public Response<Long> xtrim(byte[] key, long maxLen, boolean approximateLength) {\n    return appendCommand(commandObjects.xtrim(key, maxLen, approximateLength));\n  }\n\n  @Override\n  public Response<Long> xtrim(byte[] key, XTrimParams params) {\n    return appendCommand(commandObjects.xtrim(key, params));\n  }\n\n  @Override\n  public Response<Object> xpending(byte[] key, byte[] groupName) {\n    return appendCommand(commandObjects.xpending(key, groupName));\n  }\n\n  @Override\n  public Response<List<Object>> xpending(byte[] key, byte[] groupName, XPendingParams params) {\n    return appendCommand(commandObjects.xpending(key, groupName, params));\n  }\n\n  @Override\n  public Response<List<byte[]>> xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) {\n    return appendCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public Response<List<byte[]>> xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) {\n    return appendCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public Response<List<Object>> xautoclaim(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) {\n    return appendCommand(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Response<List<Object>> xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) {\n    return appendCommand(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Response<Object> xinfoStream(byte[] key) {\n    return appendCommand(commandObjects.xinfoStream(key));\n  }\n\n  @Override\n  public Response<Object> xinfoStreamFull(byte[] key) {\n    return appendCommand(commandObjects.xinfoStreamFull(key));\n  }\n\n  @Override\n  public Response<Object> xinfoStreamFull(byte[] key, int count) {\n    return appendCommand(commandObjects.xinfoStreamFull(key, count));\n  }\n\n  @Override\n  public Response<List<Object>> xinfoGroups(byte[] key) {\n    return appendCommand(commandObjects.xinfoGroups(key));\n  }\n\n  @Override\n  public Response<List<Object>> xinfoConsumers(byte[] key, byte[] group) {\n    return appendCommand(commandObjects.xinfoConsumers(key, group));\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use {@link #xreadBinary(XReadParams, Map)} or\n   *     {@link #xreadBinaryAsMap(XReadParams, Map)} for type safety and better stream entry\n   *     parsing.\n   */\n  @Deprecated\n  @Override\n  public Response<List<Object>> xread(XReadParams xReadParams,\n      Map.Entry<byte[], byte[]>... streams) {\n    return appendCommand(commandObjects.xread(xReadParams, streams));\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use\n   *     {@link #xreadGroupBinary(byte[], byte[], XReadGroupParams, Map)} or\n   *     {@link #xreadGroupBinaryAsMap(byte[], byte[], XReadGroupParams, Map)} instead.\n   */\n  @Deprecated\n  @Override\n  public Response<List<Object>> xreadGroup(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map.Entry<byte[], byte[]>... streams) {\n    return appendCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Response<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadBinary(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams) {\n    return appendCommand(commandObjects.xreadBinary(xReadParams, streams));\n  }\n\n  @Override\n  public Response<Map<byte[], List<StreamEntryBinary>>> xreadBinaryAsMap(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams) {\n    return appendCommand(commandObjects.xreadBinaryAsMap(xReadParams, streams));\n  }\n\n  @Override\n  public Response<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadGroupBinary(byte[] groupName,\n      byte[] consumer, XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams) {\n    return appendCommand(\n        commandObjects.xreadGroupBinary(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Response<Map<byte[], List<StreamEntryBinary>>> xreadGroupBinaryAsMap(byte[] groupName,\n      byte[] consumer, XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams) {\n    return appendCommand(\n        commandObjects.xreadGroupBinaryAsMap(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Response<byte[]> xcfgset(byte[] key, XCfgSetParams params) {\n    return appendCommand(commandObjects.xcfgset(key, params));\n  }\n\n  @Override\n  public Response<String> set(byte[] key, byte[] value) {\n    return appendCommand(commandObjects.set(key, value));\n  }\n\n  @Override\n  public Response<String> set(byte[] key, byte[] value, SetParams params) {\n    return appendCommand(commandObjects.set(key, value, params));\n  }\n\n  @Override\n  public Response<byte[]> get(byte[] key) {\n    return appendCommand(commandObjects.get(key));\n  }\n\n  @Override\n  public Response<byte[]> setGet(byte[] key, byte[] value) {\n    return appendCommand(commandObjects.setGet(key, value));\n  }\n\n  @Override\n  public Response<byte[]> setGet(byte[] key, byte[] value, SetParams params) {\n    return appendCommand(commandObjects.setGet(key, value, params));\n  }\n\n  @Override\n  public Response<byte[]> getDel(byte[] key) {\n    return appendCommand(commandObjects.getDel(key));\n  }\n\n  @Override\n  public Response<byte[]> getEx(byte[] key, GetExParams params) {\n    return appendCommand(commandObjects.getEx(key, params));\n  }\n\n  @Override\n  public Response<Boolean> setbit(byte[] key, long offset, boolean value) {\n    return appendCommand(commandObjects.setbit(key, offset, value));\n  }\n\n  @Override\n  public Response<Boolean> getbit(byte[] key, long offset) {\n    return appendCommand(commandObjects.getbit(key, offset));\n  }\n\n  @Override\n  public Response<Long> setrange(byte[] key, long offset, byte[] value) {\n    return appendCommand(commandObjects.setrange(key, offset, value));\n  }\n\n  @Override\n  public Response<byte[]> getrange(byte[] key, long startOffset, long endOffset) {\n    return appendCommand(commandObjects.getrange(key, startOffset, endOffset));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#setGet(byte[], byte[])}.\n   */\n  @Deprecated\n  @Override\n  public Response<byte[]> getSet(byte[] key, byte[] value) {\n    return appendCommand(commandObjects.getSet(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public Response<Long> setnx(byte[] key, byte[] value) {\n    return appendCommand(commandObjects.setnx(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public Response<String> setex(byte[] key, long seconds, byte[] value) {\n    return appendCommand(commandObjects.setex(key, seconds, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public Response<String> psetex(byte[] key, long milliseconds, byte[] value) {\n    return appendCommand(commandObjects.psetex(key, milliseconds, value));\n  }\n\n  @Override\n  public Response<List<byte[]>> mget(byte[]... keys) {\n    return appendCommand(commandObjects.mget(keys));\n  }\n\n  @Override\n  public Response<String> mset(byte[]... keysvalues) {\n    return appendCommand(commandObjects.mset(keysvalues));\n  }\n\n  @Override\n  public Response<Boolean> msetex(MSetExParams params, byte[]... keysvalues) {\n    return appendCommand(commandObjects.msetex(params, keysvalues));\n  }\n\n  @Override\n  public Response<Long> msetnx(byte[]... keysvalues) {\n    return appendCommand(commandObjects.msetnx(keysvalues));\n  }\n\n  @Override\n  public Response<Long> incr(byte[] key) {\n    return appendCommand(commandObjects.incr(key));\n  }\n\n  @Override\n  public Response<Long> incrBy(byte[] key, long increment) {\n    return appendCommand(commandObjects.incrBy(key, increment));\n  }\n\n  @Override\n  public Response<Double> incrByFloat(byte[] key, double increment) {\n    return appendCommand(commandObjects.incrByFloat(key, increment));\n  }\n\n  @Override\n  public Response<Long> decr(byte[] key) {\n    return appendCommand(commandObjects.decr(key));\n  }\n\n  @Override\n  public Response<Long> decrBy(byte[] key, long decrement) {\n    return appendCommand(commandObjects.decrBy(key, decrement));\n  }\n\n  @Override\n  public Response<Long> append(byte[] key, byte[] value) {\n    return appendCommand(commandObjects.append(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link PipeliningBase#getrange(byte[], long, long)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  @Override\n  public Response<byte[]> substr(byte[] key, int start, int end) {\n    return appendCommand(commandObjects.substr(key, start, end));\n  }\n\n  @Override\n  public Response<Long> strlen(byte[] key) {\n    return appendCommand(commandObjects.strlen(key));\n  }\n\n  @Override\n  public Response<Long> bitcount(byte[] key) {\n    return appendCommand(commandObjects.bitcount(key));\n  }\n\n  @Override\n  public Response<Long> bitcount(byte[] key, long start, long end) {\n    return appendCommand(commandObjects.bitcount(key, start, end));\n  }\n\n  @Override\n  public Response<Long> bitcount(byte[] key, long start, long end, BitCountOption option) {\n    return appendCommand(commandObjects.bitcount(key, start, end, option));\n  }\n\n  @Override\n  public Response<Long> bitpos(byte[] key, boolean value) {\n    return appendCommand(commandObjects.bitpos(key, value));\n  }\n\n  @Override\n  public Response<Long> bitpos(byte[] key, boolean value, BitPosParams params) {\n    return appendCommand(commandObjects.bitpos(key, value, params));\n  }\n\n  @Override\n  public Response<List<Long>> bitfield(byte[] key, byte[]... arguments) {\n    return appendCommand(commandObjects.bitfield(key, arguments));\n  }\n\n  @Override\n  public Response<List<Long>> bitfieldReadonly(byte[] key, byte[]... arguments) {\n    return appendCommand(commandObjects.bitfieldReadonly(key, arguments));\n  }\n\n  @Override\n  public Response<Long> bitop(BitOP op, byte[] destKey, byte[]... srcKeys) {\n    return appendCommand(commandObjects.bitop(op, destKey, srcKeys));\n  }\n\n  // RediSearch commands\n  @Override\n  public Response<String> ftCreate(String indexName, IndexOptions indexOptions, Schema schema) {\n    return appendCommand(commandObjects.ftCreate(indexName, indexOptions, schema));\n  }\n\n  @Override\n  public Response<String> ftCreate(String indexName, FTCreateParams createParams, Iterable<SchemaField> schemaFields) {\n    return appendCommand(commandObjects.ftCreate(indexName, createParams, schemaFields));\n  }\n\n  @Override\n  public Response<String> ftAlter(String indexName, Schema schema) {\n    return appendCommand(commandObjects.ftAlter(indexName, schema));\n  }\n\n  @Override\n  public Response<String> ftAlter(String indexName, Iterable<SchemaField> schemaFields) {\n    return appendCommand(commandObjects.ftAlter(indexName, schemaFields));\n  }\n\n  @Override\n  public Response<String> ftAliasAdd(String aliasName, String indexName) {\n    return appendCommand(commandObjects.ftAliasAdd(aliasName, indexName));\n  }\n\n  @Override\n  public Response<String> ftAliasUpdate(String aliasName, String indexName) {\n    return appendCommand(commandObjects.ftAliasUpdate(aliasName, indexName));\n  }\n\n  @Override\n  public Response<String> ftAliasDel(String aliasName) {\n    return appendCommand(commandObjects.ftAliasDel(aliasName));\n  }\n\n  @Override\n  public Response<String> ftDropIndex(String indexName) {\n    return appendCommand(commandObjects.ftDropIndex(indexName));\n  }\n\n  @Override\n  public Response<String> ftDropIndexDD(String indexName) {\n    return appendCommand(commandObjects.ftDropIndexDD(indexName));\n  }\n\n  @Override\n  public Response<SearchResult> ftSearch(String indexName, String query) {\n    return appendCommand(commandObjects.ftSearch(indexName, query));\n  }\n\n  @Override\n  public Response<SearchResult> ftSearch(String indexName, String query, FTSearchParams searchParams) {\n    return appendCommand(commandObjects.ftSearch(indexName, query, searchParams));\n  }\n\n  @Override\n  public Response<SearchResult> ftSearch(String indexName, Query query) {\n    return appendCommand(commandObjects.ftSearch(indexName, query));\n  }\n\n  @Override\n  @Deprecated\n  public Response<SearchResult> ftSearch(byte[] indexName, Query query) {\n    return appendCommand(commandObjects.ftSearch(indexName, query));\n  }\n\n  @Override\n  public Response<String> ftExplain(String indexName, Query query) {\n    return appendCommand(commandObjects.ftExplain(indexName, query));\n  }\n\n  @Override\n  public Response<List<String>> ftExplainCLI(String indexName, Query query) {\n    return appendCommand(commandObjects.ftExplainCLI(indexName, query));\n  }\n\n  @Override\n  public Response<AggregationResult> ftAggregate(String indexName, AggregationBuilder aggr) {\n    return appendCommand(commandObjects.ftAggregate(indexName, aggr));\n  }\n\n  @Override\n  @Experimental\n  public Response<HybridResult> ftHybrid(String indexName, FTHybridParams hybridParams) {\n    return appendCommand(commandObjects.ftHybrid(indexName, hybridParams));\n  }\n\n  @Override\n  public Response<String> ftSynUpdate(String indexName, String synonymGroupId, String... terms) {\n    return appendCommand(commandObjects.ftSynUpdate(indexName, synonymGroupId, terms));\n  }\n\n  @Override\n  public Response<Map<String, List<String>>> ftSynDump(String indexName) {\n    return appendCommand(commandObjects.ftSynDump(indexName));\n  }\n\n  @Override\n  public Response<Long> ftDictAdd(String dictionary, String... terms) {\n    return appendCommand(commandObjects.ftDictAdd(dictionary, terms));\n  }\n\n  @Override\n  public Response<Long> ftDictDel(String dictionary, String... terms) {\n    return appendCommand(commandObjects.ftDictDel(dictionary, terms));\n  }\n\n  @Override\n  public Response<Set<String>> ftDictDump(String dictionary) {\n    return appendCommand(commandObjects.ftDictDump(dictionary));\n  }\n\n  @Override\n  public Response<Long> ftDictAddBySampleKey(String indexName, String dictionary, String... terms) {\n    return appendCommand(commandObjects.ftDictAddBySampleKey(indexName, dictionary, terms));\n  }\n\n  @Override\n  public Response<Long> ftDictDelBySampleKey(String indexName, String dictionary, String... terms) {\n    return appendCommand(commandObjects.ftDictDelBySampleKey(indexName, dictionary, terms));\n  }\n\n  @Override\n  public Response<Set<String>> ftDictDumpBySampleKey(String indexName, String dictionary) {\n    return appendCommand(commandObjects.ftDictDumpBySampleKey(indexName, dictionary));\n  }\n\n  @Override\n  public Response<Map<String, Map<String, Double>>> ftSpellCheck(String index, String query) {\n    return appendCommand(commandObjects.ftSpellCheck(index, query));\n  }\n\n  @Override\n  public Response<Map<String, Map<String, Double>>> ftSpellCheck(String index, String query, FTSpellCheckParams spellCheckParams) {\n    return appendCommand(commandObjects.ftSpellCheck(index, query, spellCheckParams));\n  }\n\n  @Override\n  public Response<Map<String, Object>> ftInfo(String indexName) {\n    return appendCommand(commandObjects.ftInfo(indexName));\n  }\n\n  @Override\n  public Response<Set<String>> ftTagVals(String indexName, String fieldName) {\n    return appendCommand(commandObjects.ftTagVals(indexName, fieldName));\n  }\n\n  @Override\n  @Deprecated\n  public Response<Map<String, Object>> ftConfigGet(String option) {\n    return appendCommand(commandObjects.ftConfigGet(option));\n  }\n\n  @Override\n  @Deprecated\n  public Response<Map<String, Object>> ftConfigGet(String indexName, String option) {\n    return appendCommand(commandObjects.ftConfigGet(indexName, option));\n  }\n\n  @Override\n  @Deprecated\n  public Response<String> ftConfigSet(String option, String value) {\n    return appendCommand(commandObjects.ftConfigSet(option, value));\n  }\n\n  @Override\n  @Deprecated\n  public Response<String> ftConfigSet(String indexName, String option, String value) {\n    return appendCommand(commandObjects.ftConfigSet(indexName, option, value));\n  }\n\n  @Override\n  public Response<Long> ftSugAdd(String key, String string, double score) {\n    return appendCommand(commandObjects.ftSugAdd(key, string, score));\n  }\n\n  @Override\n  public Response<Long> ftSugAddIncr(String key, String string, double score) {\n    return appendCommand(commandObjects.ftSugAddIncr(key, string, score));\n  }\n\n  @Override\n  public Response<List<String>> ftSugGet(String key, String prefix) {\n    return appendCommand(commandObjects.ftSugGet(key, prefix));\n  }\n\n  @Override\n  public Response<List<String>> ftSugGet(String key, String prefix, boolean fuzzy, int max) {\n    return appendCommand(commandObjects.ftSugGet(key, prefix, fuzzy, max));\n  }\n\n  @Override\n  public Response<List<Tuple>> ftSugGetWithScores(String key, String prefix) {\n    return appendCommand(commandObjects.ftSugGetWithScores(key, prefix));\n  }\n\n  @Override\n  public Response<List<Tuple>> ftSugGetWithScores(String key, String prefix, boolean fuzzy, int max) {\n    return appendCommand(commandObjects.ftSugGetWithScores(key, prefix, fuzzy, max));\n  }\n\n  @Override\n  public Response<Boolean> ftSugDel(String key, String string) {\n    return appendCommand(commandObjects.ftSugDel(key, string));\n  }\n\n  @Override\n  public Response<Long> ftSugLen(String key) {\n    return appendCommand(commandObjects.ftSugLen(key));\n  }\n  // RediSearch commands\n\n  // RedisJSON commands\n  @Override\n  public Response<LCSMatchResult> lcs(byte[] keyA, byte[] keyB, LCSParams params) {\n    return appendCommand(commandObjects.lcs(keyA, keyB, params));\n  }\n\n  @Override\n  public Response<String> jsonSet(String key, Path2 path, Object object) {\n    return appendCommand(commandObjects.jsonSet(key, path, object));\n  }\n\n  @Override\n  public Response<String> jsonSetWithEscape(String key, Path2 path, Object object) {\n    return appendCommand(commandObjects.jsonSetWithEscape(key, path, object));\n  }\n\n  @Override\n  public Response<String> jsonSet(String key, Path path, Object object) {\n    return appendCommand(commandObjects.jsonSet(key, path, object));\n  }\n\n  @Override\n  public Response<String> jsonSet(String key, Path2 path, Object object, JsonSetParams params) {\n    return appendCommand(commandObjects.jsonSet(key, path, object, params));\n  }\n\n  @Override\n  public Response<String> jsonSetWithEscape(String key, Path2 path, Object object, JsonSetParams params) {\n    return appendCommand(commandObjects.jsonSetWithEscape(key, path, object, params));\n  }\n\n  @Override\n  public Response<String> jsonSet(String key, Path path, Object object, JsonSetParams params) {\n    return appendCommand(commandObjects.jsonSet(key, path, object, params));\n  }\n\n  @Override\n  public Response<String> jsonMerge(String key, Path2 path, Object object) {\n    return appendCommand(commandObjects.jsonMerge(key, path, object));\n  }\n\n  @Override\n  public Response<String> jsonMerge(String key, Path path, Object object) {\n    return appendCommand(commandObjects.jsonMerge(key, path, object));\n  }\n\n  @Override\n  public Response<Object> jsonGet(String key) {\n    return appendCommand(commandObjects.jsonGet(key));\n  }\n\n  @Override\n  public <T> Response<T> jsonGet(String key, Class<T> clazz) {\n    return appendCommand(commandObjects.jsonGet(key, clazz));\n  }\n\n  @Override\n  public Response<Object> jsonGet(String key, Path2... paths) {\n    return appendCommand(commandObjects.jsonGet(key, paths));\n  }\n\n  @Override\n  public Response<Object> jsonGet(String key, Path... paths) {\n    return appendCommand(commandObjects.jsonGet(key, paths));\n  }\n\n  @Override\n  public <T> Response<T> jsonGet(String key, Class<T> clazz, Path... paths) {\n    return appendCommand(commandObjects.jsonGet(key, clazz, paths));\n  }\n\n  @Override\n  public Response<List<JSONArray>> jsonMGet(Path2 path, String... keys) {\n    return appendCommand(commandObjects.jsonMGet(path, keys));\n  }\n\n  @Override\n  public <T> Response<List<T>> jsonMGet(Path path, Class<T> clazz, String... keys) {\n    return appendCommand(commandObjects.jsonMGet(path, clazz, keys));\n  }\n\n  @Override\n  public Response<Long> jsonDel(String key) {\n    return appendCommand(commandObjects.jsonDel(key));\n  }\n\n  @Override\n  public Response<Long> jsonDel(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonDel(key, path));\n  }\n\n  @Override\n  public Response<Long> jsonDel(String key, Path path) {\n    return appendCommand(commandObjects.jsonDel(key, path));\n  }\n\n  @Override\n  public Response<Long> jsonClear(String key) {\n    return appendCommand(commandObjects.jsonClear(key));\n  }\n\n  @Override\n  public Response<Long> jsonClear(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonClear(key, path));\n  }\n\n  @Override\n  public Response<Long> jsonClear(String key, Path path) {\n    return appendCommand(commandObjects.jsonClear(key, path));\n  }\n\n  @Override\n  public Response<List<Boolean>> jsonToggle(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonToggle(key, path));\n  }\n\n  @Override\n  public Response<String> jsonToggle(String key, Path path) {\n    return appendCommand(commandObjects.jsonToggle(key, path));\n  }\n\n  @Override\n  public Response<Class<?>> jsonType(String key) {\n    return appendCommand(commandObjects.jsonType(key));\n  }\n\n  @Override\n  public Response<List<Class<?>>> jsonType(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonType(key, path));\n  }\n\n  @Override\n  public Response<Class<?>> jsonType(String key, Path path) {\n    return appendCommand(commandObjects.jsonType(key, path));\n  }\n\n  @Override\n  public Response<Long> jsonStrAppend(String key, Object string) {\n    return appendCommand(commandObjects.jsonStrAppend(key, string));\n  }\n\n  @Override\n  public Response<List<Long>> jsonStrAppend(String key, Path2 path, Object string) {\n    return appendCommand(commandObjects.jsonStrAppend(key, path, string));\n  }\n\n  @Override\n  public Response<Long> jsonStrAppend(String key, Path path, Object string) {\n    return appendCommand(commandObjects.jsonStrAppend(key, path, string));\n  }\n\n  @Override\n  public Response<Long> jsonStrLen(String key) {\n    return appendCommand(commandObjects.jsonStrLen(key));\n  }\n\n  @Override\n  public Response<List<Long>> jsonStrLen(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonStrLen(key, path));\n  }\n\n  @Override\n  public Response<Long> jsonStrLen(String key, Path path) {\n    return appendCommand(commandObjects.jsonStrLen(key, path));\n  }\n\n  @Override\n  public Response<Object> jsonNumIncrBy(String key, Path2 path, double value) {\n    return appendCommand(commandObjects.jsonNumIncrBy(key, path, value));\n  }\n\n  @Override\n  public Response<Double> jsonNumIncrBy(String key, Path path, double value) {\n    return appendCommand(commandObjects.jsonNumIncrBy(key, path, value));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrAppend(String key, Path2 path, Object... objects) {\n    return appendCommand(commandObjects.jsonArrAppend(key, path, objects));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrAppendWithEscape(String key, Path2 path, Object... objects) {\n    return appendCommand(commandObjects.jsonArrAppendWithEscape(key, path, objects));\n  }\n\n  @Override\n  public Response<Long> jsonArrAppend(String key, Path path, Object... objects) {\n    return appendCommand(commandObjects.jsonArrAppend(key, path, objects));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrIndex(String key, Path2 path, Object scalar) {\n    return appendCommand(commandObjects.jsonArrIndex(key, path, scalar));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrIndexWithEscape(String key, Path2 path, Object scalar) {\n    return appendCommand(commandObjects.jsonArrIndexWithEscape(key, path, scalar));\n  }\n\n  @Override\n  public Response<Long> jsonArrIndex(String key, Path path, Object scalar) {\n    return appendCommand(commandObjects.jsonArrIndex(key, path, scalar));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrInsert(String key, Path2 path, int index, Object... objects) {\n    return appendCommand(commandObjects.jsonArrInsert(key, path, index, objects));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrInsertWithEscape(String key, Path2 path, int index, Object... objects) {\n    return appendCommand(commandObjects.jsonArrInsertWithEscape(key, path, index, objects));\n  }\n\n  @Override\n  public Response<Long> jsonArrInsert(String key, Path path, int index, Object... pojos) {\n    return appendCommand(commandObjects.jsonArrInsert(key, path, index, pojos));\n  }\n\n  @Override\n  public Response<Object> jsonArrPop(String key) {\n    return appendCommand(commandObjects.jsonArrPop(key));\n  }\n\n  @Override\n  public Response<Long> jsonArrLen(String key, Path path) {\n    return appendCommand(commandObjects.jsonArrLen(key, path));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrTrim(String key, Path2 path, int start, int stop) {\n    return appendCommand(commandObjects.jsonArrTrim(key, path, start, stop));\n  }\n\n  @Override\n  public Response<Long> jsonArrTrim(String key, Path path, int start, int stop) {\n    return appendCommand(commandObjects.jsonArrTrim(key, path, start, stop));\n  }\n\n  @Override\n  public <T> Response<T> jsonArrPop(String key, Class<T> clazz, Path path) {\n    return appendCommand(commandObjects.jsonArrPop(key, clazz, path));\n  }\n\n  @Override\n  public Response<List<Object>> jsonArrPop(String key, Path2 path, int index) {\n    return appendCommand(commandObjects.jsonArrPop(key, path, index));\n  }\n\n  @Override\n  public Response<Object> jsonArrPop(String key, Path path, int index) {\n    return appendCommand(commandObjects.jsonArrPop(key, path, index));\n  }\n\n  @Override\n  public <T> Response<T> jsonArrPop(String key, Class<T> clazz, Path path, int index) {\n    return appendCommand(commandObjects.jsonArrPop(key, clazz, path, index));\n  }\n\n  @Override\n  public Response<Long> jsonArrLen(String key) {\n    return appendCommand(commandObjects.jsonArrLen(key));\n  }\n\n  @Override\n  public Response<List<Long>> jsonArrLen(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonArrLen(key, path));\n  }\n\n  @Override\n  public <T> Response<T> jsonArrPop(String key, Class<T> clazz) {\n    return appendCommand(commandObjects.jsonArrPop(key, clazz));\n  }\n\n  @Override\n  public Response<List<Object>> jsonArrPop(String key, Path2 path) {\n    return appendCommand(commandObjects.jsonArrPop(key, path));\n  }\n\n  @Override\n  public Response<Object> jsonArrPop(String key, Path path) {\n    return appendCommand(commandObjects.jsonArrPop(key, path));\n  }\n  // RedisJSON commands\n\n  // RedisTimeSeries commands\n  @Override\n  public Response<String> tsCreate(String key) {\n    return appendCommand(commandObjects.tsCreate(key));\n  }\n\n  @Override\n  public Response<String> tsCreate(String key, TSCreateParams createParams) {\n    return appendCommand(commandObjects.tsCreate(key, createParams));\n  }\n\n  @Override\n  public Response<Long> tsDel(String key, long fromTimestamp, long toTimestamp) {\n    return appendCommand(commandObjects.tsDel(key, fromTimestamp, toTimestamp));\n  }\n\n  @Override\n  public Response<String> tsAlter(String key, TSAlterParams alterParams) {\n    return appendCommand(commandObjects.tsAlter(key, alterParams));\n  }\n\n  @Override\n  public Response<Long> tsAdd(String key, double value) {\n    return appendCommand(commandObjects.tsAdd(key, value));\n  }\n\n  @Override\n  public Response<Long> tsAdd(String key, long timestamp, double value) {\n    return appendCommand(commandObjects.tsAdd(key, timestamp, value));\n  }\n\n  @Override\n  public Response<Long> tsAdd(String key, long timestamp, double value, TSCreateParams createParams) {\n    return appendCommand(commandObjects.tsAdd(key, timestamp, value, createParams));\n  }\n\n  @Override\n  public Response<Long> tsAdd(String key, long timestamp, double value, TSAddParams addParams) {\n    return appendCommand(commandObjects.tsAdd(key, timestamp, value, addParams));\n  }\n\n  @Override\n  public Response<List<Long>> tsMAdd(Map.Entry<String, TSElement>... entries) {\n    return appendCommand(commandObjects.tsMAdd(entries));\n  }\n\n  @Override\n  public Response<Long> tsIncrBy(String key, double value) {\n    return appendCommand(commandObjects.tsIncrBy(key, value));\n  }\n\n  @Override\n  public Response<Long> tsIncrBy(String key, double value, long timestamp) {\n    return appendCommand(commandObjects.tsIncrBy(key, value, timestamp));\n  }\n\n  @Override\n  public Response<Long> tsIncrBy(String key, double addend, TSIncrByParams incrByParams) {\n    return appendCommand(commandObjects.tsIncrBy(key, addend, incrByParams));\n  }\n\n  @Override\n  public Response<Long> tsDecrBy(String key, double value) {\n    return appendCommand(commandObjects.tsDecrBy(key, value));\n  }\n\n  @Override\n  public Response<Long> tsDecrBy(String key, double value, long timestamp) {\n    return appendCommand(commandObjects.tsDecrBy(key, value, timestamp));\n  }\n\n  @Override\n  public Response<Long> tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) {\n    return appendCommand(commandObjects.tsDecrBy(key, subtrahend, decrByParams));\n  }\n\n  @Override\n  public Response<List<TSElement>> tsRange(String key, long fromTimestamp, long toTimestamp) {\n    return appendCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp));\n  }\n\n  @Override\n  public Response<List<TSElement>> tsRange(String key, TSRangeParams rangeParams) {\n    return appendCommand(commandObjects.tsRange(key, rangeParams));\n  }\n\n  @Override\n  public Response<List<TSElement>> tsRevRange(String key, long fromTimestamp, long toTimestamp) {\n    return appendCommand(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp));\n  }\n\n  @Override\n  public Response<List<TSElement>> tsRevRange(String key, TSRangeParams rangeParams) {\n    return appendCommand(commandObjects.tsRevRange(key, rangeParams));\n  }\n\n  @Override\n  public Response<Map<String, TSMRangeElements>> tsMRange(long fromTimestamp, long toTimestamp, String... filters) {\n    return appendCommand(commandObjects.tsMRange(fromTimestamp, toTimestamp, filters));\n  }\n\n  @Override\n  public Response<Map<String, TSMRangeElements>> tsMRange(TSMRangeParams multiRangeParams) {\n    return appendCommand(commandObjects.tsMRange(multiRangeParams));\n  }\n\n  @Override\n  public Response<Map<String, TSMRangeElements>> tsMRevRange(long fromTimestamp, long toTimestamp, String... filters) {\n    return appendCommand(commandObjects.tsMRevRange(fromTimestamp, toTimestamp, filters));\n  }\n\n  @Override\n  public Response<Map<String, TSMRangeElements>> tsMRevRange(TSMRangeParams multiRangeParams) {\n    return appendCommand(commandObjects.tsMRevRange(multiRangeParams));\n  }\n\n  @Override\n  public Response<TSElement> tsGet(String key) {\n    return appendCommand(commandObjects.tsGet(key));\n  }\n\n  @Override\n  public Response<TSElement> tsGet(String key, TSGetParams getParams) {\n    return appendCommand(commandObjects.tsGet(key, getParams));\n  }\n\n  @Override\n  public Response<Map<String, TSMGetElement>> tsMGet(TSMGetParams multiGetParams, String... filters) {\n    return appendCommand(commandObjects.tsMGet(multiGetParams, filters));\n  }\n\n  @Override\n  public Response<String> tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long timeBucket) {\n    return appendCommand(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, timeBucket));\n  }\n\n  @Override\n  public Response<String> tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, long alignTimestamp) {\n    return appendCommand(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp));\n  }\n\n  @Override\n  public Response<String> tsDeleteRule(String sourceKey, String destKey) {\n    return appendCommand(commandObjects.tsDeleteRule(sourceKey, destKey));\n  }\n\n  @Override\n  public Response<List<String>> tsQueryIndex(String... filters) {\n    return appendCommand(commandObjects.tsQueryIndex(filters));\n  }\n\n  @Override\n  public Response<TSInfo> tsInfo(String key) {\n    return appendCommand(commandObjects.tsInfo(key));\n  }\n\n  @Override\n  public Response<TSInfo> tsInfoDebug(String key) {\n    return appendCommand(commandObjects.tsInfoDebug(key));\n  }\n  // RedisTimeSeries commands\n\n  // RedisBloom commands\n  @Override\n  public Response<String> bfReserve(String key, double errorRate, long capacity) {\n    return appendCommand(commandObjects.bfReserve(key, errorRate, capacity));\n  }\n\n  @Override\n  public Response<String> bfReserve(String key, double errorRate, long capacity, BFReserveParams reserveParams) {\n    return appendCommand(commandObjects.bfReserve(key, errorRate, capacity, reserveParams));\n  }\n\n  @Override\n  public Response<Boolean> bfAdd(String key, String item) {\n    return appendCommand(commandObjects.bfAdd(key, item));\n  }\n\n  @Override\n  public Response<List<Boolean>> bfMAdd(String key, String... items) {\n    return appendCommand(commandObjects.bfMAdd(key, items));\n  }\n\n  @Override\n  public Response<List<Boolean>> bfInsert(String key, String... items) {\n    return appendCommand(commandObjects.bfInsert(key, items));\n  }\n\n  @Override\n  public Response<List<Boolean>> bfInsert(String key, BFInsertParams insertParams, String... items) {\n    return appendCommand(commandObjects.bfInsert(key, insertParams, items));\n  }\n\n  @Override\n  public Response<Boolean> bfExists(String key, String item) {\n    return appendCommand(commandObjects.bfExists(key, item));\n  }\n\n  @Override\n  public Response<List<Boolean>> bfMExists(String key, String... items) {\n    return appendCommand(commandObjects.bfMExists(key, items));\n  }\n\n  @Override\n  public Response<Map.Entry<Long, byte[]>> bfScanDump(String key, long iterator) {\n    return appendCommand(commandObjects.bfScanDump(key, iterator));\n  }\n\n  @Override\n  public Response<String> bfLoadChunk(String key, long iterator, byte[] data) {\n    return appendCommand(commandObjects.bfLoadChunk(key, iterator, data));\n  }\n\n  @Override\n  public Response<Long> bfCard(String key) {\n    return appendCommand(commandObjects.bfCard(key));\n  }\n\n  @Override\n  public Response<Map<String, Object>> bfInfo(String key) {\n    return appendCommand(commandObjects.bfInfo(key));\n  }\n\n  @Override\n  public Response<String> cfReserve(String key, long capacity) {\n    return appendCommand(commandObjects.cfReserve(key, capacity));\n  }\n\n  @Override\n  public Response<String> cfReserve(String key, long capacity, CFReserveParams reserveParams) {\n    return appendCommand(commandObjects.cfReserve(key, capacity, reserveParams));\n  }\n\n  @Override\n  public Response<Boolean> cfAdd(String key, String item) {\n    return appendCommand(commandObjects.cfAdd(key, item));\n  }\n\n  @Override\n  public Response<Boolean> cfAddNx(String key, String item) {\n    return appendCommand(commandObjects.cfAddNx(key, item));\n  }\n\n  @Override\n  public Response<List<Boolean>> cfInsert(String key, String... items) {\n    return appendCommand(commandObjects.cfInsert(key, items));\n  }\n\n  @Override\n  public Response<List<Boolean>> cfInsert(String key, CFInsertParams insertParams, String... items) {\n    return appendCommand(commandObjects.cfInsert(key, insertParams, items));\n  }\n\n  @Override\n  public Response<List<Boolean>> cfInsertNx(String key, String... items) {\n    return appendCommand(commandObjects.cfInsertNx(key, items));\n  }\n\n  @Override\n  public Response<List<Boolean>> cfInsertNx(String key, CFInsertParams insertParams, String... items) {\n    return appendCommand(commandObjects.cfInsertNx(key, insertParams, items));\n  }\n\n  @Override\n  public Response<Boolean> cfExists(String key, String item) {\n    return appendCommand(commandObjects.cfExists(key, item));\n  }\n\n  @Override\n  public Response<List<Boolean>> cfMExists(String key, String... items) {\n    return appendCommand(commandObjects.cfMExists(key, items));\n  }\n\n  @Override\n  public Response<Boolean> cfDel(String key, String item) {\n    return appendCommand(commandObjects.cfDel(key, item));\n  }\n\n  @Override\n  public Response<Long> cfCount(String key, String item) {\n    return appendCommand(commandObjects.cfCount(key, item));\n  }\n\n  @Override\n  public Response<Map.Entry<Long, byte[]>> cfScanDump(String key, long iterator) {\n    return appendCommand(commandObjects.cfScanDump(key, iterator));\n  }\n\n  @Override\n  public Response<String> cfLoadChunk(String key, long iterator, byte[] data) {\n    return appendCommand(commandObjects.cfLoadChunk(key, iterator, data));\n  }\n\n  @Override\n  public Response<Map<String, Object>> cfInfo(String key) {\n    return appendCommand(commandObjects.cfInfo(key));\n  }\n\n  @Override\n  public Response<String> cmsInitByDim(String key, long width, long depth) {\n    return appendCommand(commandObjects.cmsInitByDim(key, width, depth));\n  }\n\n  @Override\n  public Response<String> cmsInitByProb(String key, double error, double probability) {\n    return appendCommand(commandObjects.cmsInitByProb(key, error, probability));\n  }\n\n  @Override\n  public Response<List<Long>> cmsIncrBy(String key, Map<String, Long> itemIncrements) {\n    return appendCommand(commandObjects.cmsIncrBy(key, itemIncrements));\n  }\n\n  @Override\n  public Response<List<Long>> cmsQuery(String key, String... items) {\n    return appendCommand(commandObjects.cmsQuery(key, items));\n  }\n\n  @Override\n  public Response<String> cmsMerge(String destKey, String... keys) {\n    return appendCommand(commandObjects.cmsMerge(destKey, keys));\n  }\n\n  @Override\n  public Response<String> cmsMerge(String destKey, Map<String, Long> keysAndWeights) {\n    return appendCommand(commandObjects.cmsMerge(destKey, keysAndWeights));\n  }\n\n  @Override\n  public Response<Map<String, Object>> cmsInfo(String key) {\n    return appendCommand(commandObjects.cmsInfo(key));\n  }\n\n  @Override\n  public Response<String> topkReserve(String key, long topk) {\n    return appendCommand(commandObjects.topkReserve(key, topk));\n  }\n\n  @Override\n  public Response<String> topkReserve(String key, long topk, long width, long depth, double decay) {\n    return appendCommand(commandObjects.topkReserve(key, topk, width, depth, decay));\n  }\n\n  @Override\n  public Response<List<String>> topkAdd(String key, String... items) {\n    return appendCommand(commandObjects.topkAdd(key, items));\n  }\n\n  @Override\n  public Response<List<String>> topkIncrBy(String key, Map<String, Long> itemIncrements) {\n    return appendCommand(commandObjects.topkIncrBy(key, itemIncrements));\n  }\n\n  @Override\n  public Response<List<Boolean>> topkQuery(String key, String... items) {\n    return appendCommand(commandObjects.topkQuery(key, items));\n  }\n\n  @Override\n  public Response<List<String>> topkList(String key) {\n    return appendCommand(commandObjects.topkList(key));\n  }\n\n  @Override\n  public Response<Map<String, Long>> topkListWithCount(String key) {\n    return appendCommand(commandObjects.topkListWithCount(key));\n  }\n\n  @Override\n  public Response<Map<String, Object>> topkInfo(String key) {\n    return appendCommand(commandObjects.topkInfo(key));\n  }\n\n  @Override\n  public Response<String> tdigestCreate(String key) {\n    return appendCommand(commandObjects.tdigestCreate(key));\n  }\n\n  @Override\n  public Response<String> tdigestCreate(String key, int compression) {\n    return appendCommand(commandObjects.tdigestCreate(key, compression));\n  }\n\n  @Override\n  public Response<String> tdigestReset(String key) {\n    return appendCommand(commandObjects.tdigestReset(key));\n  }\n\n  @Override\n  public Response<String> tdigestMerge(String destinationKey, String... sourceKeys) {\n    return appendCommand(commandObjects.tdigestMerge(destinationKey, sourceKeys));\n  }\n\n  @Override\n  public Response<String> tdigestMerge(TDigestMergeParams mergeParams, String destinationKey, String... sourceKeys) {\n    return appendCommand(commandObjects.tdigestMerge(mergeParams, destinationKey, sourceKeys));\n  }\n\n  @Override\n  public Response<Map<String, Object>> tdigestInfo(String key) {\n    return appendCommand(commandObjects.tdigestInfo(key));\n  }\n\n  @Override\n  public Response<String> tdigestAdd(String key, double... values) {\n    return appendCommand(commandObjects.tdigestAdd(key, values));\n  }\n\n  @Override\n  public Response<List<Double>> tdigestCDF(String key, double... values) {\n    return appendCommand(commandObjects.tdigestCDF(key, values));\n  }\n\n  @Override\n  public Response<List<Double>> tdigestQuantile(String key, double... quantiles) {\n    return appendCommand(commandObjects.tdigestQuantile(key, quantiles));\n  }\n\n  @Override\n  public Response<Double> tdigestMin(String key) {\n    return appendCommand(commandObjects.tdigestMin(key));\n  }\n\n  @Override\n  public Response<Double> tdigestMax(String key) {\n    return appendCommand(commandObjects.tdigestMax(key));\n  }\n\n  @Override\n  public Response<Double> tdigestTrimmedMean(String key, double lowCutQuantile, double highCutQuantile) {\n    return appendCommand(commandObjects.tdigestTrimmedMean(key, lowCutQuantile, highCutQuantile));\n  }\n\n  @Override\n  public Response<List<Long>> tdigestRank(String key, double... values) {\n    return appendCommand(commandObjects.tdigestRank(key, values));\n  }\n\n  @Override\n  public Response<List<Long>> tdigestRevRank(String key, double... values) {\n    return appendCommand(commandObjects.tdigestRevRank(key, values));\n  }\n\n  @Override\n  public Response<List<Double>> tdigestByRank(String key, long... ranks) {\n    return appendCommand(commandObjects.tdigestByRank(key, ranks));\n  }\n\n  @Override\n  public Response<List<Double>> tdigestByRevRank(String key, long... ranks) {\n    return appendCommand(commandObjects.tdigestByRevRank(key, ranks));\n  }\n  // RedisBloom commands\n\n  // Vector Set commands\n  @Override\n  public Response<Boolean> vadd(String key, float[] vector, String element) {\n    return appendCommand(commandObjects.vadd(key, vector, element));\n  }\n\n  @Override\n  public Response<Boolean> vadd(String key, float[] vector, String element, VAddParams params) {\n    return appendCommand(commandObjects.vadd(key, vector, element, params));\n  }\n\n  @Override\n  public Response<Boolean> vaddFP32(String key, byte[] vectorBlob, String element) {\n    return appendCommand(commandObjects.vaddFP32(key, vectorBlob, element));\n  }\n\n  @Override\n  public Response<Boolean> vaddFP32(String key, byte[] vectorBlob, String element, VAddParams params) {\n    return appendCommand(commandObjects.vaddFP32(key, vectorBlob, element, params));\n  }\n\n  @Override\n  public Response<Boolean> vadd(String key, float[] vector, String element, int reduceDim, VAddParams params) {\n    return appendCommand(commandObjects.vadd(key, vector, element, reduceDim, params));\n  }\n\n  @Override\n  public Response<Boolean> vaddFP32(String key, byte[] vectorBlob, String element, int reduceDim, VAddParams params) {\n    return appendCommand(commandObjects.vaddFP32(key, vectorBlob, element, reduceDim, params));\n  }\n\n  @Override\n  public Response<List<String>> vsim(String key, float[] vector) {\n    return appendCommand(commandObjects.vsim(key, vector));\n  }\n\n  @Override\n  public Response<List<String>> vsim(String key, float[] vector, VSimParams params) {\n    return appendCommand(commandObjects.vsim(key, vector, params));\n  }\n\n  @Override\n  public Response<Map<String, Double>> vsimWithScores(String key, float[] vector, VSimParams params) {\n    return appendCommand(commandObjects.vsimWithScores(key, vector, params));\n  }\n\n  @Override\n  public Response<List<String>> vsimByElement(String key, String element) {\n    return appendCommand(commandObjects.vsimByElement(key, element));\n  }\n\n  @Override\n  public Response<List<String>> vsimByElement(String key, String element, VSimParams params) {\n    return appendCommand(commandObjects.vsimByElement(key, element, params));\n  }\n\n  @Override\n  public Response<Map<String, Double>> vsimByElementWithScores(String key, String element, VSimParams params) {\n    return appendCommand(commandObjects.vsimByElementWithScores(key, element, params));\n  }\n\n  @Override\n  public Response<Long> vdim(String key) {\n    return appendCommand(commandObjects.vdim(key));\n  }\n\n  @Override\n  public Response<Long> vcard(String key) {\n    return appendCommand(commandObjects.vcard(key));\n  }\n\n  @Override\n  public Response<List<Double>> vemb(String key, String element) {\n    return appendCommand(commandObjects.vemb(key, element));\n  }\n\n  @Override\n  public Response<RawVector> vembRaw(String key, String element) {\n    return appendCommand(commandObjects.vembRaw(key, element));\n  }\n\n  @Override\n  public Response<Boolean> vrem(String key, String element) {\n    return appendCommand(commandObjects.vrem(key, element));\n  }\n\n  @Override\n  public Response<List<List<String>>> vlinks(String key, String element) {\n    return appendCommand(commandObjects.vlinks(key, element));\n  }\n\n  @Override\n  public Response<List<Map<String, Double>>> vlinksWithScores(String key, String element) {\n    return appendCommand(commandObjects.vlinksWithScores(key, element));\n  }\n\n  @Override\n  public Response<String> vrandmember(String key) {\n    return appendCommand(commandObjects.vrandmember(key));\n  }\n\n  @Override\n  public Response<List<String>> vrandmember(String key, int count) {\n    return appendCommand(commandObjects.vrandmember(key, count));\n  }\n\n  @Override\n  public Response<String> vgetattr(String key, String element) {\n    return appendCommand(commandObjects.vgetattr(key, element));\n  }\n\n  @Override\n  public Response<Boolean> vsetattr(String key, String element, String attributes) {\n    return appendCommand(commandObjects.vsetattr(key, element, attributes));\n  }\n\n  @Override\n  public Response<VectorInfo> vinfo(String key) {\n    return appendCommand(commandObjects.vinfo(key));\n  }\n\n  // Binary vector set pipeline commands\n  @Override\n  public Response<Boolean> vadd(byte[] key, float[] vector, byte[] element) {\n    return appendCommand(commandObjects.vadd(key, vector, element));\n  }\n\n  @Override\n  public Response<Boolean> vadd(byte[] key, float[] vector, byte[] element, VAddParams params) {\n    return appendCommand(commandObjects.vadd(key, vector, element, params));\n  }\n\n  @Override\n  public Response<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element) {\n    return appendCommand(commandObjects.vaddFP32(key, vectorBlob, element));\n  }\n\n  @Override\n  public Response<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, VAddParams params) {\n    return appendCommand(commandObjects.vaddFP32(key, vectorBlob, element, params));\n  }\n\n  @Override\n  public Response<Boolean> vadd(byte[] key, float[] vector, byte[] element, int reduceDim, VAddParams params) {\n    return appendCommand(commandObjects.vadd(key, vector, element, reduceDim, params));\n  }\n\n  @Override\n  public Response<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, int reduceDim, VAddParams params) {\n    return appendCommand(commandObjects.vaddFP32(key, vectorBlob, element, reduceDim, params));\n  }\n\n  @Override\n  public Response<List<byte[]>> vsim(byte[] key, float[] vector) {\n    return appendCommand(commandObjects.vsim(key, vector));\n  }\n\n  @Override\n  public Response<List<byte[]>> vsim(byte[] key, float[] vector, VSimParams params) {\n    return appendCommand(commandObjects.vsim(key, vector, params));\n  }\n\n  @Override\n  public Response<Map<byte[], Double>> vsimWithScores(byte[] key, float[] vector, VSimParams params) {\n    return appendCommand(commandObjects.vsimWithScores(key, vector, params));\n  }\n\n  @Override\n  public Response<List<byte[]>> vsimByElement(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vsimByElement(key, element));\n  }\n\n  @Override\n  public Response<List<byte[]>> vsimByElement(byte[] key, byte[] element, VSimParams params) {\n    return appendCommand(commandObjects.vsimByElement(key, element, params));\n  }\n\n  @Override\n  public Response<Map<byte[], Double>> vsimByElementWithScores(byte[] key, byte[] element, VSimParams params) {\n    return appendCommand(commandObjects.vsimByElementWithScores(key, element, params));\n  }\n\n  @Override\n  public Response<Long> vdim(byte[] key) {\n    return appendCommand(commandObjects.vdim(key));\n  }\n\n  @Override\n  public Response<Long> vcard(byte[] key) {\n    return appendCommand(commandObjects.vcard(key));\n  }\n\n  @Override\n  public Response<List<Double>> vemb(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vemb(key, element));\n  }\n\n  @Override\n  public Response<RawVector> vembRaw(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vembRaw(key, element));\n  }\n\n  @Override\n  public Response<Boolean> vrem(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vrem(key, element));\n  }\n\n  @Override\n  public Response<List<List<byte[]>>> vlinks(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vlinks(key, element));\n  }\n\n  @Override\n  public Response<List<Map<byte[], Double>>> vlinksWithScores(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vlinksWithScores(key, element));\n  }\n\n  @Override\n  public Response<byte[]> vrandmember(byte[] key) {\n    return appendCommand(commandObjects.vrandmember(key));\n  }\n\n  @Override\n  public Response<List<byte[]>> vrandmember(byte[] key, int count) {\n    return appendCommand(commandObjects.vrandmember(key, count));\n  }\n\n  @Override\n  public Response<byte[]> vgetattr(byte[] key, byte[] element) {\n    return appendCommand(commandObjects.vgetattr(key, element));\n  }\n\n  @Override\n  public Response<Boolean> vsetattr(byte[] key, byte[] element, byte[] attributes) {\n    return appendCommand(commandObjects.vsetattr(key, element, attributes));\n  }\n  // Vector Set pipeline commands end\n\n  // Hotkeys pipeline commands\n  public Response<String> hotkeysStart(HotkeysParams params) {\n    return appendCommand(commandObjects.hotkeysStart(params));\n  }\n\n  public Response<String> hotkeysStop() {\n    return appendCommand(commandObjects.hotkeysStop());\n  }\n\n  public Response<String> hotkeysReset() {\n    return appendCommand(commandObjects.hotkeysReset());\n  }\n\n  public Response<HotkeysInfo> hotkeysGet() {\n    return appendCommand(commandObjects.hotkeysGet());\n  }\n  // Hotkeys pipeline commands end\n\n  public Response<Object> sendCommand(ProtocolCommand cmd, String... args) {\n    return sendCommand(new CommandArguments(cmd).addObjects((Object[]) args));\n  }\n\n  public Response<Object> sendCommand(ProtocolCommand cmd, byte[]... args) {\n    return sendCommand(new CommandArguments(cmd).addObjects((Object[]) args));\n  }\n\n  public Response<Object> sendCommand(CommandArguments args) {\n    return executeCommand(new CommandObject<>(args, BuilderFactory.RAW_OBJECT));\n  }\n\n  public <T> Response<T> executeCommand(CommandObject<T> command) {\n    return appendCommand(command);\n  }\n\n  public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {\n    this.commandObjects.setJsonObjectMapper(jsonObjectMapper);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Protocol.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.exceptions.*;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.RedisInputStream;\nimport redis.clients.jedis.util.RedisOutputStream;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic final class Protocol {\n\n  public static final String DEFAULT_HOST = \"127.0.0.1\";\n  public static final int DEFAULT_PORT = 6379;\n  public static final int DEFAULT_SENTINEL_PORT = 26379;\n  public static final int DEFAULT_TIMEOUT = 2000;\n  public static final int DEFAULT_DATABASE = 0;\n  public static final int CLUSTER_HASHSLOTS = 16384;\n\n  public static final Charset CHARSET = StandardCharsets.UTF_8;\n\n  public static final byte ASTERISK_BYTE = '*';\n  public static final byte COLON_BYTE = ':';\n  public static final byte COMMA_BYTE = ',';\n  public static final byte DOLLAR_BYTE = '$';\n  public static final byte EQUAL_BYTE = '=';\n  public static final byte GREATER_THAN_BYTE = '>';\n  public static final byte HASH_BYTE = '#';\n  public static final byte LEFT_BRACE_BYTE = '(';\n  public static final byte MINUS_BYTE = '-';\n  public static final byte PERCENT_BYTE = '%';\n  public static final byte PLUS_BYTE = '+';\n  public static final byte TILDE_BYTE = '~';\n  public static final byte UNDERSCORE_BYTE = '_';\n\n  public static final byte[] BYTES_TRUE = toByteArray(1);\n  public static final byte[] BYTES_FALSE = toByteArray(0);\n  public static final byte[] BYTES_TILDE = SafeEncoder.encode(\"~\");\n  public static final byte[] BYTES_EQUAL = SafeEncoder.encode(\"=\");\n  public static final byte[] BYTES_ASTERISK = SafeEncoder.encode(\"*\");\n\n  public static final byte[] POSITIVE_INFINITY_BYTES = \"+inf\".getBytes();\n  public static final byte[] NEGATIVE_INFINITY_BYTES = \"-inf\".getBytes();\n\n  static final List<KeyValue> PROTOCOL_EMPTY_MAP = Collections.unmodifiableList(new ArrayList<>(0));\n\n  private static final String ASK_PREFIX = \"ASK \";\n  private static final String MOVED_PREFIX = \"MOVED \";\n  private static final String CLUSTERDOWN_PREFIX = \"CLUSTERDOWN \";\n  private static final String BUSY_PREFIX = \"BUSY \";\n  private static final String NOSCRIPT_PREFIX = \"NOSCRIPT \";\n  private static final String NOAUTH_PREFIX = \"NOAUTH\";\n  private static final String WRONGPASS_PREFIX = \"WRONGPASS\";\n  private static final String NOPERM_PREFIX = \"NOPERM\";\n\n  private static final byte[] INVALIDATE_BYTES = SafeEncoder.encode(\"invalidate\");\n\n  private Protocol() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  public static void sendCommand(final RedisOutputStream os, CommandArguments args) {\n    try {\n      os.write(ASTERISK_BYTE);\n      os.writeIntCrLf(args.size());\n      for (Rawable arg : args) {\n        os.write(DOLLAR_BYTE);\n        final byte[] bin = arg.getRaw();\n        os.writeIntCrLf(bin.length);\n        os.write(bin);\n        os.writeCrLf();\n      }\n    } catch (IOException e) {\n      throw new JedisConnectionException(e);\n    }\n  }\n\n  private static void processError(final RedisInputStream is) {\n    String message = is.readLine();\n    // TODO: I'm not sure if this is the best way to do this.\n    // Maybe Read only first 5 bytes instead?\n    if (message.startsWith(MOVED_PREFIX)) {\n      String[] movedInfo = parseTargetHostAndSlot(message);\n      throw new JedisMovedDataException(message, HostAndPort.from(movedInfo[1]), Integer.parseInt(movedInfo[0]));\n    } else if (message.startsWith(ASK_PREFIX)) {\n      String[] askInfo = parseTargetHostAndSlot(message);\n      throw new JedisAskDataException(message, HostAndPort.from(askInfo[1]), Integer.parseInt(askInfo[0]));\n    } else if (message.startsWith(CLUSTERDOWN_PREFIX)) {\n      throw new JedisClusterException(message);\n    } else if (message.startsWith(BUSY_PREFIX)) {\n      throw new JedisBusyException(message);\n    } else if (message.startsWith(NOSCRIPT_PREFIX)) {\n      throw new JedisNoScriptException(message);\n    } else if (message.startsWith(NOAUTH_PREFIX)\n        || message.startsWith(WRONGPASS_PREFIX)\n        || message.startsWith(NOPERM_PREFIX)) {\n      throw new JedisAccessControlException(message);\n    }\n    throw new JedisDataException(message);\n  }\n\n  public static String readErrorLineIfPossible(RedisInputStream is) {\n    final byte b = is.readByte();\n    // if buffer contains other type of response, just ignore.\n    if (b != MINUS_BYTE) {\n      return null;\n    }\n    return is.readLine();\n  }\n\n  private static String[] parseTargetHostAndSlot(String clusterRedirectResponse) {\n    String[] response = new String[2];\n    String[] messageInfo = clusterRedirectResponse.split(\" \");\n    response[0] = messageInfo[1];\n    response[1] = messageInfo[2];\n    return response;\n  }\n\n  private static Object process(final RedisInputStream is) {\n    final byte b = is.readByte();\n    // System.out.println(\"BYTE: \" + (char) b);\n    switch (b) {\n      case PLUS_BYTE:\n        return is.readLineBytes();\n      case DOLLAR_BYTE:\n      case EQUAL_BYTE:\n        return processBulkReply(is);\n      case ASTERISK_BYTE:\n        return processMultiBulkReply(is);\n      case UNDERSCORE_BYTE:\n        return is.readNullCrLf();\n      case HASH_BYTE:\n        return is.readBooleanCrLf();\n      case COLON_BYTE:\n        return is.readLongCrLf();\n      case COMMA_BYTE:\n        return is.readDoubleCrLf();\n      case LEFT_BRACE_BYTE:\n        return is.readBigIntegerCrLf();\n      case PERCENT_BYTE: // TODO: currently just to start working with HELLO\n        return processMapKeyValueReply(is);\n      case TILDE_BYTE: // TODO:\n        return processMultiBulkReply(is);\n      case GREATER_THAN_BYTE:\n        return processMultiBulkReply(is);\n      case MINUS_BYTE:\n        processError(is);\n        return null;\n      // TODO: Blob error '!'\n      default:\n        throw new JedisConnectionException(\"Unknown reply: \" + (char) b);\n    }\n  }\n\n  private static byte[] processBulkReply(final RedisInputStream is) {\n    final int len = is.readIntCrLf();\n    if (len == -1) {\n      return null;\n    }\n\n    final byte[] read = new byte[len];\n    int offset = 0;\n    while (offset < len) {\n      final int size = is.read(read, offset, (len - offset));\n      if (size == -1) {\n        throw new JedisConnectionException(\"It seems like server has closed the connection.\");\n      }\n      offset += size;\n    }\n\n    // read 2 more bytes for the command delimiter\n    is.readByte();\n    is.readByte();\n\n    return read;\n  }\n\n  private static List<Object> processMultiBulkReply(final RedisInputStream is) {\n    final int num = is.readIntCrLf();\n    if (num == -1)\n      return null;\n    final List<Object> ret = new ArrayList<>(num);\n    for (int i = 0; i < num; i++) {\n      try {\n        ret.add(process(is));\n      } catch (JedisDataException e) {\n        ret.add(e);\n      }\n    }\n    return ret;\n  }\n\n  private static List<KeyValue> processMapKeyValueReply(final RedisInputStream is) {\n    final int num = is.readIntCrLf();\n    switch (num) {\n      case -1:\n        return null;\n      case 0:\n        return PROTOCOL_EMPTY_MAP;\n      default:\n        final List<KeyValue> ret = new ArrayList<>(num);\n        for (int i = 0; i < num; i++) {\n          ret.add(new KeyValue(process(is), process(is)));\n        }\n        return ret;\n    }\n  }\n\n  public static Object read(final RedisInputStream is) {\n    return process(is);\n  }\n\n  @Experimental\n  public static Object read(final RedisInputStream is, final Cache cache) {\n    Object unhandledPush = readPushes(is, cache, false);\n    return unhandledPush == null ? process(is) : unhandledPush;\n  }\n\n  @Experimental\n  public static Object readPushes(final RedisInputStream is, final Cache cache,\n      boolean onlyPendingBuffer) {\n    Object unhandledPush = null;\n    if (onlyPendingBuffer) {\n      try {\n        while (unhandledPush == null && is.available() > 0 && is.peek(GREATER_THAN_BYTE)) {\n          unhandledPush = processPush(is, cache);\n        }\n      } catch (IOException e) {\n        throw new JedisConnectionException(\"Failed to read pending buffer for push messages!\", e);\n      }\n    } else {\n      while (unhandledPush == null && is.peek(GREATER_THAN_BYTE)) {\n        unhandledPush = processPush(is, cache);\n      }\n    }\n    return unhandledPush;\n  }\n\n  private static Object processPush(final RedisInputStream is, Cache cache) {\n    is.readByte();\n    List<Object> list = processMultiBulkReply(is);\n    if (list.size() == 2 && list.get(0) instanceof byte[]\n        && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) {\n      cache.deleteByRedisKeys((List) list.get(1));\n      return null;\n    } else {\n      return list;\n    }\n  }\n\n  public static final byte[] toByteArray(final boolean value) {\n    return value ? BYTES_TRUE : BYTES_FALSE;\n  }\n\n  public static final byte[] toByteArray(final int value) {\n    return SafeEncoder.encode(String.valueOf(value));\n  }\n\n  public static final byte[] toByteArray(final long value) {\n    return SafeEncoder.encode(String.valueOf(value));\n  }\n\n  public static final byte[] toByteArray(final double value) {\n    if (value == Double.POSITIVE_INFINITY) {\n      return POSITIVE_INFINITY_BYTES;\n    } else if (value == Double.NEGATIVE_INFINITY) {\n      return NEGATIVE_INFINITY_BYTES;\n    } else {\n      return SafeEncoder.encode(String.valueOf(value));\n    }\n  }\n\n  public static enum Command implements ProtocolCommand {\n\n    PING, AUTH, HELLO, SET, GET, GETDEL, GETEX, DIGEST, EXISTS, DEL, DELEX, UNLINK, TYPE, FLUSHDB, FLUSHALL, MOVE,\n    KEYS, RANDOMKEY, RENAME, RENAMENX, DUMP, RESTORE, DBSIZE, SELECT, SWAPDB, MIGRATE, ECHO, //\n    EXPIRE, EXPIREAT, EXPIRETIME, PEXPIRE, PEXPIREAT, PEXPIRETIME, TTL, PTTL, // <-- key expiration\n    MULTI, DISCARD, EXEC, WATCH, UNWATCH, SORT, SORT_RO, INFO, SHUTDOWN, MONITOR, CONFIG, LCS, //\n    GETSET, MGET, SETNX, SETEX, PSETEX, MSETEX, MSET, MSETNX, DECR, DECRBY, INCR, INCRBY, INCRBYFLOAT,\n    STRLEN, APPEND, SUBSTR, // <-- string\n    SETBIT, GETBIT, BITPOS, SETRANGE, GETRANGE, BITCOUNT, BITOP, BITFIELD, BITFIELD_RO, // <-- bit (string)\n    HSET, HGET, HSETNX, HMSET, HMGET, HINCRBY, HEXISTS, HDEL, HLEN, HKEYS, HVALS, HGETALL, HSTRLEN,\n    HEXPIRE, HPEXPIRE, HEXPIREAT, HPEXPIREAT, HTTL, HPTTL, HEXPIRETIME, HPEXPIRETIME, HPERSIST,\n    HRANDFIELD, HINCRBYFLOAT, HSETEX, HGETEX, HGETDEL, // <-- hash\n    RPUSH, LPUSH, LLEN, LRANGE, LTRIM, LINDEX, LSET, LREM, LPOP, RPOP, BLPOP, BRPOP, LINSERT, LPOS,\n    RPOPLPUSH, BRPOPLPUSH, BLMOVE, LMOVE, LMPOP, BLMPOP, LPUSHX, RPUSHX, // <-- list\n    SADD, SMEMBERS, SREM, SPOP, SMOVE, SCARD, SRANDMEMBER, SINTER, SINTERSTORE, SUNION, SUNIONSTORE,\n    SDIFF, SDIFFSTORE, SISMEMBER, SMISMEMBER, SINTERCARD, // <-- set\n    ZADD, ZDIFF, ZDIFFSTORE, ZRANGE, ZREM, ZINCRBY, ZRANK, ZREVRANK, ZREVRANGE, ZRANDMEMBER, ZCARD,\n    ZSCORE, ZPOPMAX, ZPOPMIN, ZCOUNT, ZUNION, ZUNIONSTORE, ZINTER, ZINTERSTORE, ZRANGEBYSCORE,\n    ZREVRANGEBYSCORE, ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZLEXCOUNT, ZRANGEBYLEX, ZREVRANGEBYLEX,\n    ZREMRANGEBYLEX, ZMSCORE, ZRANGESTORE, ZINTERCARD, ZMPOP, BZMPOP, BZPOPMIN, BZPOPMAX, // <-- zset\n    GEOADD, GEODIST, GEOHASH, GEOPOS, GEORADIUS, GEORADIUS_RO, GEOSEARCH, GEOSEARCHSTORE,\n    GEORADIUSBYMEMBER, GEORADIUSBYMEMBER_RO, // <-- geo\n    PFADD, PFCOUNT, PFMERGE, // <-- hyper log log\n    XADD, XLEN, XDEL, XTRIM, XRANGE, XREVRANGE, XREAD, XACK, XGROUP, XREADGROUP, XPENDING, XCLAIM,\n    XAUTOCLAIM, XINFO, XDELEX, XACKDEL, XCFGSET, // <-- stream\n    EVAL, EVALSHA, SCRIPT, EVAL_RO, EVALSHA_RO, FUNCTION, FCALL, FCALL_RO, // <-- program\n    SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,\n    SSUBSCRIBE, SUNSUBSCRIBE, SPUBLISH, // <-- pub sub\n    SAVE, BGSAVE, BGREWRITEAOF, LASTSAVE, PERSIST, ROLE, FAILOVER, SLOWLOG, OBJECT, CLIENT, TIME,\n    SCAN, HSCAN, SSCAN, ZSCAN, WAIT, CLUSTER, ASKING, READONLY, READWRITE, SLAVEOF, REPLICAOF, COPY,\n    SENTINEL, MODULE, ACL, TOUCH, MEMORY, LOLWUT, COMMAND, RESET, LATENCY, WAITAOF, HOTKEYS,\n    VADD, VSIM, VDIM, VCARD, VEMB, VREM, VLINKS, VRANDMEMBER, VGETATTR, VSETATTR, VINFO; // <-- vector set\n\n    private final byte[] raw;\n\n    private Command() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public static enum Keyword implements Rawable {\n\n    AGGREGATE, ALPHA, BY, GET, LIMIT, NO, NOSORT, ONE, SET, STORE, WEIGHTS, WITHSCORE, WITHSCORES,\n    RESETSTAT, REWRITE, RESET, FLUSH, EXISTS, LOAD, LEN, HELP, SCHEDULE, MATCH, COUNT, TYPE, KEYS,\n    REFCOUNT, ENCODING, IDLETIME, FREQ, REPLACE, GETNAME, SETNAME, SETINFO, LIST, ID, KILL, PERSIST,\n    STREAMS, CREATE, MKSTREAM, SETID, DESTROY, DELCONSUMER, MAXLEN, GROUP, IDLE, TIME, BLOCK, NOACK, CLAIM,\n    RETRYCOUNT, STREAM, GROUPS, CONSUMERS, JUSTID, WITHVALUES, NOMKSTREAM, MINID, CREATECONSUMER,\n    IDMPAUTO, IDMP, DURATION, MAXSIZE,\n    SETUSER, GETUSER, DELUSER, WHOAMI, USERS, CAT, GENPASS, LOG, SAVE, DRYRUN, COPY, AUTH, AUTH2,\n    NX, XX, IFEQ, IFNE, IFDEQ, IFDNE, EX, PX, EXAT, PXAT, ABSTTL, KEEPTTL, INCR, LT, GT, CH, INFO, PAUSE, UNPAUSE, UNBLOCK,\n    REV, WITHCOORD, WITHDIST, WITHHASH, ANY, FROMMEMBER, FROMLONLAT, BYRADIUS, BYBOX, BYLEX, BYSCORE,\n    STOREDIST, TO, FORCE, TIMEOUT, DB, UNLOAD, ABORT, IDX, MINMATCHLEN, WITHMATCHLEN, FULL,\n    DELETE, LIBRARYNAME, WITHCODE, DESCRIPTION, GETKEYS, GETKEYSANDFLAGS, DOCS, FILTERBY, DUMP,\n    MODULE, ACLCAT, PATTERN, DOCTOR, LATEST, HISTORY, USAGE, SAMPLES, PURGE, STATS, LOADEX, CONFIG,\n    ARGS, RANK, NOW, VERSION, ADDR, SKIPME, USER, LADDR, FIELDS,\n    CHANNELS, NUMPAT, NUMSUB, SHARDCHANNELS, SHARDNUMSUB, NOVALUES, MAXAGE, FXX, FNX,\n    // Vector set keywords\n    REDUCE, CAS, NOQUANT, Q8, BIN, EF, SETATTR, M, VALUES, FP32, ELE, FILTER, FILTER_EF, TRUTH, NOTHREAD, RAW, EPSILON, WITHATTRIBS,\n    // Hotkeys keywords\n    METRICS, SAMPLE, SLOTS, START, STOP, CPU, NET;\n\n    private final byte[] raw;\n\n    private Keyword() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public static enum SentinelKeyword implements Rawable {\n\n    MYID, MASTERS, MASTER, SENTINELS, SLAVES, REPLICAS, RESET, FAILOVER, REMOVE, SET, MONITOR,\n    GET_MASTER_ADDR_BY_NAME(\"GET-MASTER-ADDR-BY-NAME\");\n\n    private final byte[] raw;\n\n    private SentinelKeyword() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    private SentinelKeyword(String str) {\n      raw = SafeEncoder.encode(str);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public static enum ResponseKeyword implements Rawable {\n\n    SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, PUNSUBSCRIBE, MESSAGE, PMESSAGE, PONG,\n    SSUBSCRIBE, SUNSUBSCRIBE, SMESSAGE;\n\n    private final byte[] raw;\n\n    private ResponseKeyword() {\n      raw = SafeEncoder.encode(name().toLowerCase(Locale.ENGLISH));\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public static enum ClusterKeyword implements Rawable {\n\n    MEET, RESET, INFO, FAILOVER, SLOTS, NODES, REPLICAS, SLAVES, MYID, ADDSLOTS, DELSLOTS,\n    GETKEYSINSLOT, SETSLOT, NODE, MIGRATING, IMPORTING, STABLE, FORGET, FLUSHSLOTS, KEYSLOT,\n    COUNTKEYSINSLOT, SAVECONFIG, REPLICATE, LINKS, ADDSLOTSRANGE, DELSLOTSRANGE, BUMPEPOCH,\n    MYSHARDID, SHARDS;\n\n    private final byte[] raw;\n\n    private ClusterKeyword() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/RedisClient.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\n\nimport redis.clients.jedis.builders.StandaloneClientBuilder;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\nimport redis.clients.jedis.util.JedisAsserts;\nimport redis.clients.jedis.util.JedisURIHelper;\nimport redis.clients.jedis.util.Pool;\n\n/**\n * {@code RedisClient} is the recommended client for connecting to standalone Redis deployments.\n * <p>\n * This class provides a modern, unified interface for interacting with Redis, supporting connection\n * pooling, authentication, and configuration via a fluent builder API.\n * </p>\n * <p>\n * {@code RedisClient} supersedes the deprecated {@link JedisPooled} and {@link UnifiedJedis}\n * classes, offering improved usability and extensibility. For new applications, use\n * {@code RedisClient} instead of the older classes.\n * </p>\n * <p>\n * Example usage:\n * </p>\n * \n * <pre>\n * {\n *   &#64;code\n *   RedisClient client = RedisClient.builder().host(\"localhost\").port(6379).build();\n * }\n * </pre>\n * <p>\n * For advanced configuration, see the {@link RedisClient.Builder} class.\n * </p>\n */\npublic class RedisClient extends UnifiedJedis {\n\n  private RedisClient(CommandExecutor commandExecutor, ConnectionProvider connectionProvider,\n      CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n  }\n\n  /**\n   * Creates a RedisClient with default host and port (localhost:6379).\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @return a new {@link RedisClient} instance\n   */\n  public static RedisClient create() {\n    return builder().build();\n  }\n\n  /**\n   * Creates a RedisClient with the specified host and port.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param host the Redis server hostname\n   * @param port the Redis server port\n   * @return a new {@link RedisClient} instance\n   */\n  public static RedisClient create(final String host, final int port) {\n    return builder().hostAndPort(host, port).build();\n  }\n\n  /**\n   * Creates a RedisClient with the specified host and port.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param hostAndPort the Redis server host and port\n   * @return a new {@link RedisClient} instance\n   */\n  public static RedisClient create(final HostAndPort hostAndPort) {\n    return builder().hostAndPort(hostAndPort).build();\n  }\n\n  /**\n   * Creates a RedisClient with the specified host, port, user, and password.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param host the Redis server hostname\n   * @param port the Redis server port\n   * @param user the username for authentication\n   * @param password the password for authentication\n   * @return a new {@link RedisClient} instance\n   */\n  public static RedisClient create(final String host, final int port, final String user,\n      final String password) {\n    return builder().hostAndPort(host, port)\n        .clientConfig(DefaultJedisClientConfig.builder().user(user).password(password).build())\n        .build();\n  }\n\n  /**\n   * Creates a RedisClient from a Redis URI.\n   * <p>\n   * The URI must be in the format: {@code redis[s]://[[user][:password]@]host[:port][/database]}\n   * <p>\n   * Examples:\n   * <ul>\n   * <li>{@code redis://localhost:6379}</li>\n   * <li>{@code redis://user:password@localhost:6379/0}</li>\n   * <li>{@code rediss://localhost:6380} (SSL)</li>\n   * </ul>\n   * <p>\n   * <b>Note:</b> To connect using just a hostname and port without a URI, use\n   * {@link #create(String, int)} instead.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param url Redis URI string (not just a hostname)\n   * @return a new {@link RedisClient} instance\n   * @throws IllegalArgumentException if the URI format is invalid\n   * @see JedisURIHelper#isValid(java.net.URI)\n   */\n  public static RedisClient create(final String url) {\n\n    return create(URI.create(url));\n  }\n\n  /**\n   * Creates a RedisClient from a Redis URI.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param uri the Redis server URI\n   * @return a new {@link RedisClient} instance\n   */\n  public static RedisClient create(final URI uri) {\n    JedisAsserts.notNull(uri, \"Redis URI must not be null\");\n    JedisAsserts.isTrue(JedisURIHelper.isValid(uri), \"Invalid Redis URI\");\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder(uri).build();\n    HostAndPort hostAndPort = JedisURIHelper.getHostAndPort(uri);\n\n    return builder().hostAndPort(hostAndPort).clientConfig(clientConfig).build();\n  }\n\n  /**\n   * Fluent builder for {@link RedisClient} (standalone).\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  public static class Builder extends StandaloneClientBuilder<RedisClient> {\n\n    @Override\n    protected RedisClient createClient() {\n      return new RedisClient(commandExecutor, connectionProvider, commandObjects,\n          clientConfig.getRedisProtocol(), cache);\n    }\n  }\n\n  /**\n   * Create a new builder for configuring RedisClient instances.\n   * @return a new {@link RedisClient.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public final Pool<Connection> getPool() {\n    return ((PooledConnectionProvider) provider).getPool();\n  }\n\n  @Override\n  public Pipeline pipelined() {\n    return (Pipeline) super.pipelined();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/RedisClusterClient.java",
    "content": "package redis.clients.jedis;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.builders.ClusterClientBuilder;\nimport redis.clients.jedis.executors.ClusterCommandExecutor;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.params.MSetExParams;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.util.JedisClusterCRC16;\n\n// @formatter:off\n/**\n * RedisClusterClient provides a high-level, unified interface for interacting with a Redis Cluster.\n * <p>\n * This class is intended as a modern replacement for the deprecated {@code JedisCluster} class. It\n * supports all cluster operations and is designed to work seamlessly with the {@link UnifiedJedis}\n * API, allowing for consistent usage patterns across standalone, sentinel, and cluster deployments.\n * <p>\n * <b>Usage:</b>\n *\n * <pre>{@code\n *   Set<HostAndPort> clusterNodes = new HashSet<>();\n *   clusterNodes.add(new HostAndPort(\"127.0.0.1\", 7000));\n *   RedisClusterClient client = RedisClusterClient.create(clusterNodes);\n *   client.set(\"key\", \"value\");\n *   String value = client.get(\"key\");\n * }</pre>\n * <p>\n * <b>Migration:</b> Users of {@code JedisCluster} are encouraged to migrate to this class for\n * improved API consistency, better resource management, and enhanced support for future Redis\n * features.\n * <p>\n * <b>Thread-safety:</b> This client is thread-safe and can be shared across multiple threads.\n * <p>\n * <b>Configuration:</b> Use the {@link #builder()} method for advanced configuration, or the\n * {@link #create(HostAndPort)} and {@link #create(Set)} factory methods for simple use cases.\n */\n// @formatter:on\npublic class RedisClusterClient extends UnifiedJedis {\n\n  public static final String INIT_NO_ERROR_PROPERTY = \"jedis.cluster.initNoError\";\n\n  /**\n   * Default timeout in milliseconds.\n   */\n  public static final int DEFAULT_TIMEOUT = 2000;\n\n  /**\n   * Default amount of attempts for executing a command\n   */\n  public static final int DEFAULT_MAX_ATTEMPTS = 5;\n\n  private final CommandFlagsRegistry commandFlagsRegistry;\n\n  private RedisClusterClient(CommandExecutor commandExecutor, ConnectionProvider connectionProvider,\n      CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache,\n      CommandFlagsRegistry commandFlagsRegistry) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n    this.commandFlagsRegistry = commandFlagsRegistry;\n  }\n\n  /**\n   * Creates a RedisClusterClient instance. The provided node is used to make the first contact with\n   * the cluster.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.RedisClusterClient#DEFAULT_TIMEOUT} ms\n   * is being used with {@value redis.clients.jedis.RedisClusterClient#DEFAULT_MAX_ATTEMPTS} maximum\n   * attempts.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param node Node to first connect to.\n   * @return a new {@link RedisClusterClient} instance\n   */\n  public static RedisClusterClient create(HostAndPort node) {\n    return builder().nodes(Collections.singleton(node))\n        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(DEFAULT_TIMEOUT).build())\n        .maxAttempts(DEFAULT_MAX_ATTEMPTS)\n        .maxTotalRetriesDuration(Duration.ofMillis((long) DEFAULT_TIMEOUT * DEFAULT_MAX_ATTEMPTS))\n        .build();\n  }\n\n  /**\n   * Creates a RedisClusterClient with multiple entry points.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.RedisClusterClient#DEFAULT_TIMEOUT} ms\n   * is being used with {@value redis.clients.jedis.RedisClusterClient#DEFAULT_MAX_ATTEMPTS} maximum\n   * attempts.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param nodes Nodes to connect to.\n   * @return a new {@link RedisClusterClient} instance\n   */\n  public static RedisClusterClient create(Set<HostAndPort> nodes) {\n    return builder().nodes(nodes)\n        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(DEFAULT_TIMEOUT).build())\n        .maxAttempts(DEFAULT_MAX_ATTEMPTS)\n        .maxTotalRetriesDuration(Duration.ofMillis((long) DEFAULT_TIMEOUT * DEFAULT_MAX_ATTEMPTS))\n        .build();\n  }\n\n  /**\n   * Creates a RedisClusterClient with multiple entry points and authentication.\n   * <p>\n   * Here, the default timeout of {@value redis.clients.jedis.Protocol#DEFAULT_TIMEOUT} ms is being\n   * used with {@value redis.clients.jedis.RedisClusterClient#DEFAULT_MAX_ATTEMPTS} maximum\n   * attempts.\n   * <p>\n   * This is a convenience factory method that uses the builder pattern internally.\n   * @param nodes Nodes to connect to.\n   * @param user Username for authentication.\n   * @param password Password for authentication.\n   * @return a new {@link RedisClusterClient} instance\n   */\n  public static RedisClusterClient create(Set<HostAndPort> nodes, String user, String password) {\n    return builder().nodes(nodes)\n        .clientConfig(DefaultJedisClientConfig.builder().user(user).password(password).build())\n        .maxAttempts(DEFAULT_MAX_ATTEMPTS).maxTotalRetriesDuration(\n          Duration.ofMillis((long) Protocol.DEFAULT_TIMEOUT * DEFAULT_MAX_ATTEMPTS))\n        .build();\n  }\n\n  /**\n   * Fluent builder for {@link RedisClusterClient} (Redis Cluster).\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  public static class Builder extends ClusterClientBuilder<RedisClusterClient> {\n\n    @Override\n    protected RedisClusterClient createClient() {\n      return new RedisClusterClient(commandExecutor, connectionProvider, commandObjects,\n          clientConfig.getRedisProtocol(), cache, getCommandFlags());\n    }\n  }\n\n  /**\n   * Create a new builder for configuring RedisClusterClient instances.\n   * @return a new {@link RedisClusterClient.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Returns all nodes that were configured to connect to in key-value pairs ({@link Map}).<br>\n   * Key is the HOST:PORT and the value is the connection pool.\n   * @return the map of all connections.\n   */\n  public Map<String, ConnectionPool> getClusterNodes() {\n    return ((ClusterConnectionProvider) provider).getNodes();\n  }\n\n  /**\n   * Returns the connection for one of the 16,384 slots.\n   * @param slot the slot to retrieve the connection for.\n   * @return connection of the provided slot. {@code close()} of this connection must be called\n   *         after use.\n   */\n  public Connection getConnectionFromSlot(int slot) {\n    return ((ClusterConnectionProvider) provider).getConnectionFromSlot(slot);\n  }\n\n  // commands\n  public long spublish(String channel, String message) {\n    return executeCommand(commandObjects.spublish(channel, message));\n  }\n\n  public long spublish(byte[] channel, byte[] message) {\n    return executeCommand(commandObjects.spublish(channel, message));\n  }\n\n  public void ssubscribe(final JedisShardedPubSub jedisPubSub, final String... channels) {\n    try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) {\n      jedisPubSub.proceed(connection, channels);\n    }\n  }\n\n  public void ssubscribe(BinaryJedisShardedPubSub jedisPubSub, final byte[]... channels) {\n    try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) {\n      jedisPubSub.proceed(connection, channels);\n    }\n  }\n  // commands\n\n  @Override\n  public ClusterPipeline pipelined() {\n    return new ClusterPipeline((ClusterConnectionProvider) provider,\n        (ClusterCommandObjects) commandObjects, commandFlagsRegistry);\n  }\n\n  /**\n   * @param doMulti param\n   * @return nothing\n   * @throws UnsupportedOperationException\n   */\n  @Override\n  public AbstractTransaction transaction(boolean doMulti) {\n    throw new UnsupportedOperationException();\n  }\n\n  public final <T> T executeCommandToReplica(CommandObject<T> commandObject) {\n    if (!(executor instanceof ClusterCommandExecutor)) {\n      throw new UnsupportedOperationException(\n          \"Support only execute to replica in ClusterCommandExecutor\");\n    }\n    return ((ClusterCommandExecutor) executor).executeCommandToReplica(commandObject);\n  }\n\n  /**\n   * Broadcast a command to all primary nodes in the cluster.\n   * <p>\n   * This method is useful for administrative commands that need to be executed on all primary nodes,\n   * such as {@code PING}, {@code CONFIG SET}, {@code FLUSHALL}, etc.\n   * </p>\n   * @param commandObject the command to broadcast\n   * @param <T> the return type of the command\n   * @return the aggregated reply from all primary nodes\n   * @throws UnsupportedOperationException if the executor is not a ClusterCommandExecutor\n   */\n  public final <T> T broadcastCommand(CommandObject<T> commandObject) {\n    if (!(executor instanceof ClusterCommandExecutor)) {\n      throw new UnsupportedOperationException(\n          \"Broadcast command is only supported in ClusterCommandExecutor\");\n    }\n    return ((ClusterCommandExecutor) executor).broadcastCommand(commandObject, true);\n  }\n\n  // ==================== Multi-Shard Command Methods ====================\n  // These methods execute commands across multiple Redis cluster shards when keys\n  // hash to different slots, aggregating the results appropriately.\n\n  private <T> T executeMultiShardCommand(List<CommandObject<T>> commandObjects) {\n    if (!(executor instanceof ClusterCommandExecutor)) {\n      throw new UnsupportedOperationException(\n          \"Multi-shard command is only supported in ClusterCommandExecutor\");\n    }\n    return ((ClusterCommandExecutor) executor).executeMultiShardCommand(commandObjects);\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes DEL on each shard,\n   * aggregating the results (sum of deleted keys).\n   * </p>\n   */\n  @Override\n  public long del(String... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().delMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes DEL on each shard,\n   * aggregating the results (sum of deleted keys).\n   * </p>\n   */\n  @Override\n  public long del(byte[]... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().delMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes EXISTS on each shard,\n   * aggregating the results (sum of existing keys).\n   * </p>\n   */\n  @Override\n  public long exists(String... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().existsMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes EXISTS on each shard,\n   * aggregating the results (sum of existing keys).\n   * </p>\n   */\n  @Override\n  public long exists(byte[]... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().existsMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes MGET on each shard,\n   * concatenating the results.\n   * </p>\n   *\n   * <p><b>Order Guarantee:</b> The returned values are in the same order as the input keys.\n   * Each {@code values.get(i)} corresponds to {@code keys[i]}.</p>\n   *\n   * <p><b>Performance Tip:</b> For better performance, pre-sort keys by hash slot before calling\n   * this method. This minimizes the number of separate Redis commands by allowing consecutive\n   * keys with the same slot to be batched together. Example:</p>\n   * <pre>{@code\n   * // Sort keys by hash slot for optimal batching\n   * String[] sortedKeys = Arrays.stream(keys)\n   *     .sorted(Comparator.comparingInt(JedisClusterCRC16::getSlot))\n   *     .toArray(String[]::new);\n   * List<String> values = client.mget(sortedKeys);\n   * }</pre>\n   */\n  @Override\n  public List<String> mget(String... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().mgetMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes MGET on each shard,\n   * concatenating the results.\n   * </p>\n   *\n   * <p><b>Order Guarantee:</b> The returned values are in the same order as the input keys.\n   * Each {@code values.get(i)} corresponds to {@code keys[i]}.</p>\n   *\n   * <p><b>Performance Tip:</b> For better performance, pre-sort keys by hash slot before calling\n   * this method. This minimizes the number of separate Redis commands by allowing consecutive\n   * keys with the same slot to be batched together. Example:</p>\n   * <pre>{@code\n   * // Sort keys by hash slot for optimal batching\n   * byte[][] sortedKeys = Arrays.stream(keys)\n   *     .sorted(Comparator.comparingInt(JedisClusterCRC16::getSlot))\n   *     .toArray(byte[][]::new);\n   * List<byte[]> values = client.mget(sortedKeys);\n   * }</pre>\n   */\n  @Override\n  public List<byte[]> mget(byte[]... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().mgetMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the key-value pairs by hash slot and executes MSET\n   * on each shard.\n   * </p>\n   */\n  @Override\n  public String mset(String... keysvalues) {\n    return executeMultiShardCommand(getClusterCommandObjects().msetMultiShard(keysvalues));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the key-value pairs by hash slot and executes MSET\n   * on each shard.\n   * </p>\n   */\n  @Override\n  public String mset(byte[]... keysvalues) {\n    return executeMultiShardCommand(getClusterCommandObjects().msetMultiShard(keysvalues));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes TOUCH on each shard,\n   * aggregating the results (sum of touched keys).\n   * </p>\n   */\n  @Override\n  public long touch(String... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().touchMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes TOUCH on each shard,\n   * aggregating the results (sum of touched keys).\n   * </p>\n   */\n  @Override\n  public long touch(byte[]... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().touchMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes UNLINK on each shard,\n   * aggregating the results (sum of unlinked keys).\n   * </p>\n   */\n  @Override\n  public long unlink(String... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().unlinkMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the keys by hash slot and executes UNLINK on each shard,\n   * aggregating the results (sum of unlinked keys).\n   * </p>\n   */\n  @Override\n  public long unlink(byte[]... keys) {\n    return executeMultiShardCommand(getClusterCommandObjects().unlinkMultiShard(keys));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the key-value pairs by hash slot and executes MSETEX\n   * on each shard with the provided parameters.\n   * </p>\n   */\n  @Override\n  public boolean msetex(MSetExParams params, String... keysvalues) {\n    return executeMultiShardCommand(getClusterCommandObjects().msetexMultiShard(params, keysvalues));\n  }\n\n  /**\n   * {@inheritDoc}\n   * <p>\n   * This override automatically splits the key-value pairs by hash slot and executes MSETEX\n   * on each shard with the provided parameters.\n   * </p>\n   */\n  @Override\n  public boolean msetex(MSetExParams params, byte[]... keysvalues) {\n    return executeMultiShardCommand(getClusterCommandObjects().msetexMultiShard(params, keysvalues));\n  }\n\n  private ClusterCommandObjects getClusterCommandObjects() {\n    return (ClusterCommandObjects) commandObjects;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/RedisCredentials.java",
    "content": "package redis.clients.jedis;\n\npublic interface RedisCredentials {\n\n  /**\n   * @return Redis ACL user\n   */\n  default String getUser() {\n    return null;\n  }\n\n  default char[] getPassword() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/RedisCredentialsProvider.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.function.Supplier;\n\npublic interface RedisCredentialsProvider extends Supplier<RedisCredentials> {\n\n  /**\n   * Prepare {@link RedisCredentials} before {@link RedisCredentialsProvider#get()} is called.\n   * <p>\n   * An application may:\n   * <ul>\n   * <li>Load credentials from the credentials management system</li>\n   * <li>Reload credentials when credentials are rotated.</li>\n   * <li>Reload credentials after an authentication error (e.g. NOAUTH, WRONGPASS, etc).</li>\n   * <li>Minimize the time that the password lives in the memory (in combination with\n   * {@link RedisCredentialsProvider#cleanUp()}).</li>\n   * </ul>\n   */\n  default void prepare() { }\n\n  /**\n   * Clean up credentials (e.g. from memory).\n   *\n   * @see RedisCredentialsProvider#prepare()\n   */\n  default void cleanUp() { }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/RedisProtocol.java",
    "content": "package redis.clients.jedis;\n\npublic enum RedisProtocol {\n\n  RESP2(\"2\"),\n  RESP3(\"3\");\n\n  private final String version;\n\n  private RedisProtocol(String ver) {\n    this.version = ver;\n  }\n\n  public String version() {\n    return version;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/RedisSentinelClient.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.builders.SentinelClientBuilder;\nimport redis.clients.jedis.csc.Cache;\n\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.SentineledConnectionProvider;\n\n// @formatter:off\n/**\n * A high-level client for interacting with Redis Sentinel-managed Redis deployments.\n * <p>\n * {@code RedisSentinelClient} provides robust support for automatic master failover, connection\n * management, and command execution in environments where Redis Sentinel is used to monitor and\n * manage Redis servers.\n * </p>\n * <p>\n * Usage:\n * </p>\n * \n * <pre>\n * RedisSentinelClient client = RedisSentinelClient.builder().sentinel(\"localhost\", 26379)\n *     .masterName(\"mymaster\").build();\n * </pre>\n * <p>\n * <b>Relationship to {@code JedisSentineled}:</b></p>\n * <ul>\n * <li>{@code RedisSentinelClient} is the recommended replacement for the deprecated\n * {@code JedisSentineled} class.</li>\n * <li>It offers improved API consistency, better failover handling, and a fluent builder for\n * configuration.</li>\n * <li>Use {@code RedisSentinelClient} for new codebases and when migrating from\n * {@code JedisSentineled}.</li>\n * </ul>\n */\n // @formatter:on\npublic class RedisSentinelClient extends UnifiedJedis {\n  private RedisSentinelClient(CommandExecutor commandExecutor,\n      ConnectionProvider connectionProvider, CommandObjects commandObjects,\n      RedisProtocol redisProtocol, Cache cache) {\n    super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);\n  }\n\n  /**\n   * Fluent builder for {@link RedisSentinelClient} (Redis Sentinel).\n   * <p>\n   * Obtain an instance via {@link #builder()}.\n   * </p>\n   */\n  public static class Builder extends SentinelClientBuilder<RedisSentinelClient> {\n\n    @Override\n    protected RedisSentinelClient createClient() {\n      return new RedisSentinelClient(commandExecutor, connectionProvider, commandObjects,\n          clientConfig.getRedisProtocol(), cache);\n    }\n  }\n\n  /**\n   * Create a new builder for configuring RedisSentinelClient instances.\n   * @return a new {@link RedisSentinelClient.Builder} instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public HostAndPort getCurrentMaster() {\n    return ((SentineledConnectionProvider) provider).getCurrentMaster();\n  }\n\n  @Override\n  public Pipeline pipelined() {\n    return (Pipeline) super.pipelined();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ReliableTransaction.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.Command.DISCARD;\nimport static redis.clients.jedis.Protocol.Command.EXEC;\nimport static redis.clients.jedis.Protocol.Command.MULTI;\nimport static redis.clients.jedis.Protocol.Command.UNWATCH;\n\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\n\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.exceptions.JedisException;\n\n/**\n * A transaction where commands are immediately sent to Redis server and the {@code QUEUED} reply checked.\n */\npublic class ReliableTransaction extends AbstractTransaction {\n\n  private static final String QUEUED_STR = \"QUEUED\";\n\n  private final Queue<Response<?>> pipelinedResponses = new LinkedList<>();\n  protected final Connection connection;\n  private final boolean closeConnection;\n\n  private boolean broken = false;\n  private boolean inWatch = false;\n  private boolean inMulti = false;\n\n  /**\n   * Creates a new transaction.\n   * \n   * A MULTI command will be executed. WATCH/UNWATCH/MULTI commands must not be called with this object.\n   * @param connection connection\n   */\n  public ReliableTransaction(Connection connection) {\n    this(connection, true);\n  }\n\n  /**\n   * Creates a new transaction.\n   *\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   *\n   * @param connection connection\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   */\n  public ReliableTransaction(Connection connection, boolean doMulti) {\n    this(connection, doMulti, false);\n  }\n\n  /**\n   * Creates a new transaction.\n   *\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   *\n   * @param connection connection\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @param closeConnection should the 'connection' be closed when 'close()' is called?\n   */\n  public ReliableTransaction(Connection connection, boolean doMulti, boolean closeConnection) {\n    this(connection, doMulti, closeConnection, createCommandObjects(connection));\n  }\n\n  /**\n   * Creates a new transaction.\n   *\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   *\n   * @param connection connection\n   * @param commandObjects command objects\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @param closeConnection should the 'connection' be closed when 'close()' is called?\n   */\n  ReliableTransaction(Connection connection, boolean doMulti, boolean closeConnection, CommandObjects commandObjects) {\n    super(commandObjects);\n    this.connection = connection;\n    this.closeConnection = closeConnection;\n    if (doMulti) multi();\n  }\n\n  private static CommandObjects createCommandObjects(Connection connection) {\n    CommandObjects commandObjects = new CommandObjects();\n    RedisProtocol proto = connection.getRedisProtocol();\n    if (proto != null) commandObjects.setProtocol(proto);\n    return commandObjects;\n  }\n\n  @Override\n  public final void multi() {\n    connection.sendCommand(MULTI);\n    String status = connection.getStatusCodeReply();\n    if (!\"OK\".equals(status)) {\n      throw new JedisException(\"MULTI command failed. Received response: \" + status);\n    }\n    inMulti = true;\n  }\n\n  @Override\n  public String watch(final String... keys) {\n    String status = connection.executeCommand(commandObjects.watch(keys));\n    inWatch = true;\n    return status;\n  }\n\n  @Override\n  public String watch(final byte[]... keys) {\n    String status = connection.executeCommand(commandObjects.watch(keys));\n    inWatch = true;\n    return status;\n  }\n\n  @Override\n  public String unwatch() {\n    connection.sendCommand(UNWATCH);\n    String status = connection.getStatusCodeReply();\n    inWatch = false;\n    return status;\n  }\n\n  @Override\n  protected final <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n    connection.sendCommand(commandObject.getArguments());\n    String status = connection.getStatusCodeReply();\n    if (!QUEUED_STR.equals(status)) {\n      throw new JedisException(status);\n    }\n    Response<T> response = new Response<>(commandObject.getBuilder());\n    pipelinedResponses.add(response);\n    return response;\n  }\n\n  @Override\n  public final void close() {\n    try {\n      clear();\n    } finally {\n      if (closeConnection) {\n        connection.close();\n      }\n    }\n  }\n\n  @Deprecated // TODO: private\n  public final void clear() {\n    if (broken) {\n      return;\n    }\n    if (inMulti) {\n      discard();\n    } else if (inWatch) {\n      unwatch();\n    }\n  }\n\n  @Override\n  public List<Object> exec() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"EXEC without MULTI\");\n    }\n\n    try {\n      // processPipelinedResponses(pipelinedResponses.size());\n      // do nothing\n      connection.sendCommand(EXEC);\n\n      List<Object> unformatted = connection.getObjectMultiBulkReply();\n      if (unformatted == null) {\n        pipelinedResponses.clear();\n        return null;\n      }\n\n      List<Object> formatted = new ArrayList<>(unformatted.size());\n      for (Object o : unformatted) {\n        try {\n          Response<?> response = pipelinedResponses.poll();\n          response.set(o);\n          formatted.add(response.get());\n        } catch (JedisDataException e) {\n          formatted.add(e);\n        }\n      }\n      return formatted;\n    } catch (JedisConnectionException jce) {\n      broken = true;\n      throw jce;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      pipelinedResponses.clear();\n    }\n  }\n\n  @Override\n  public String discard() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"DISCARD without MULTI\");\n    }\n\n    try {\n      // processPipelinedResponses(pipelinedResponses.size());\n      // do nothing\n      connection.sendCommand(DISCARD);\n      String status = connection.getStatusCodeReply();\n      if (!\"OK\".equals(status)) {\n        throw new JedisException(\"DISCARD command failed. Received response: \" + status);\n      }\n      return status;\n    } catch (JedisConnectionException jce) {\n      broken = true;\n      throw jce;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      pipelinedResponses.clear();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Response.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.function.Supplier;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\npublic class Response<T> implements Supplier<T> {\n  protected T response = null;\n  protected JedisDataException exception = null;\n\n  private boolean building = false;\n  private boolean built = false;\n  private boolean set = false;\n\n  private Builder<T> builder;\n  private Object data;\n  private Response<?> dependency = null;\n\n  public Response(Builder<T> b) {\n    this.builder = b;\n  }\n\n  public void set(Object data) {\n    this.data = data;\n    set = true;\n  }\n\n  @Override\n  public T get() {\n    // if response has dependency response and dependency is not built, build it first and no more!!\n    if (dependency != null && dependency.set && !dependency.built) {\n      dependency.build();\n    }\n    if (!set) {\n      throw new IllegalStateException(\n          \"Please close pipeline or multi block before calling this method.\");\n    }\n    if (!built) {\n      build();\n    }\n    if (exception != null) {\n      throw exception;\n    }\n    return response;\n  }\n\n  public void setDependency(Response<?> dependency) {\n    this.dependency = dependency;\n  }\n\n  private void build() {\n    // check build state to prevent recursion\n    if (building) {\n      return;\n    }\n\n    building = true;\n    try {\n      if (data != null) {\n        if (data instanceof JedisDataException) {\n          exception = (JedisDataException) data;\n        } else {\n          response = builder.build(data);\n        }\n      }\n\n      data = null;\n    } finally {\n      building = false;\n      built = true;\n    }\n  }\n\n  @Override\n  public String toString() {\n    return \"Response \" + builder.toString();\n  }\n\n  static class DecodedResponse<T> extends Response<T> {\n\n   private DecodedResponse(T response, JedisDataException exception) {\n      super(null);\n      this.exception = exception;\n      this.response = response;\n    }\n\n    @Override\n    public T get() {\n      if (exception != null) {\n        throw exception;\n      }\n\n      return response;\n    }\n\n    @Override\n    public String toString() {\n        return  \"Response<T>\";\n    }\n  }\n\n   static <T> Response<T> of(T response) {\n      return new DecodedResponse<>(response, null);\n   }\n\n   static <T> Response<T> error(JedisDataException exception) {\n      return new DecodedResponse<>(null, exception);\n   }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/SSLSocketWrapper.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.InetAddress;\nimport java.net.Socket;\nimport java.net.SocketAddress;\nimport java.net.SocketException;\nimport java.util.function.BiFunction;\nimport java.util.List;\nimport javax.net.ssl.HandshakeCompletedListener;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSocket;\n\npublic class SSLSocketWrapper extends SSLSocket {\n\n  SSLSocket actual;\n  Socket underlying;\n  InputStream wrapper;\n\n  private class InputStreamWrapper extends InputStream {\n    private InputStream actual;\n    private InputStream underlying;\n\n    public InputStreamWrapper(InputStream actual, InputStream underlying) {\n      this.actual = actual;\n      this.underlying = underlying;\n    }\n\n    @Override\n    public int read() throws IOException {\n      return actual.read();\n    }\n\n    @Override\n    public int read(byte[] b) throws IOException {\n      return actual.read(b);\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n      return actual.read(b, off, len);\n    }\n\n    @Override\n    public long skip(long n) throws IOException {\n      return actual.skip(n);\n    }\n\n    @Override\n    public int available() throws IOException {\n      return underlying.available();\n    }\n\n    @Override\n    public void close() throws IOException {\n      actual.close();\n    }\n\n    @Override\n    public synchronized void mark(int readlimit) {\n      actual.mark(readlimit);\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n      actual.reset();\n    }\n\n    @Override\n    public boolean markSupported() {\n      return actual.markSupported();\n    }\n  }\n\n  public SSLSocketWrapper(SSLSocket actual, Socket underlying) throws IOException {\n    this.actual = actual;\n    this.underlying = underlying;\n    this.wrapper = new InputStreamWrapper(actual.getInputStream(), underlying.getInputStream());\n  }\n\n  @Override\n  public void connect(SocketAddress endpoint) throws IOException {\n    actual.connect(endpoint);\n  }\n\n  @Override\n  public void connect(SocketAddress endpoint, int timeout) throws IOException {\n    actual.connect(endpoint, timeout);\n  }\n\n  @Override\n  public void bind(SocketAddress bindpoint) throws IOException {\n    actual.bind(bindpoint);\n  }\n\n  @Override\n  public InetAddress getInetAddress() {\n    return actual.getInetAddress();\n  }\n\n  @Override\n  public InetAddress getLocalAddress() {\n    return actual.getLocalAddress();\n  }\n\n  @Override\n  public int getPort() {\n    return actual.getPort();\n  }\n\n  @Override\n  public int getLocalPort() {\n    return actual.getLocalPort();\n  }\n\n  @Override\n  public SocketAddress getRemoteSocketAddress() {\n    return actual.getRemoteSocketAddress();\n  }\n\n  @Override\n  public SocketAddress getLocalSocketAddress() {\n    return actual.getLocalSocketAddress();\n  }\n\n  @Override\n  public void setTcpNoDelay(boolean on) throws SocketException {\n    actual.setTcpNoDelay(on);\n  }\n\n  @Override\n  public boolean getTcpNoDelay() throws SocketException {\n    return actual.getTcpNoDelay();\n  }\n\n  @Override\n  public void setSoLinger(boolean on, int linger) throws SocketException {\n    actual.setSoLinger(on, linger);\n  }\n\n  @Override\n  public int getSoLinger() throws SocketException {\n    return actual.getSoLinger();\n  }\n\n  @Override\n  public void sendUrgentData(int data) throws IOException {\n    actual.sendUrgentData(data);\n  }\n\n  @Override\n  public void setOOBInline(boolean on) throws SocketException {\n    actual.setOOBInline(on);\n  }\n\n  @Override\n  public boolean getOOBInline() throws SocketException {\n    return actual.getOOBInline();\n  }\n\n  @Override\n  public synchronized void setSoTimeout(int timeout) throws SocketException {\n    actual.setSoTimeout(timeout);\n  }\n\n  @Override\n  public synchronized int getSoTimeout() throws SocketException {\n    return actual.getSoTimeout();\n  }\n\n  @Override\n  public synchronized void setSendBufferSize(int size) throws SocketException {\n    actual.setSendBufferSize(size);\n  }\n\n  @Override\n  public synchronized int getSendBufferSize() throws SocketException {\n    return actual.getSendBufferSize();\n  }\n\n  @Override\n  public synchronized void setReceiveBufferSize(int size) throws SocketException {\n    actual.setReceiveBufferSize(size);\n  }\n\n  @Override\n  public synchronized int getReceiveBufferSize() throws SocketException {\n    return actual.getReceiveBufferSize();\n  }\n\n  @Override\n  public void setKeepAlive(boolean on) throws SocketException {\n    actual.setKeepAlive(on);\n  }\n\n  @Override\n  public boolean getKeepAlive() throws SocketException {\n    return actual.getKeepAlive();\n  }\n\n  @Override\n  public void setTrafficClass(int tc) throws SocketException {\n    actual.setTrafficClass(tc);\n  }\n\n  @Override\n  public int getTrafficClass() throws SocketException {\n    return actual.getTrafficClass();\n  }\n\n  @Override\n  public void setReuseAddress(boolean on) throws SocketException {\n    actual.setReuseAddress(on);\n  }\n\n  @Override\n  public boolean getReuseAddress() throws SocketException {\n    return actual.getReuseAddress();\n  }\n\n  @Override\n  public synchronized void close() throws IOException {\n    actual.close();\n  }\n\n  @Override\n  public void shutdownInput() throws IOException {\n    actual.shutdownInput();\n  }\n\n  @Override\n  public void shutdownOutput() throws IOException {\n    actual.shutdownOutput();\n  }\n\n  @Override\n  public String toString() {\n    return actual.toString();\n  }\n\n  @Override\n  public boolean isConnected() {\n    return actual.isConnected();\n  }\n\n  @Override\n  public boolean isBound() {\n    return actual.isBound();\n  }\n\n  @Override\n  public boolean isClosed() {\n    return actual.isClosed();\n  }\n\n  @Override\n  public boolean isInputShutdown() {\n    return actual.isInputShutdown();\n  }\n\n  @Override\n  public boolean isOutputShutdown() {\n    return actual.isOutputShutdown();\n  }\n\n  @Override\n  public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {\n    actual.setPerformancePreferences(connectionTime, latency, bandwidth);\n  }\n\n  @Override\n  public InputStream getInputStream() throws IOException {\n    return wrapper;\n  }\n\n  @Override\n  public OutputStream getOutputStream() throws IOException {\n    return actual.getOutputStream();\n  }\n\n  @Override\n  public String[] getSupportedCipherSuites() {\n    return actual.getSupportedCipherSuites();\n  }\n\n  @Override\n  public String[] getEnabledCipherSuites() {\n    return actual.getEnabledCipherSuites();\n  }\n\n  @Override\n  public void setEnabledCipherSuites(String[] var1) {\n    actual.setEnabledCipherSuites(var1);\n  }\n\n  @Override\n  public String[] getSupportedProtocols() {\n    return actual.getSupportedProtocols();\n  }\n\n  @Override\n  public String[] getEnabledProtocols() {\n    return actual.getEnabledProtocols();\n  }\n\n  @Override\n  public void setEnabledProtocols(String[] var1) {\n    actual.setEnabledProtocols(var1);\n  }\n\n  @Override\n  public SSLSession getSession() {\n    return actual.getSession();\n  }\n\n  @Override\n  public SSLSession getHandshakeSession() {\n    return actual.getHandshakeSession();\n  }\n\n  @Override\n  public void addHandshakeCompletedListener(HandshakeCompletedListener var1) {\n    actual.addHandshakeCompletedListener(var1);\n  }\n\n  @Override\n  public void removeHandshakeCompletedListener(HandshakeCompletedListener var1) {\n    actual.removeHandshakeCompletedListener(var1);\n  }\n\n  @Override\n  public void startHandshake() throws IOException {\n    actual.startHandshake();\n  }\n\n  @Override\n  public void setUseClientMode(boolean var1) {\n    actual.setUseClientMode(var1);\n  }\n\n  @Override\n  public boolean getUseClientMode() {\n    return actual.getUseClientMode();\n  }\n\n  @Override\n  public void setNeedClientAuth(boolean var1) {\n    actual.setNeedClientAuth(var1);\n  }\n\n  @Override\n  public boolean getNeedClientAuth() {\n    return actual.getNeedClientAuth();\n  }\n\n  @Override\n  public void setWantClientAuth(boolean var1) {\n    actual.setWantClientAuth(var1);\n  }\n\n  @Override\n  public boolean getWantClientAuth() {\n    return actual.getWantClientAuth();\n  }\n\n  @Override\n  public void setEnableSessionCreation(boolean var1) {\n    actual.setEnableSessionCreation(var1);\n  }\n\n  @Override\n  public boolean getEnableSessionCreation() {\n    return actual.getEnableSessionCreation();\n  }\n\n  @Override\n  public SSLParameters getSSLParameters() {\n    return actual.getSSLParameters();\n  }\n\n  @Override\n  public void setSSLParameters(SSLParameters var1) {\n    actual.setSSLParameters(var1);\n  }\n\n  @Override\n  public String getApplicationProtocol() {\n    return actual.getApplicationProtocol();\n  }\n\n  @Override\n  public String getHandshakeApplicationProtocol() {\n    return actual.getHandshakeApplicationProtocol();\n  }\n\n  @Override\n  public void setHandshakeApplicationProtocolSelector(BiFunction<SSLSocket, List<String>, String> var1) {\n    actual.setHandshakeApplicationProtocolSelector(var1);\n  }\n\n  @Override\n  public BiFunction<SSLSocket, List<String>, String> getHandshakeApplicationProtocolSelector() {\n    return actual.getHandshakeApplicationProtocolSelector();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/ScanIteration.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.Collection;\nimport java.util.function.Function;\n\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.JedisCommandIterationBase;\n\npublic class ScanIteration extends JedisCommandIterationBase<ScanResult<String>, String> {\n\n  private final int count;\n  private final Function<String, CommandArguments> args;\n\n  public ScanIteration(ConnectionProvider connectionProvider, int batchCount, String match) {\n    super(connectionProvider, BuilderFactory.SCAN_RESPONSE);\n    this.count = batchCount;\n    this.args = (cursor) -> new CommandArguments(Protocol.Command.SCAN).add(cursor)\n        .add(Keyword.MATCH).add(match).add(Keyword.COUNT).add(count);\n  }\n\n  public ScanIteration(ConnectionProvider connectionProvider, int batchCount, String match, String type) {\n    super(connectionProvider, BuilderFactory.SCAN_RESPONSE);\n    this.count = batchCount;\n    this.args = (cursor) -> new CommandArguments(Protocol.Command.SCAN).add(cursor)\n        .add(Keyword.MATCH).add(match).add(Keyword.COUNT).add(count).add(Keyword.TYPE).add(type);\n  }\n\n  @Override\n  protected boolean isNodeCompleted(ScanResult<String> reply) {\n    return reply.isCompleteIteration();\n  }\n\n  @Override\n  protected CommandArguments initCommandArguments() {\n    return args.apply(ScanParams.SCAN_POINTER_START);\n  }\n\n  @Override\n  protected CommandArguments nextCommandArguments(ScanResult<String> lastReply) {\n    return args.apply(lastReply.getCursor());\n  }\n\n  @Override\n  protected Collection<String> convertBatchToData(ScanResult<String> batch) {\n    return batch.getResult();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/SslOptions.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.Socket;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.Objects;\n\nimport java.security.GeneralSecurityException;\nimport java.security.KeyStore;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\n\nimport javax.net.ssl.KeyManager;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509ExtendedTrustManager;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Options to configure SSL options for the connections kept to Redis servers.\n *\n * @author Mark Paluch\n */\npublic class SslOptions {\n\n    private static final Logger logger = LoggerFactory.getLogger(SslOptions.class);\n\n    private final String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm();\n\n    private final String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();\n\n    private final String keyStoreType;\n\n    private final String trustStoreType;\n\n    private final Resource keystoreResource;\n\n    private final char[] keystorePassword;\n\n    private final Resource truststoreResource;\n\n    private final char[] truststorePassword;\n\n    private final SSLParameters sslParameters;\n\n    private final SslVerifyMode sslVerifyMode;\n\n    private final String sslProtocol; // protocol for SSLContext\n\n    private SslOptions(Builder builder) {\n        this.keyStoreType = builder.keyStoreType;\n        this.trustStoreType = builder.trustStoreType;\n        this.keystoreResource = builder.keystoreResource;\n        this.keystorePassword = builder.keystorePassword;\n        this.truststoreResource = builder.truststoreResource;\n        this.truststorePassword = builder.truststorePassword;\n        this.sslParameters = builder.sslParameters;\n        this.sslVerifyMode = builder.sslVerifyMode;\n        this.sslProtocol = builder.sslProtocol;\n    }\n\n    /**\n     * Returns a new {@link SslOptions.Builder} to construct {@link SslOptions}.\n     *\n     * @return a new {@link SslOptions.Builder} to construct {@link SslOptions}.\n     */\n    public static SslOptions.Builder builder() {\n        return new SslOptions.Builder();\n    }\n\n    /**\n     * Builder for {@link SslOptions}.\n     */\n    public static class Builder {\n\n        private String keyStoreType;\n\n        private String trustStoreType;\n\n        private Resource keystoreResource;\n\n        private char[] keystorePassword = null;\n\n        private Resource truststoreResource;\n\n        private char[] truststorePassword = null;\n\n        private SSLParameters sslParameters;\n\n        private SslVerifyMode sslVerifyMode = SslVerifyMode.FULL;\n\n        private String sslProtocol = \"TLS\"; // protocol for SSLContext\n\n        private Builder() {\n        }\n\n        /**\n         * Sets the KeyStore type. Defaults to {@link KeyStore#getDefaultType()} if not set.\n         *\n         * @param keyStoreType the keystore type to use, must not be {@code null}.\n         * @return {@code this}\n         */\n        public Builder keyStoreType(String keyStoreType) {\n            this.keyStoreType = Objects.requireNonNull(keyStoreType, \"KeyStoreType must not be null\");\n            return this;\n        }\n\n        /**\n         * Sets the KeyStore type. Defaults to {@link KeyStore#getDefaultType()} if not set.\n         *\n         * @param trustStoreType the keystore type to use, must not be {@code null}.\n         * @return {@code this}\n         */\n        public Builder trustStoreType(String trustStoreType) {\n            this.trustStoreType = Objects.requireNonNull(trustStoreType, \"TrustStoreType must not be null\");\n            return this;\n        }\n\n        /**\n         * Sets the Keystore file to load client certificates. The key store file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The keystore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param keystore the keystore file, must not be {@code null}.\n         * @return {@code this}\n         */\n        public Builder keystore(File keystore) {\n            return keystore(keystore, null);\n        }\n\n        /**\n         * Sets the Keystore file to load client certificates. The keystore file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The keystore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param keystore the keystore file, must not be {@code null}.\n         * @param keystorePassword the keystore password. May be empty to omit password and the keystore integrity check.\n         * @return {@code this}\n         */\n        public Builder keystore(File keystore, char[] keystorePassword) {\n\n            Objects.requireNonNull(keystore, \"Keystore must not be null\");\n            assertFile(\"Keystore\", keystore);\n\n            return keystore(Resource.from(keystore), keystorePassword);\n        }\n\n        /**\n         * Sets the Keystore resource to load client certificates. The keystore file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The keystore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param keystore the keystore URL, must not be {@code null}.\n         * @return {@code this}\n         */\n        public Builder keystore(URL keystore) {\n            return keystore(keystore, null);\n        }\n\n        /**\n         * Sets the Keystore resource to load client certificates. The keystore file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The keystore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param keystore the keystore file, must not be {@code null}.\n         * @param keystorePassword\n         * @return {@code this}\n         */\n        public Builder keystore(URL keystore, char[] keystorePassword) {\n\n            Objects.requireNonNull(keystore, \"Keystore must not be null\");\n\n            return keystore(Resource.from(keystore), keystorePassword);\n        }\n\n        /**\n         * Sets the Java Keystore resource to load client certificates. The keystore file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The keystore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param resource the provider that opens a {@link InputStream} to the keystore file, must not be {@code null}.\n         * @param keystorePassword the keystore password. May be empty to omit password and the keystore integrity check.\n         * @return {@code this}\n         */\n        public Builder keystore(Resource resource, char[] keystorePassword) {\n\n            this.keystoreResource = Objects.requireNonNull(resource, \"Keystore InputStreamProvider must not be null\");\n\n            this.keystorePassword = getPassword(keystorePassword);\n\n            return this;\n        }\n\n        /**\n         * Sets the Truststore file to load trusted certificates. The truststore file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The truststore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param truststore the truststore file, must not be {@code null}.\n         * @return {@code this}\n         */\n        public Builder truststore(File truststore) {\n            return truststore(truststore, null);\n        }\n\n        /**\n         * Sets the Truststore file to load trusted certificates. The truststore file must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The truststore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param truststore the truststore file, must not be {@code null}.\n         * @param truststorePassword the truststore password. May be empty to omit password and the truststore integrity check.\n         * @return {@code this}\n         */\n        public Builder truststore(File truststore, char[] truststorePassword) {\n\n            Objects.requireNonNull(truststore, \"Truststore must not be null\");\n            assertFile(\"Truststore\", truststore);\n\n            return truststore(Resource.from(truststore), truststorePassword);\n        }\n\n        /**\n         * Sets the Truststore resource to load trusted certificates. The truststore resource must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The truststore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param truststore the truststore file, must not be {@code null}.\n         * @return {@code this}\n         */\n        public Builder truststore(URL truststore) {\n            return truststore(truststore, null);\n        }\n\n        /**\n         * Sets the Truststore resource to load trusted certificates. The truststore resource must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The truststore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param truststore the truststore file, must not be {@code null}.\n         * @param truststorePassword the truststore password. May be empty to omit password and the truststore integrity check.\n         * @return {@code this}\n         */\n        public Builder truststore(URL truststore, char[] truststorePassword) {\n\n            Objects.requireNonNull(truststore, \"Truststore must not be null\");\n\n            return truststore(Resource.from(truststore), truststorePassword);\n        }\n\n        /**\n         * Sets the Truststore resource to load trusted certificates. The truststore resource must be supported by\n         * {@link java.security.KeyStore} which is {@link KeyStore#getDefaultType()} by default. The truststore is reloaded on\n         * each connection attempt that allows to replace certificates during runtime.\n         *\n         * @param resource the provider that opens a {@link InputStream} to the keystore file, must not be {@code null}.\n         * @param truststorePassword the truststore password. May be empty to omit password and the truststore integrity check.\n         * @return {@code this}\n         */\n        public Builder truststore(Resource resource, char[] truststorePassword) {\n\n            this.truststoreResource = Objects.requireNonNull(resource, \"Truststore InputStreamProvider must not be null\");\n\n            this.truststorePassword = getPassword(truststorePassword);\n\n            return this;\n        }\n\n        /**\n         * Sets a configured {@link SSLParameters}.\n         *\n         * @param sslParameters a {@link SSLParameters} object.\n         * @return {@code this}\n         */\n        public Builder sslParameters(SSLParameters sslParameters) {\n            this.sslParameters = sslParameters;\n            return this;\n        }\n\n        /**\n         * Sets the {@link SslVerifyMode}.\n         *\n         * @param sslVerifyMode the {@link SslVerifyMode}.\n         * @return {@code this}\n         */\n        public Builder sslVerifyMode(SslVerifyMode sslVerifyMode) {\n            this.sslVerifyMode = sslVerifyMode;\n            return this;\n        }\n\n        /**\n         * The SSL/TLS protocol to be used to initialize {@link SSLContext}.\n         * @param protocol the ssl/tls protocol\n         * @return {@code this}\n         */\n        public Builder sslProtocol(String protocol) {\n            this.sslProtocol = protocol;\n            return this;\n        }\n\n        /**\n         * Create a new instance of {@link SslOptions}\n         *\n         * @return new instance of {@link SslOptions}\n         */\n        public SslOptions build() {\n            if (this.sslParameters == null) {\n                this.sslParameters = new SSLParameters();\n            }\n            return new SslOptions(this);\n        }\n    }\n\n    /**\n     * A {@link SSLContext} object that is configured with values from this {@link SslOptions} object.\n     *\n     * @return {@link SSLContext}\n     * @throws IOException thrown when loading the keystore or the truststore fails.\n     * @throws GeneralSecurityException thrown when loading the keystore or the truststore fails.\n     */\n    public SSLContext createSslContext() throws IOException, GeneralSecurityException {\n\n        KeyManager[] keyManagers = null;\n        TrustManager[] trustManagers = null;\n\n        if (sslVerifyMode == SslVerifyMode.FULL) {\n            this.sslParameters.setEndpointIdentificationAlgorithm(\"HTTPS\");\n        } else if (sslVerifyMode == SslVerifyMode.CA) {\n            this.sslParameters.setEndpointIdentificationAlgorithm(\"\");\n        } else if (sslVerifyMode == SslVerifyMode.INSECURE) {\n            trustManagers = new TrustManager[] { INSECURE_TRUST_MANAGER };\n        }\n\n        if (keystoreResource != null) {\n\n            KeyStore keyStore = KeyStore.getInstance(keyStoreType==null ? KeyStore.getDefaultType() : keyStoreType);\n            try (InputStream keystoreStream = keystoreResource.get()) {\n                keyStore.load(keystoreStream, keystorePassword);\n            }\n\n            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgorithm);\n            keyManagerFactory.init(keyStore, keystorePassword);\n            keyManagers = keyManagerFactory.getKeyManagers();\n        }\n\n        if (trustManagers == null && truststoreResource != null) {\n\n\n            KeyStore trustStore = KeyStore.getInstance(trustStoreType == null ? KeyStore.getDefaultType() : trustStoreType);\n            try (InputStream truststoreStream = truststoreResource.get()) {\n                trustStore.load(truststoreStream, truststorePassword);\n            }\n\n            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm);\n            trustManagerFactory.init(trustStore);\n            trustManagers = trustManagerFactory.getTrustManagers();\n        }\n\n        SSLContext sslContext = SSLContext.getInstance(sslProtocol);\n        sslContext.init(keyManagers, trustManagers, null);\n\n        return sslContext;\n    }\n\n    /**\n     * {@link #createSslContext()} must be called before this.\n     * @return {@link SSLParameters}\n     */\n    public SSLParameters getSslParameters() {\n        return sslParameters;\n    }\n\n\n    /**\n     * Configured ssl verify mode.\n     * @return {@link SslVerifyMode}\n     */\n    public SslVerifyMode getSslVerifyMode() {\n        return sslVerifyMode;\n    }\n\n    private static char[] getPassword(char[] chars) {\n        return chars != null ? Arrays.copyOf(chars, chars.length) : null;\n    }\n\n    /**\n     * Assert that {@code file} {@link File#exists() exists}.\n     *\n     * @param keyword file recognizer\n     * @param file\n     * @throws IllegalArgumentException if the file doesn't exist\n     */\n    public static void assertFile(String keyword, File file) {\n        if (!file.exists()) {\n            throw new IllegalArgumentException(String.format(\"%s file %s does not exist\", keyword, file));\n        }\n        if (!file.isFile()) {\n            throw new IllegalArgumentException(String.format(\"%s file %s is not a file\", keyword, file));\n        }\n    }\n\n    /**\n     * Supplier for a {@link InputStream} representing a resource. The resulting {@link InputStream} must be closed by\n     * the calling code.\n     */\n    @FunctionalInterface\n    public interface Resource {\n\n        /**\n         * Create a {@link Resource} that obtains a {@link InputStream} from a {@link URL}.\n         *\n         * @param url the URL to obtain the {@link InputStream} from.\n         * @return a {@link Resource} that opens a connection to the URL and obtains the {@link InputStream} for it.\n         */\n        static Resource from(URL url) {\n\n            Objects.requireNonNull(url, \"URL must not be null\");\n\n            return () -> url.openConnection().getInputStream();\n        }\n\n        /**\n         * Create a {@link Resource} that obtains a {@link InputStream} from a {@link File}.\n         *\n         * @param file the File to obtain the {@link InputStream} from.\n         * @return a {@link Resource} that obtains the {@link FileInputStream} for the given {@link File}.\n         */\n        static Resource from(File file) {\n\n            Objects.requireNonNull(file, \"File must not be null\");\n\n            return () -> new FileInputStream(file);\n        }\n\n        /**\n         * Obtains the {@link InputStream}.\n         *\n         * @return the {@link InputStream}.\n         * @throws IOException\n         */\n        InputStream get() throws IOException;\n\n    }\n\n    private static final X509Certificate[] EMPTY_X509_CERTIFICATES = {};\n\n    private static final TrustManager INSECURE_TRUST_MANAGER = new X509ExtendedTrustManager() {\n\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String s) {\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"Accepting a client certificate: \" + chain[0].getSubjectDN());\n            }\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String s) {\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"Accepting a server certificate: \" + chain[0].getSubjectDN());\n            }\n        }\n\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String s, Socket socket)\n                throws CertificateException {\n            checkClientTrusted(chain, s);\n        }\n\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String s, SSLEngine sslEngine)\n                throws CertificateException {\n            checkClientTrusted(chain, s);\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String s, Socket socket)\n                throws CertificateException {\n            checkServerTrusted(chain, s);\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String s, SSLEngine sslEngine)\n                throws CertificateException {\n            checkServerTrusted(chain, s);\n        }\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n            return EMPTY_X509_CERTIFICATES;\n        }\n    };\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/SslVerifyMode.java",
    "content": "package redis.clients.jedis;\n\n/**\n * Enumeration of SSL/TLS hostname verification modes.\n */\npublic enum SslVerifyMode {\n\n    /**\n     * DO NOT USE THIS IN PRODUCTION.\n     * <p>\n     * No verification at all.\n     */\n    INSECURE,\n\n    /**\n     * Verify the CA and certificate without verifying that the hostname matches.\n     */\n    CA,\n\n    /**\n     * Full certificate verification.\n     */\n    FULL;\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/StaticCommandFlagsRegistry.java",
    "content": "package redis.clients.jedis;\n\nimport redis.clients.jedis.annots.Internal;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.JedisByteMap;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.util.EnumSet;\nimport java.util.Map;\n\n/**\n * Static implementation of CommandFlagsRegistry.\n */\n@Internal\npublic class StaticCommandFlagsRegistry implements CommandFlagsRegistry {\n\n  // Empty flags constant for commands with no flags\n  public static final EnumSet<CommandFlag> EMPTY_FLAGS = EnumSet.noneOf(CommandFlag.class);\n\n  // Default request policy for commands without a specific policy\n  public static final RequestPolicy DEFAULT_REQUEST_POLICY = RequestPolicy.DEFAULT;\n\n  // Default response policy for commands without a specific policy\n  public static final ResponsePolicy DEFAULT_RESPONSE_POLICY = ResponsePolicy.DEFAULT;\n\n  // Singleton instance\n  private static final StaticCommandFlagsRegistry REGISTRY = createRegistry();\n\n  private final Commands commands;\n\n  private StaticCommandFlagsRegistry(Commands commands) {\n    this.commands = commands;\n  }\n\n  /**\n   * Get the singleton instance of the static command flags registry.\n   * <p>\n   * DO NOT USE THIS METHOD UNLESS YOU KNOW WHAT YOU ARE DOING.\n   * </p>\n   * @return StaticCommandFlagsRegistry\n   */\n  public static StaticCommandFlagsRegistry registry() {\n    return REGISTRY;\n  }\n\n  private static StaticCommandFlagsRegistry createRegistry() {\n\n    Builder builder = new Builder();\n\n    // Delegate population to generated class\n    StaticCommandFlagsRegistryInitializer.initialize(builder);\n\n    return builder.build();\n  }\n\n  /**\n   * Get the flags for a given command. Flags are looked up from a static registry based on the\n   * command arguments. This approach significantly reduces memory usage by sharing flag instances\n   * across all CommandObject instances.\n   * <p>\n   * Uses the same hierarchical lookup strategy as {@link #lookupCommandMeta(CommandArguments)}.\n   * @param commandArguments the command arguments containing the command and its parameters\n   * @return EnumSet of CommandFlag for this command, or empty set if command has no flags\n   */\n  @Override\n  public EnumSet<CommandFlag> getFlags(CommandArguments commandArguments) {\n    CommandMeta commandMeta = lookupCommandMeta(commandArguments);\n    if (commandMeta == null) {\n      return EMPTY_FLAGS;\n    }\n    return commandMeta.getFlags();\n  }\n\n  /**\n   * Get the request policy for a given command. The request policy helps clients determine which\n   * shards to send the command to in a clustered deployment.\n   * <p>\n   * Uses the same hierarchical lookup strategy as {@link #getFlags(CommandArguments)}.\n   * @param commandArguments the command arguments containing the command and its parameters\n   * @return RequestPolicy for this command, or DEFAULT if no specific policy is defined\n   */\n  @Override\n  public RequestPolicy getRequestPolicy(CommandArguments commandArguments) {\n    CommandMeta commandMeta = lookupCommandMeta(commandArguments);\n    if (commandMeta == null) {\n      return DEFAULT_REQUEST_POLICY;\n    }\n    return commandMeta.getRequestPolicy();\n  }\n\n  /**\n   * Get the response policy for a given command. The response policy helps clients determine how to\n   * aggregate replies from multiple shards in a cluster.\n   * <p>\n   * Uses the same hierarchical lookup strategy as {@link #getFlags(CommandArguments)}.\n   * @param commandArguments the command arguments containing the command and its parameters\n   * @return ResponsePolicy for this command, or DEFAULT if no specific policy is defined\n   */\n  @Override\n  public ResponsePolicy getResponsePolicy(CommandArguments commandArguments) {\n    CommandMeta commandMeta = lookupCommandMeta(commandArguments);\n    if (commandMeta == null) {\n      return DEFAULT_RESPONSE_POLICY;\n    }\n    return commandMeta.getResponsePolicy();\n  }\n\n  /**\n   * Common lookup logic for finding the CommandMeta for a given command. Handles both simple\n   * commands and commands with subcommands.\n   */\n  private CommandMeta lookupCommandMeta(CommandArguments commandArguments) {\n    ProtocolCommand cmd = commandArguments.getCommand();\n    byte[] raw = cmd.getRaw();\n    byte[] uppercaseBytes = SafeEncoder.toUpperCase(raw);\n\n    CommandMeta commandMeta = commands.getCommand(uppercaseBytes);\n    if (commandMeta == null) {\n      return null;\n    }\n\n    if (commandMeta.hasSubcommands()) {\n      byte[] subCommand = getSubCommand(commandArguments);\n      if (subCommand != null) {\n        CommandMeta subCommandMeta = commandMeta.getSubcommand(subCommand);\n        if (subCommandMeta != null) {\n          return subCommandMeta;\n        }\n      }\n    }\n    return commandMeta;\n  }\n\n  private byte[] getSubCommand(CommandArguments commandArguments) {\n    if (commandArguments.size() > 1) {\n      Rawable secondArg = commandArguments.get(1);\n      byte[] subRaw = secondArg.getRaw();\n\n      // Convert to uppercase using SafeEncoder utility\n      return SafeEncoder.toUpperCase(subRaw);\n    } else {\n      return null;\n    }\n  }\n\n  // Internal class to hold subcommand mappings for parent commands.\n  static class Commands {\n\n    final JedisByteMap<CommandMeta> commands = new JedisByteMap<>();\n\n    boolean isEmpty() {\n      return commands.isEmpty();\n    }\n\n    public Commands register(byte[] cmd, CommandMeta command) {\n      commands.put(cmd, command);\n      return this;\n    }\n\n    public boolean containsKey(byte[] command) {\n      return commands.containsKey(command);\n    }\n\n    public CommandMeta getCommand(byte[] command) {\n      return commands.get(command);\n    }\n\n    public Map<byte[], CommandMeta> getCommands() {\n      return commands;\n    }\n  }\n\n  /**\n   * Internal class to hold command metadata including flags and policies.\n   */\n  static class CommandMeta {\n\n    final EnumSet<CommandFlag> flags;\n    final RequestPolicy requestPolicy;\n    final ResponsePolicy responsePolicy;\n    final Commands subcommands = new Commands();\n\n    CommandMeta(EnumSet<CommandFlag> flags) {\n      this(flags, DEFAULT_REQUEST_POLICY, DEFAULT_RESPONSE_POLICY);\n    }\n\n    CommandMeta(EnumSet<CommandFlag> flags, RequestPolicy requestPolicy,\n        ResponsePolicy responsePolicy) {\n      this.flags = flags;\n      this.requestPolicy = requestPolicy != null ? requestPolicy : DEFAULT_REQUEST_POLICY;\n      this.responsePolicy = responsePolicy != null ? responsePolicy : DEFAULT_RESPONSE_POLICY;\n    }\n\n    void putSubCommand(byte[] subCommand, CommandMeta subCommandMeta) {\n      this.subcommands.register(subCommand, subCommandMeta);\n    }\n\n    boolean hasSubcommands() {\n      return !subcommands.isEmpty();\n    }\n\n    EnumSet<CommandFlag> getFlags() {\n      if (flags == null) {\n        return EMPTY_FLAGS;\n      }\n      return flags;\n    }\n\n    RequestPolicy getRequestPolicy() {\n      return requestPolicy;\n    }\n\n    ResponsePolicy getResponsePolicy() {\n      return responsePolicy;\n    }\n\n    CommandMeta getSubcommand(byte[] subcommand) {\n      return subcommands.getCommand(subcommand);\n    }\n  }\n\n  /**\n   * Builder for constructing StaticCommandFlagsRegistry instances.\n   */\n  static public class Builder {\n\n    private final Commands commands = new Commands();\n\n    public Builder register(String name, EnumSet<CommandFlag> flags) {\n      commands.register(SafeEncoder.encode(name), new CommandMeta(flags));\n      return this;\n    }\n\n    public Builder register(String name, EnumSet<CommandFlag> flags, RequestPolicy requestPolicy,\n        ResponsePolicy responsePolicy) {\n      commands.register(SafeEncoder.encode(name),\n        new CommandMeta(flags, requestPolicy, responsePolicy));\n      return this;\n    }\n\n    public Builder register(String name, String subcommand, EnumSet<CommandFlag> flags) {\n      byte[] cmdName = SafeEncoder.encode(name);\n\n      if (!commands.containsKey(cmdName)) {\n        commands.register(SafeEncoder.encode(name), new CommandMeta(EMPTY_FLAGS));\n      }\n\n      byte[] subCmdName = SafeEncoder.encode(subcommand);\n      commands.getCommand(cmdName).putSubCommand(subCmdName, new CommandMeta(flags));\n      return this;\n    }\n\n    public Builder register(String name, String subcommand, EnumSet<CommandFlag> flags,\n        RequestPolicy requestPolicy, ResponsePolicy responsePolicy) {\n      byte[] cmdName = SafeEncoder.encode(name);\n\n      if (!commands.containsKey(cmdName)) {\n        commands.register(SafeEncoder.encode(name), new CommandMeta(EMPTY_FLAGS));\n      }\n\n      byte[] subCmdName = SafeEncoder.encode(subcommand);\n      commands.getCommand(cmdName).putSubCommand(subCmdName,\n        new CommandMeta(flags, requestPolicy, responsePolicy));\n      return this;\n    }\n\n    public StaticCommandFlagsRegistry build() {\n      return new StaticCommandFlagsRegistry(commands);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/StaticCommandFlagsRegistryInitializer.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.EnumSet;\nimport static redis.clients.jedis.StaticCommandFlagsRegistry.EMPTY_FLAGS;\nimport static redis.clients.jedis.CommandFlagsRegistry.CommandFlag;\nimport static redis.clients.jedis.CommandFlagsRegistry.RequestPolicy;\nimport static redis.clients.jedis.CommandFlagsRegistry.ResponsePolicy;\n\n/**\n * Static implementation of CommandFlagsRegistry. This class is auto-generated by\n * CommandFlagsRegistryGenerator. DO NOT EDIT MANUALLY.\n * <p>\n * Generated from Redis Server:\n * <ul>\n * <li>Version: 8.6.1</li>\n * <li>Mode: standalone</li>\n * <li>Loaded Modules: timeseries, search, vectorset, bf, ReJSON</li>\n * <li>Generated at: 2026-03-20 13:22:33 EET</li>\n * </ul>\n */\nfinal class StaticCommandFlagsRegistryInitializer {\n\n  static void initialize(StaticCommandFlagsRegistry.Builder builder) {\n    builder.register(\"FT.CONFIG\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    // FT.CONFIG subcommands\n    builder.register(\"FT.CONFIG\", \"GET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.CONFIG\", \"SET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"ACL\", EMPTY_FLAGS);\n    // ACL subcommands\n    builder.register(\"ACL\", \"CAT\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"DELUSER\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"ACL\", \"DRYRUN\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"GENPASS\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"GETUSER\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"LIST\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"LOAD\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"LOG\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"SAVE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"ACL\", \"SETUSER\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"ACL\", \"USERS\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"ACL\", \"WHOAMI\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"OBJECT\", EMPTY_FLAGS);\n    // OBJECT subcommands\n    builder.register(\"OBJECT\", \"ENCODING\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"OBJECT\", \"FREQ\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"OBJECT\", \"IDLETIME\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"OBJECT\", \"REFCOUNT\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"FUNCTION\", EMPTY_FLAGS);\n    // FUNCTION subcommands\n    builder.register(\"FUNCTION\", \"DELETE\", EnumSet.of(CommandFlag.NOSCRIPT, CommandFlag.WRITE),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"FUNCTION\", \"DUMP\", EnumSet.of(CommandFlag.NOSCRIPT));\n    builder.register(\"FUNCTION\", \"FLUSH\", EnumSet.of(CommandFlag.NOSCRIPT, CommandFlag.WRITE),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"FUNCTION\", \"KILL\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.NOSCRIPT),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.ONE_SUCCEEDED);\n    builder.register(\"FUNCTION\", \"LIST\", EnumSet.of(CommandFlag.NOSCRIPT));\n    builder.register(\"FUNCTION\", \"LOAD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.NOSCRIPT, CommandFlag.WRITE),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"FUNCTION\", \"RESTORE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.NOSCRIPT, CommandFlag.WRITE),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"FUNCTION\", \"STATS\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.NOSCRIPT),\n      RequestPolicy.DEFAULT, ResponsePolicy.SPECIAL);\n    builder.register(\"CLIENT\", EMPTY_FLAGS);\n    // CLIENT subcommands\n    builder.register(\"CLIENT\", \"CACHING\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"GETNAME\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"GETREDIR\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"ID\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"INFO\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"KILL\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"LIST\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"NO-EVICT\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"NO-TOUCH\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"PAUSE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"REPLY\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"SETINFO\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"CLIENT\", \"SETNAME\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"CLIENT\", \"TRACKING\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"TRACKINGINFO\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"UNBLOCK\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLIENT\", \"UNPAUSE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CONFIG\", EMPTY_FLAGS);\n    // CONFIG subcommands\n    builder.register(\"CONFIG\", \"GET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CONFIG\", \"RESETSTAT\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"CONFIG\", \"REWRITE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"CONFIG\", \"SET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"MODULE\", EMPTY_FLAGS);\n    // MODULE subcommands\n    builder.register(\"MODULE\", \"LIST\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT));\n    builder.register(\"MODULE\", \"LOAD\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING));\n    builder.register(\"MODULE\", \"LOADEX\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING));\n    builder.register(\"MODULE\", \"UNLOAD\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING));\n    builder.register(\"HOTKEYS\", EMPTY_FLAGS);\n    // HOTKEYS subcommands\n    builder.register(\"HOTKEYS\", \"GET\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT),\n      RequestPolicy.SPECIAL, ResponsePolicy.SPECIAL);\n    builder.register(\"HOTKEYS\", \"RESET\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT),\n      RequestPolicy.SPECIAL, null);\n    builder.register(\"HOTKEYS\", \"START\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT),\n      RequestPolicy.SPECIAL, null);\n    builder.register(\"HOTKEYS\", \"STOP\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT),\n      RequestPolicy.SPECIAL, null);\n    builder.register(\"PUBSUB\", EMPTY_FLAGS);\n    // PUBSUB subcommands\n    builder.register(\"PUBSUB\", \"CHANNELS\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"PUBSUB\", \"NUMPAT\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"PUBSUB\", \"NUMSUB\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"PUBSUB\", \"SHARDCHANNELS\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"PUBSUB\", \"SHARDNUMSUB\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"SCRIPT\", EMPTY_FLAGS);\n    // SCRIPT subcommands\n    builder.register(\"SCRIPT\", \"DEBUG\", EnumSet.of(CommandFlag.NOSCRIPT));\n    builder.register(\"SCRIPT\", \"EXISTS\", EnumSet.of(CommandFlag.NOSCRIPT), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.AGG_LOGICAL_AND);\n    builder.register(\"SCRIPT\", \"FLUSH\", EnumSet.of(CommandFlag.NOSCRIPT), RequestPolicy.ALL_NODES,\n      ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"SCRIPT\", \"KILL\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.NOSCRIPT),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.ONE_SUCCEEDED);\n    builder.register(\"SCRIPT\", \"LOAD\", EnumSet.of(CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"MEMORY\", EMPTY_FLAGS);\n    // MEMORY subcommands\n    builder.register(\"MEMORY\", \"DOCTOR\", EMPTY_FLAGS, RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.SPECIAL);\n    builder.register(\"MEMORY\", \"MALLOC-STATS\", EMPTY_FLAGS, RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.SPECIAL);\n    builder.register(\"MEMORY\", \"PURGE\", EMPTY_FLAGS, RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"MEMORY\", \"STATS\", EMPTY_FLAGS, RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.SPECIAL);\n    builder.register(\"MEMORY\", \"USAGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SLOWLOG\", EMPTY_FLAGS);\n    // SLOWLOG subcommands\n    builder.register(\"SLOWLOG\", \"GET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, null);\n    builder.register(\"SLOWLOG\", \"LEN\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.AGG_SUM);\n    builder.register(\"SLOWLOG\", \"RESET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"FT.CURSOR\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    // FT.CURSOR subcommands\n    builder.register(\"FT.CURSOR\", \"DEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY),\n      RequestPolicy.SPECIAL, null);\n    builder.register(\"FT.CURSOR\", \"GC\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.CURSOR\", \"READ\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY),\n      RequestPolicy.SPECIAL, null);\n    builder.register(\"COMMAND\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    // COMMAND subcommands\n    builder.register(\"COMMAND\", \"COUNT\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"COMMAND\", \"DOCS\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"COMMAND\", \"GETKEYS\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"COMMAND\", \"GETKEYSANDFLAGS\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"COMMAND\", \"INFO\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"COMMAND\", \"LIST\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"LATENCY\", EMPTY_FLAGS);\n    // LATENCY subcommands\n    builder.register(\"LATENCY\", \"DOCTOR\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.SPECIAL);\n    builder.register(\"LATENCY\", \"GRAPH\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.SPECIAL);\n    builder.register(\"LATENCY\", \"HISTOGRAM\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.SPECIAL);\n    builder.register(\"LATENCY\", \"HISTORY\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.SPECIAL);\n    builder.register(\"LATENCY\", \"LATEST\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.SPECIAL);\n    builder.register(\"LATENCY\", \"RESET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE),\n      RequestPolicy.ALL_NODES, ResponsePolicy.AGG_SUM);\n    builder.register(\"CLUSTER\", EMPTY_FLAGS);\n    // CLUSTER subcommands\n    builder.register(\"CLUSTER\", \"ADDSLOTS\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"ADDSLOTSRANGE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"BUMPEPOCH\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"COUNT-FAILURE-REPORTS\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"COUNTKEYSINSLOT\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"DELSLOTS\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"DELSLOTSRANGE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"FAILOVER\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"FLUSHSLOTS\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"FORGET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"GETKEYSINSLOT\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"INFO\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"KEYSLOT\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"LINKS\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"MEET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"MIGRATION\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"MYID\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"MYSHARDID\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"NODES\", EnumSet.of(CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"REPLICAS\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"REPLICATE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"RESET\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SAVECONFIG\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SET-CONFIG-EPOCH\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SETSLOT\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SHARDS\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SLAVES\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SLOT-STATS\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE),\n      RequestPolicy.ALL_SHARDS, null);\n    builder.register(\"CLUSTER\", \"SLOTS\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"CLUSTER\", \"SYNCSLOTS\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"XINFO\", EMPTY_FLAGS);\n    // XINFO subcommands\n    builder.register(\"XINFO\", \"CONSUMERS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"XINFO\", \"GROUPS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"XINFO\", \"STREAM\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"XGROUP\", EMPTY_FLAGS);\n    // XGROUP subcommands\n    builder.register(\"XGROUP\", \"CREATE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"XGROUP\", \"CREATECONSUMER\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"XGROUP\", \"DELCONSUMER\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"XGROUP\", \"DESTROY\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"XGROUP\", \"SETID\", EnumSet.of(CommandFlag.WRITE));\n    // 1 command(s) with: admin\n    builder.register(\"PFSELFTEST\", EnumSet.of(CommandFlag.ADMIN));\n\n    // 2 command(s) with: blocking; request_policy=all_shards; response_policy=agg_min\n    builder.register(\"WAIT\", EnumSet.of(CommandFlag.BLOCKING), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.AGG_MIN);\n    builder.register(\"WAITAOF\", EnumSet.of(CommandFlag.BLOCKING), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.AGG_MIN);\n\n    // 1 command(s) with: fast; request_policy=all_shards; response_policy=all_succeeded\n    builder.register(\"PING\", EnumSet.of(CommandFlag.FAST), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.ALL_SUCCEEDED);\n\n    // 1 command(s) with: fast\n    builder.register(\"ASKING\", EnumSet.of(CommandFlag.FAST));\n\n    // 1 command(s) with: readonly; request_policy=all_shards; response_policy=special\n    builder.register(\"RANDOMKEY\", EnumSet.of(CommandFlag.READONLY), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.SPECIAL);\n\n    // 1 command(s) with: readonly; request_policy=all_shards\n    builder.register(\"KEYS\", EnumSet.of(CommandFlag.READONLY), RequestPolicy.ALL_SHARDS, null);\n\n    // 1 command(s) with: readonly; request_policy=special; response_policy=special\n    builder.register(\"SCAN\", EnumSet.of(CommandFlag.READONLY), RequestPolicy.SPECIAL,\n      ResponsePolicy.SPECIAL);\n\n    // 38 command(s) with: readonly\n    builder.register(\"BITCOUNT\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"BITPOS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"DUMP\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GEODIST\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GEOHASH\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GEOPOS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GEORADIUSBYMEMBER_RO\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GEORADIUS_RO\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GEOSEARCH\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"GETRANGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"HGETALL\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"HKEYS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"HRANDFIELD\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"HSCAN\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"HVALS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"LCS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"LINDEX\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"LPOS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"LRANGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"PFCOUNT\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SDIFF\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SINTER\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SMEMBERS\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SRANDMEMBER\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SSCAN\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SUBSTR\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"SUNION\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"XPENDING\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"XRANGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"XREVRANGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZRANDMEMBER\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZRANGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZRANGEBYLEX\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZRANGEBYSCORE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZREVRANGE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZREVRANGEBYLEX\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZREVRANGEBYSCORE\", EnumSet.of(CommandFlag.READONLY));\n    builder.register(\"ZSCAN\", EnumSet.of(CommandFlag.READONLY));\n\n    // 2 command(s) with: write; request_policy=all_shards; response_policy=all_succeeded\n    builder.register(\"FLUSHALL\", EnumSet.of(CommandFlag.WRITE), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.ALL_SUCCEEDED);\n    builder.register(\"FLUSHDB\", EnumSet.of(CommandFlag.WRITE), RequestPolicy.ALL_SHARDS,\n      ResponsePolicy.ALL_SUCCEEDED);\n\n    // 1 command(s) with: write; request_policy=multi_shard; response_policy=agg_sum\n    builder.register(\"DEL\", EnumSet.of(CommandFlag.WRITE), RequestPolicy.MULTI_SHARD,\n      ResponsePolicy.AGG_SUM);\n\n    // 8 command(s) with: write\n    builder.register(\"LREM\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"LTRIM\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"RENAME\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"TRIMSLOTS\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"XTRIM\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"ZREMRANGEBYLEX\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"ZREMRANGEBYRANK\", EnumSet.of(CommandFlag.WRITE));\n    builder.register(\"ZREMRANGEBYSCORE\", EnumSet.of(CommandFlag.WRITE));\n\n    // 2 command(s) with: blocking, write\n    builder.register(\"BLPOP\", EnumSet.of(CommandFlag.BLOCKING, CommandFlag.WRITE));\n    builder.register(\"BRPOP\", EnumSet.of(CommandFlag.BLOCKING, CommandFlag.WRITE));\n\n    // 1 command(s) with: denyoom, write; request_policy=multi_shard; response_policy=all_succeeded\n    builder.register(\"MSET\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE),\n      RequestPolicy.MULTI_SHARD, ResponsePolicy.ALL_SUCCEEDED);\n\n    // 21 command(s) with: denyoom, write\n    builder.register(\"BITFIELD\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"BITOP\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"COPY\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"GEOADD\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"GEOSEARCHSTORE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"LINSERT\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"LMOVE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"LSET\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"MSETNX\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"PFMERGE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"PSETEX\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"RESTORE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"RPOPLPUSH\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SDIFFSTORE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SET\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SETBIT\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SETEX\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SETRANGE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SINTERSTORE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"SUNIONSTORE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"ZRANGESTORE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE));\n\n    // 1 command(s) with: fast, readonly; request_policy=all_shards; response_policy=agg_sum\n    builder.register(\"DBSIZE\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY),\n      RequestPolicy.ALL_SHARDS, ResponsePolicy.AGG_SUM);\n\n    // 2 command(s) with: fast, readonly; request_policy=multi_shard; response_policy=agg_sum\n    builder.register(\"EXISTS\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY),\n      RequestPolicy.MULTI_SHARD, ResponsePolicy.AGG_SUM);\n    builder.register(\"TOUCH\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY),\n      RequestPolicy.MULTI_SHARD, ResponsePolicy.AGG_SUM);\n\n    // 1 command(s) with: fast, readonly; request_policy=multi_shard\n    builder.register(\"MGET\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY),\n      RequestPolicy.MULTI_SHARD, null);\n\n    // 32 command(s) with: fast, readonly\n    builder.register(\"BITFIELD_RO\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"DIGEST\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"EXPIRETIME\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"GET\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"GETBIT\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HEXISTS\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HEXPIRETIME\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HGET\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HLEN\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HMGET\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HPEXPIRETIME\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HPTTL\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HSTRLEN\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"HTTL\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"LLEN\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"LOLWUT\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"PEXPIRETIME\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"PTTL\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"SCARD\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"SISMEMBER\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"SMISMEMBER\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"STRLEN\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"TTL\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"TYPE\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"XLEN\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZCARD\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZCOUNT\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZLEXCOUNT\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZMSCORE\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZRANK\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZREVRANK\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n    builder.register(\"ZSCORE\", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY));\n\n    // 1 command(s) with: fast, write; request_policy=multi_shard; response_policy=agg_sum\n    builder.register(\"UNLINK\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE),\n      RequestPolicy.MULTI_SHARD, ResponsePolicy.AGG_SUM);\n\n    // 34 command(s) with: fast, write\n    builder.register(\"DELEX\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"EXPIRE\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"EXPIREAT\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"GETDEL\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"GETEX\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HDEL\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HEXPIRE\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HEXPIREAT\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HGETDEL\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HGETEX\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HPERSIST\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HPEXPIRE\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HPEXPIREAT\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"LPOP\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"MOVE\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"PERSIST\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"PEXPIRE\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"PEXPIREAT\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"RENAMENX\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"RPOP\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"SMOVE\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"SPOP\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"SREM\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"SWAPDB\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XACK\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XACKDEL\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XAUTOCLAIM\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XCFGSET\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XCLAIM\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XDEL\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XDELEX\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"ZPOPMAX\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"ZPOPMIN\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"ZREM\", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE));\n\n    // 1 command(s) with: loading, stale; request_policy=default; response_policy=special\n    builder.register(\"INFO\", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE),\n      RequestPolicy.DEFAULT, ResponsePolicy.SPECIAL);\n\n    // 56 command(s) with: module, readonly\n    builder.register(\"CMS.INFO\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CMS.QUERY\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.AGGREGATE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.DICTDUMP\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.EXPLAIN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.EXPLAINCLI\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.GET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.HYBRID\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.INFO\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.MGET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.PROFILE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.SEARCH\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.SPELLCHECK\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.SUGGET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.SUGLEN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.SYNDUMP\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT.TAGVALS\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"FT._LIST\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.ARRINDEX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.ARRLEN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.DEBUG\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.GET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.MGET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.OBJKEYS\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.OBJLEN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.RESP\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.STRLEN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"JSON.TYPE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.BYRANK\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.BYREVRANK\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.CDF\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.INFO\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.MAX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.MIN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.QUANTILE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.RANK\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.REVRANK\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TDIGEST.TRIMMED_MEAN\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TOPK.COUNT\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TOPK.INFO\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TOPK.LIST\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TOPK.QUERY\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.GET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.INFO\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.MGET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.MRANGE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.MREVRANGE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.QUERYINDEX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.RANGE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"TS.REVRANGE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VISMEMBER\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VRANDMEMBER\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VRANGE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VSIM\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"_FT.CONFIG\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"_FT.DEBUG\", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY));\n\n    // 21 command(s) with: module, write\n    builder.register(\"FT.ALIASDEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.DEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.DICTDEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.DROP\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.DROPINDEX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.SUGDEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT._ALIASDELIFX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT._DROPIFX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT._DROPINDEXIFX\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.ARRPOP\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.ARRTRIM\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.CLEAR\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.DEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.FORGET\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.NUMINCRBY\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.NUMMULTBY\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.NUMPOWBY\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.TOGGLE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.DEL\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.DELETERULE\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"VREM\", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE));\n\n    // 6 command(s) with: movablekeys, readonly\n    builder.register(\"SINTERCARD\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n    builder.register(\"SORT_RO\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n    builder.register(\"ZDIFF\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n    builder.register(\"ZINTER\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n    builder.register(\"ZINTERCARD\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n    builder.register(\"ZUNION\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n\n    // 3 command(s) with: movablekeys, write\n    builder.register(\"LMPOP\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"MIGRATE\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"ZMPOP\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n\n    // 1 command(s) with: admin, denyoom, write\n    builder.register(\"PFDEBUG\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.DENYOOM, CommandFlag.WRITE));\n\n    // 2 command(s) with: admin, noscript, no_async_loading\n    builder.register(\"BGREWRITEAOF\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING));\n    builder.register(\"BGSAVE\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING));\n\n    // 1 command(s) with: admin, noscript, stale\n    builder.register(\"FAILOVER\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n\n    // 1 command(s) with: asking, denyoom, write\n    builder.register(\"RESTORE-ASKING\",\n      EnumSet.of(CommandFlag.ASKING, CommandFlag.DENYOOM, CommandFlag.WRITE));\n\n    // 2 command(s) with: blocking, denyoom, write\n    builder.register(\"BLMOVE\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.DENYOOM, CommandFlag.WRITE));\n    builder.register(\"BRPOPLPUSH\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.DENYOOM, CommandFlag.WRITE));\n\n    // 2 command(s) with: blocking, fast, write\n    builder.register(\"BZPOPMAX\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"BZPOPMIN\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.FAST, CommandFlag.WRITE));\n\n    // 1 command(s) with: blocking, movablekeys, readonly\n    builder.register(\"XREAD\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.READONLY));\n\n    // 3 command(s) with: blocking, movablekeys, write\n    builder.register(\"BLMPOP\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"BZMPOP\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"XREADGROUP\",\n      EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n\n    // 24 command(s) with: denyoom, fast, write\n    builder.register(\"APPEND\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"DECR\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"DECRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"GETSET\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HINCRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HINCRBYFLOAT\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HMSET\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HSET\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HSETEX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"HSETNX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"INCR\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"INCRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"INCRBYFLOAT\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"LPUSH\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"LPUSHX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"PFADD\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"RPUSH\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"RPUSHX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"SADD\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"SETNX\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XADD\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"XSETID\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"ZADD\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n    builder.register(\"ZINCRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE));\n\n    // 47 command(s) with: denyoom, module, write\n    builder.register(\"BF.ADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"BF.INSERT\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"BF.LOADCHUNK\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"BF.MADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"BF.RESERVE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CF.ADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CF.ADDNX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CF.INSERT\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CF.INSERTNX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CF.LOADCHUNK\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CF.RESERVE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CMS.INCRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CMS.INITBYDIM\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CMS.INITBYPROB\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"CMS.MERGE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.ADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.ALIASADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.ALIASUPDATE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.ALTER\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.CREATE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.DICTADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.SAFEADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.SUGADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.SYNADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT.SYNUPDATE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT._ALIASADDIFNX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT._ALTERIFNX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"FT._CREATEIFNX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.ARRAPPEND\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.ARRINSERT\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.MERGE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.MSET\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.SET\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"JSON.STRAPPEND\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TDIGEST.ADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TDIGEST.CREATE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TDIGEST.RESET\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TOPK.ADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TOPK.INCRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TOPK.RESERVE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.ADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.ALTER\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.CREATE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.DECRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.INCRBY\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.MADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"VADD\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE));\n\n    // 1 command(s) with: denyoom, movablekeys, write; request_policy=multi_shard;\n    // response_policy=all_succeeded\n    builder.register(\"MSETEX\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE),\n      RequestPolicy.MULTI_SHARD, ResponsePolicy.ALL_SUCCEEDED);\n\n    // 6 command(s) with: denyoom, movablekeys, write\n    builder.register(\"GEORADIUS\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"GEORADIUSBYMEMBER\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"SORT\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"ZDIFFSTORE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"ZINTERSTORE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n    builder.register(\"ZUNIONSTORE\",\n      EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n\n    // 6 command(s) with: fast, loading, stale\n    builder.register(\"ECHO\", EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"LASTSAVE\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"READONLY\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"READWRITE\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"SELECT\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE));\n    builder.register(\"TIME\", EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE));\n\n    // 19 command(s) with: fast, module, readonly\n    builder.register(\"BF.CARD\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"BF.DEBUG\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"BF.EXISTS\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"BF.INFO\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"BF.MEXISTS\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"BF.SCANDUMP\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.COMPACT\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.COUNT\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.DEBUG\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.EXISTS\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.INFO\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.MEXISTS\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"CF.SCANDUMP\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VCARD\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VDIM\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VEMB\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VGETATTR\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VINFO\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n    builder.register(\"VLINKS\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY));\n\n    // 3 command(s) with: fast, module, write\n    builder.register(\"CF.DEL\", EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"TS.CREATERULE\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.WRITE));\n    builder.register(\"VSETATTR\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.WRITE));\n\n    // 3 command(s) with: module, noscript, readonly\n    builder.register(\"SEARCH.CLUSTERREFRESH\",\n      EnumSet.of(CommandFlag.MODULE, CommandFlag.NOSCRIPT, CommandFlag.READONLY));\n    builder.register(\"TIMESERIES.CLUSTERSET\",\n      EnumSet.of(CommandFlag.MODULE, CommandFlag.NOSCRIPT, CommandFlag.READONLY));\n    builder.register(\"TIMESERIES.REFRESHCLUSTER\",\n      EnumSet.of(CommandFlag.MODULE, CommandFlag.NOSCRIPT, CommandFlag.READONLY));\n\n    // 2 command(s) with: admin, loading, noscript, stale\n    builder.register(\"DEBUG\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"MONITOR\",\n      EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n\n    // 3 command(s) with: admin, noscript, no_async_loading, no_multi\n    builder.register(\"PSYNC\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_ASYNC_LOADING, CommandFlag.NO_MULTI));\n    builder.register(\"SAVE\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_ASYNC_LOADING, CommandFlag.NO_MULTI));\n    builder.register(\"SYNC\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_ASYNC_LOADING, CommandFlag.NO_MULTI));\n\n    // 2 command(s) with: admin, noscript, no_async_loading, stale\n    builder.register(\"REPLICAOF\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n    builder.register(\"SLAVEOF\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE));\n\n    // 1 command(s) with: denyoom, module, movablekeys, write\n    builder.register(\"TDIGEST.MERGE\", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE,\n      CommandFlag.MOVABLEKEYS, CommandFlag.WRITE));\n\n    // 1 command(s) with: fast, loading, noscript, stale\n    builder.register(\"ROLE\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n\n    // 2 command(s) with: fast, loading, pubsub, stale\n    builder.register(\"PUBLISH\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"SPUBLISH\",\n      EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE));\n\n    // 2 command(s) with: loading, module, noscript, readonly\n    builder.register(\"SEARCH.CLUSTERINFO\", EnumSet.of(CommandFlag.LOADING, CommandFlag.MODULE,\n      CommandFlag.NOSCRIPT, CommandFlag.READONLY));\n    builder.register(\"SEARCH.CLUSTERSET\", EnumSet.of(CommandFlag.LOADING, CommandFlag.MODULE,\n      CommandFlag.NOSCRIPT, CommandFlag.READONLY));\n\n    // 6 command(s) with: loading, noscript, pubsub, stale\n    builder.register(\"PSUBSCRIBE\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"PUNSUBSCRIBE\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"SSUBSCRIBE\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"SUBSCRIBE\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"SUNSUBSCRIBE\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE));\n    builder.register(\"UNSUBSCRIBE\",\n      EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE));\n\n    // 1 command(s) with: loading, noscript, skip_slowlog, stale\n    builder.register(\"EXEC\", EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT,\n      CommandFlag.SKIP_SLOWLOG, CommandFlag.STALE));\n\n    // 1 command(s) with: admin, allow_busy, loading, noscript, stale\n    builder.register(\"REPLCONF\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.ALLOW_BUSY,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n\n    // 4 command(s) with: allow_busy, fast, loading, noscript, stale\n    builder.register(\"DISCARD\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"MULTI\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"UNWATCH\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n    builder.register(\"WATCH\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE));\n\n    // 3 command(s) with: movablekeys, noscript, no_mandatory_keys, skip_monitor, stale\n    builder.register(\"EVAL\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_MANDATORY_KEYS, CommandFlag.SKIP_MONITOR, CommandFlag.STALE));\n    builder.register(\"EVALSHA\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_MANDATORY_KEYS, CommandFlag.SKIP_MONITOR, CommandFlag.STALE));\n    builder.register(\"FCALL\", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT,\n      CommandFlag.NO_MANDATORY_KEYS, CommandFlag.SKIP_MONITOR, CommandFlag.STALE));\n\n    // 1 command(s) with: admin, allow_busy, loading, noscript, no_multi, stale\n    builder.register(\"SHUTDOWN\", EnumSet.of(CommandFlag.ADMIN, CommandFlag.ALLOW_BUSY,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_MULTI, CommandFlag.STALE));\n\n    // 4 command(s) with: allow_busy, fast, loading, noscript, no_auth, stale\n    builder.register(\"AUTH\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE));\n    builder.register(\"HELLO\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE));\n    builder.register(\"QUIT\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE));\n    builder.register(\"RESET\", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST,\n      CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE));\n\n    // 3 command(s) with: movablekeys, noscript, no_mandatory_keys, readonly, skip_monitor, stale\n    builder.register(\"EVALSHA_RO\",\n      EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, CommandFlag.NO_MANDATORY_KEYS,\n        CommandFlag.READONLY, CommandFlag.SKIP_MONITOR, CommandFlag.STALE));\n    builder.register(\"EVAL_RO\",\n      EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, CommandFlag.NO_MANDATORY_KEYS,\n        CommandFlag.READONLY, CommandFlag.SKIP_MONITOR, CommandFlag.STALE));\n    builder.register(\"FCALL_RO\",\n      EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, CommandFlag.NO_MANDATORY_KEYS,\n        CommandFlag.READONLY, CommandFlag.SKIP_MONITOR, CommandFlag.STALE));\n\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/StreamEntryID.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class StreamEntryID implements Comparable<StreamEntryID>, Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  private long time;\n  private long sequence;\n\n  public StreamEntryID() {\n    this(0, 0L);\n  }\n\n  public StreamEntryID(byte[] id) {\n    this(SafeEncoder.encode(id));\n  }\n\n  public StreamEntryID(String id) {\n    String[] split = id.split(\"-\");\n    this.time = Long.parseLong(split[0]);\n    this.sequence = Long.parseLong(split[1]);\n  }\n\n  public StreamEntryID(long time) {\n    this(time, 0);\n  }\n\n  public StreamEntryID(long time, long sequence) {\n    this.time = time;\n    this.sequence = sequence;\n  }\n\n  @Override\n  public String toString() {\n    return time + \"-\" + sequence;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) return true;\n    if (obj == null) return false;\n    if (getClass() != obj.getClass()) return false;\n    StreamEntryID other = (StreamEntryID) obj;\n    return this.time == other.time && this.sequence == other.sequence;\n  }\n\n  @Override\n  public int hashCode() {\n    return this.toString().hashCode();\n  }\n\n  @Override\n  public int compareTo(StreamEntryID other) {\n    int timeCompare = Long.compare(this.time, other.time);\n    return timeCompare != 0 ? timeCompare : Long.compare(this.sequence, other.sequence);\n  }\n\n  public long getTime() {\n    return time;\n  }\n\n  public long getSequence() {\n    return sequence;\n  }\n\n  private void writeObject(java.io.ObjectOutputStream out) throws IOException {\n    out.writeLong(this.time);\n    out.writeLong(this.sequence);\n  }\n\n  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {\n    this.time = in.readLong();\n    this.sequence = in.readLong();\n  }\n\n  /**\n   * Should be used only with XADD\n   *\n   * {@code XADD mystream * field1 value1}\n   */\n  public static final StreamEntryID NEW_ENTRY = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \"*\";\n    }\n  };\n\n  /**\n   * Should be used only with XGROUP CREATE\n   *\n   * {@code XGROUP CREATE mystream consumer-group-name $}\n   */\n  public static final StreamEntryID XGROUP_LAST_ENTRY = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \"$\";\n    }\n  };\n\n  /**\n   * @deprecated Use {@link StreamEntryID#XGROUP_LAST_ENTRY} for XGROUP CREATE command or\n   * {@link StreamEntryID#XREAD_NEW_ENTRY} for XREAD command.\n   */\n  @Deprecated\n  public static final StreamEntryID LAST_ENTRY = XGROUP_LAST_ENTRY;\n\n  /**\n   * Should be used only with XREAD\n   *\n   * {@code XREAD BLOCK 5000 COUNT 100 STREAMS mystream $}\n   */\n  public static final StreamEntryID XREAD_NEW_ENTRY = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \"$\";\n    }\n  };\n\n  /**\n   * Should be used only with XREADGROUP\n   * <p>\n   * {@code XREADGROUP GROUP mygroup myconsumer STREAMS mystream >}\n   */\n  public static final StreamEntryID XREADGROUP_UNDELIVERED_ENTRY = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \">\";\n    }\n  };\n\n  /**\n   * @deprecated Use {@link StreamEntryID#XREADGROUP_UNDELIVERED_ENTRY}.\n   */\n  @Deprecated\n  public static final StreamEntryID UNRECEIVED_ENTRY = XREADGROUP_UNDELIVERED_ENTRY;\n\n  /**\n   * Can be used in XRANGE, XREVRANGE and XPENDING commands.\n   */\n  public static final StreamEntryID MINIMUM_ID = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \"-\";\n    }\n  };\n\n  /**\n   * Can be used in XRANGE, XREVRANGE and XPENDING commands.\n   */\n  public static final StreamEntryID MAXIMUM_ID = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \"+\";\n    }\n  };\n\n  /**\n   * Should be used only with XREAD\n   *\n   * {@code XREAD STREAMS mystream +}\n   */\n  public static final StreamEntryID XREAD_LAST_ENTRY = new StreamEntryID() {\n\n    private static final long serialVersionUID = 1L;\n\n    @Override\n    public String toString() {\n      return \"+\";\n    }\n  };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/Transaction.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.Command.DISCARD;\nimport static redis.clients.jedis.Protocol.Command.EXEC;\nimport static redis.clients.jedis.Protocol.Command.MULTI;\nimport static redis.clients.jedis.Protocol.Command.UNWATCH;\n\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\n\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\n/**\n * A transaction based on <a href=\"https://redis.io/docs/manual/pipelining/\">pipelining</a>.\n */\npublic class Transaction extends AbstractTransaction {\n\n  private final Queue<Response<?>> pipelinedResponses = new LinkedList<>();\n\n  protected final Connection connection;\n  private final boolean closeConnection;\n\n  private boolean broken = false;\n  private boolean inWatch = false;\n  private boolean inMulti = false;\n\n  /**\n   * Creates a new transaction.\n   * \n   * A MULTI command will be added to be sent to server. WATCH/UNWATCH/MULTI commands must not be\n   * called with this object.\n   * @param connection connection\n   */\n  public Transaction(Connection connection) {\n    this(connection, true);\n  }\n\n  /**\n   * Creates a new transaction.\n   *\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   *\n   * @param connection connection\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   */\n  public Transaction(Connection connection, boolean doMulti) {\n    this(connection, doMulti, false, createCommandObjects(connection));\n  }\n\n  /**\n   * Creates a new transaction.\n   *\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   *\n   * @param connection connection\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @param closeConnection should the 'connection' be closed when 'close()' is called?\n   */\n  public Transaction(Connection connection, boolean doMulti, boolean closeConnection) {\n    this(connection, doMulti, closeConnection, createCommandObjects(connection));\n  }\n\n  /**\n   * Creates a new transaction.\n   *\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   *\n   * @param connection connection\n   * @param commandObjects command objects\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @param closeConnection should the 'connection' be closed when 'close()' is called?\n   */\n  Transaction(Connection connection, boolean doMulti, boolean closeConnection, CommandObjects commandObjects) {\n    super(commandObjects);\n    this.connection = connection;\n    this.closeConnection = closeConnection;\n    if (doMulti) multi();\n  }\n\n  private static CommandObjects createCommandObjects(Connection connection) {\n    CommandObjects commandObjects = new CommandObjects();\n    RedisProtocol proto = connection.getRedisProtocol();\n    if (proto != null) commandObjects.setProtocol(proto);\n    return commandObjects;\n  }\n\n  @Override\n  public final void multi() {\n    connection.sendCommand(MULTI);\n    // processMultiResponse(); // do nothing\n    inMulti = true;\n  }\n\n  @Override\n  public String watch(final String... keys) {\n    String status = connection.executeCommand(commandObjects.watch(keys));\n    inWatch = true;\n    return status;\n  }\n\n  @Override\n  public String watch(final byte[]... keys) {\n    String status = connection.executeCommand(commandObjects.watch(keys));\n    inWatch = true;\n    return status;\n  }\n\n  @Override\n  public String unwatch() {\n    connection.sendCommand(UNWATCH);\n    String status = connection.getStatusCodeReply();\n    inWatch = false;\n    return status;\n  }\n\n  @Override\n  protected final <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n    Response<T> response = null;\n    if (!inMulti) {\n      response = execute(commandObject);\n    } else {\n      connection.sendCommand(commandObject.getArguments());\n      response = new Response<>(commandObject.getBuilder());\n      pipelinedResponses.add(response);\n    }\n    return response;\n  }\n\n  private <T> Response<T> execute(CommandObject<T> commandObject) {\n    Response<T> response;\n    try {\n      T result =  connection.executeCommand(commandObject);\n      response = Response.of(result);\n    } catch (JedisDataException e) {\n      response = Response.error(e);\n    }\n    return response;\n  }\n\n  @Override\n  public final void close() {\n    try {\n      clear();\n    } finally {\n      if (closeConnection) {\n        connection.close();\n      }\n    }\n  }\n\n  @Deprecated // TODO: private\n  public final void clear() {\n    if (broken) {\n      return;\n    }\n    if (inMulti) {\n      discard();\n    } else if (inWatch) {\n      unwatch();\n    }\n  }\n\n  @Override\n  public List<Object> exec() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"EXEC without MULTI\");\n    }\n\n    try {\n      // ignore QUEUED (or ERROR)\n      // processPipelinedResponses(pipelinedResponses.size());\n      List<Object> queuedCmdResponses = connection.getMany(1 + pipelinedResponses.size());\n\n\n      connection.sendCommand(EXEC);\n\n      List<Object> unformatted;\n      try {\n        unformatted = connection.getObjectMultiBulkReply();\n      } catch (JedisDataException jce) {\n        // A command may fail to be queued, so there may be an error before EXEC is called\n        // In this case, the server will discard all commands in the transaction and return the EXECABORT error.\n        // Enhance the final error with suppressed errors.\n        queuedCmdResponses.stream()\n            .filter(o -> o instanceof Exception)\n            .map(o -> (Exception) o)\n            .forEach(jce::addSuppressed);\n        throw jce;\n      }\n\n      if (unformatted == null) {\n        pipelinedResponses.clear();\n        return null;\n      }\n\n      List<Object> formatted = new ArrayList<>(unformatted.size());\n      for (Object o : unformatted) {\n        try {\n          Response<?> response = pipelinedResponses.poll();\n          response.set(o);\n          formatted.add(response.get());\n        } catch (JedisDataException e) {\n          formatted.add(e);\n        }\n      }\n      return formatted;\n    } catch (JedisConnectionException jce) {\n      broken = true;\n      throw jce;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      pipelinedResponses.clear();\n      onAfterExec();\n    }\n  }\n\n  @Override\n  public String discard() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"DISCARD without MULTI\");\n    }\n\n    try {\n      // ignore QUEUED (or ERROR)\n      // processPipelinedResponses(pipelinedResponses.size());\n      connection.getMany(1 + pipelinedResponses.size());\n\n      connection.sendCommand(DISCARD);\n\n      return connection.getStatusCodeReply();\n    } catch (JedisConnectionException jce) {\n      broken = true;\n      throw jce;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      pipelinedResponses.clear();\n      onAfterDiscard();\n    }\n  }\n\n  /**\n   * Hook method called after exec() completes (successfully or with error).\n   * Subclasses can override this to perform cleanup actions.\n   */\n  protected void onAfterExec() {\n    // Default implementation does nothing\n  }\n\n  /**\n   * Hook method called after discard() completes (successfully or with error).\n   * Subclasses can override this to perform cleanup actions.\n   */\n  protected void onAfterDiscard() {\n    // Default implementation does nothing\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/UnifiedJedis.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.json.JSONArray;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.annots.VisibleForTesting;\nimport redis.clients.jedis.args.*;\nimport redis.clients.jedis.bloom.*;\nimport redis.clients.jedis.commands.JedisCommands;\nimport redis.clients.jedis.commands.JedisBinaryCommands;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.commands.SampleBinaryKeyedCommands;\nimport redis.clients.jedis.commands.SampleKeyedCommands;\nimport redis.clients.jedis.commands.RedisModuleCommands;\nimport redis.clients.jedis.search.hybrid.FTHybridParams;\nimport redis.clients.jedis.search.hybrid.HybridResult;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.csc.CacheConnection;\nimport redis.clients.jedis.csc.CacheFactory;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.executors.*;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.mcf.MultiDbPipeline;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.mcf.MultiDbTransaction;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.providers.*;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.aggr.FtAggregateIteration;\nimport redis.clients.jedis.search.schemafields.SchemaField;\nimport redis.clients.jedis.timeseries.*;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.util.JedisURIHelper;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class UnifiedJedis implements JedisCommands, JedisBinaryCommands,\n    SampleKeyedCommands, SampleBinaryKeyedCommands, RedisModuleCommands,\n    AutoCloseable {\n\n  @Deprecated\n  protected RedisProtocol protocol = null;\n  protected final ConnectionProvider provider;\n  protected final CommandExecutor executor;\n  protected final CommandObjects commandObjects;\n  private final Cache cache;\n\n  /**\n   * @deprecated Use {@link RedisClient#create()} instead.\n   */\n  @Deprecated\n  public UnifiedJedis() {\n    this(new HostAndPort(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT));\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#create(HostAndPort)} instead.\n   */\n  @Deprecated\n  public UnifiedJedis(HostAndPort hostAndPort) {\n    this(new PooledConnectionProvider(hostAndPort), (RedisProtocol) null);\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#create(String)} instead.\n   */\n  @Deprecated\n  public UnifiedJedis(final String url) {\n    this(URI.create(url));\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#create(URI)} instead.\n   */\n  @Deprecated\n  public UnifiedJedis(final URI uri) {\n    this(JedisURIHelper.getHostAndPort(uri), DefaultJedisClientConfig.builder()\n        .user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri))\n        .database(JedisURIHelper.getDBIndex(uri)).protocol(JedisURIHelper.getRedisProtocol(uri))\n        .ssl(JedisURIHelper.isRedisSSLScheme(uri)).build());\n  }\n\n  /**\n   * Create a new UnifiedJedis with the provided URI and JedisClientConfig object. Note that all fields\n   * that can be parsed from the URI will be used instead of the corresponding configuration values. This includes\n   * the following fields: user, password, database, protocol version, and whether to use SSL.\n   *\n   * For example, if the URI is \"redis://user:password@localhost:6379/1\", the user and password fields will be set\n   * to \"user\" and \"password\" respectively, the database field will be set to 1. Those fields will be ignored\n   * from the JedisClientConfig object.\n   *\n   * @param uri The URI to connect to\n   * @param config The JedisClientConfig object to use\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with custom settings.\n   */\n  @Deprecated\n  public UnifiedJedis(final URI uri, JedisClientConfig config) {\n    this(JedisURIHelper.getHostAndPort(uri), DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(config.getConnectionTimeoutMillis())\n        .socketTimeoutMillis(config.getSocketTimeoutMillis())\n        .blockingSocketTimeoutMillis(config.getBlockingSocketTimeoutMillis())\n        .user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri))\n        .database(JedisURIHelper.getDBIndex(uri)).clientName(config.getClientName())\n        .protocol(JedisURIHelper.getRedisProtocol(uri))\n        .ssl(JedisURIHelper.isRedisSSLScheme(uri)).sslSocketFactory(config.getSslSocketFactory())\n        .sslParameters(config.getSslParameters()).hostnameVerifier(config.getHostnameVerifier()).build());\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with custom settings.\n   */\n  @Deprecated\n  public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) {\n    this(new PooledConnectionProvider(hostAndPort, clientConfig), clientConfig.getRedisProtocol());\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with client-side caching.\n   */\n  @Experimental\n  @Deprecated\n  public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, CacheConfig cacheConfig) {\n    this(hostAndPort, clientConfig, CacheFactory.getCache(cacheConfig));\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with client-side caching.\n   */\n  @Experimental\n  @Deprecated\n  public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache cache) {\n    this(new PooledConnectionProvider(hostAndPort, clientConfig, cache), clientConfig.getRedisProtocol(), cache);\n  }\n\n  @Deprecated\n  public UnifiedJedis(ConnectionProvider provider) {\n    this(new DefaultCommandExecutor(provider), provider);\n  }\n\n  protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol) {\n    this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol);\n  }\n\n  @Experimental\n  protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, Cache cache) {\n    this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol, cache);\n  }\n\n  /**\n   * The constructor to directly use a custom {@link JedisSocketFactory}.\n   * <p>\n   * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if\n   * {@link UnifiedJedis#provider} is accessed.\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with custom settings.\n   */\n  @Deprecated\n  public UnifiedJedis(JedisSocketFactory socketFactory) {\n    this(new Connection(socketFactory));\n  }\n\n  /**\n   * The constructor to directly use a custom {@link JedisSocketFactory}.\n   * <p>\n   * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if\n   * {@link UnifiedJedis#provider} is accessed.\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with custom settings.\n   */\n  @Deprecated\n  public UnifiedJedis(JedisSocketFactory socketFactory, JedisClientConfig clientConfig) {\n    this(new Connection(socketFactory, clientConfig));\n  }\n\n  /**\n   * The constructor to directly use a {@link Connection}.\n   * <p>\n   * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if\n   * {@link UnifiedJedis#provider} is accessed.\n   * @deprecated\n   */\n  @Deprecated\n  public UnifiedJedis(Connection connection) {\n    this.provider = null;\n    this.executor = new SimpleCommandExecutor(connection);\n    this.commandObjects = new CommandObjects();\n    RedisProtocol proto = connection.getRedisProtocol();\n    if (proto != null) {\n      this.commandObjects.setProtocol(proto);\n    }\n    if (connection instanceof CacheConnection) {\n      this.cache = ((CacheConnection) connection).getCache();\n    } else {\n      this.cache = null;\n    }\n  }\n\n  /**\n   * @deprecated Use {@link RedisClusterClient#builder()} to configure the cluster client.\n   */\n  @Deprecated\n  public UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {\n    this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()), provider,\n        new ClusterCommandObjects());\n  }\n\n  @Deprecated\n  protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration,\n      RedisProtocol protocol) {\n    this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()), provider,\n        new ClusterCommandObjects(), protocol);\n  }\n\n  @Deprecated\n  protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration,\n      RedisProtocol protocol, Cache cache) {\n    this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()), provider,\n        new ClusterCommandObjects(), protocol, cache);\n  }\n\n  /**\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with retry settings.\n   */\n  @Deprecated\n  public UnifiedJedis(ConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {\n    this(new RetryableCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider);\n  }\n\n  /**\n   * The constructor to use a custom {@link CommandExecutor}.\n   * <p>\n   * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if\n   * {@link UnifiedJedis#provider} is accessed.\n   * @deprecated Use {@link RedisClient#builder()} to configure the client with custom settings.\n   */\n  @Deprecated\n  public UnifiedJedis(CommandExecutor executor) {\n    this(executor, (ConnectionProvider) null);\n  }\n\n  private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider) {\n    this(executor, provider, new CommandObjects());\n  }\n\n  /**\n   * Uses a fetched connection to process protocol. Should be avoided if possible.\n   * @deprecated\n   */\n  @VisibleForTesting\n  @Deprecated\n  public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects) {\n    this(executor, provider, commandObjects, null, null);\n    if (this.provider != null) {\n      try (Connection conn = this.provider.getConnection()) {\n        if (conn != null) {\n          RedisProtocol proto = conn.getRedisProtocol();\n          if (proto != null) {\n            this.commandObjects.setProtocol(proto);\n          }\n        }\n      } catch (JedisException je) {\n      }\n    }\n  }\n\n  @Experimental\n  private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects,\n      RedisProtocol protocol) {\n    this(executor, provider, commandObjects, protocol, (Cache) null);\n  }\n\n  @Experimental\n  UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects,\n      RedisProtocol protocol, Cache cache) {\n\n    if (cache != null && protocol != RedisProtocol.RESP3) {\n      throw new IllegalArgumentException(\"Client-side caching is only supported with RESP3.\");\n    }\n\n    this.provider = provider;\n    this.executor = executor;\n\n    this.commandObjects = commandObjects;\n    if (protocol != null) {\n      this.commandObjects.setProtocol(protocol);\n    }\n\n    this.cache = cache;\n  }\n\n  @Override\n  public void close() {\n    IOUtils.closeQuietly(this.executor);\n  }\n\n  @Deprecated\n  protected final void setProtocol(RedisProtocol protocol) {\n    this.protocol = protocol;\n    this.commandObjects.setProtocol(this.protocol);\n  }\n\n  public final <T> T executeCommand(CommandObject<T> commandObject) {\n    return executor.executeCommand(commandObject);\n  }\n\n  public Cache getCache() {\n    return cache;\n  }\n\n  public String ping() {\n    return executeCommand(commandObjects.ping());\n  }\n\n  public String echo(String string) {\n    return executeCommand(commandObjects.echo(string));\n  }\n\n  public String flushDB() {\n    return executeCommand(commandObjects.flushDB());\n  }\n\n  public String flushAll() {\n    return executeCommand(commandObjects.flushAll());\n  }\n\n  public String configSet(String parameter, String value) {\n    return executeCommand(commandObjects.configSet(parameter, value));\n  }\n\n  public String info() {\n    return executeCommand(commandObjects.info());\n  }\n\n  public String info(String section) {\n    return executeCommand(commandObjects.info(section));\n  }\n\n  public String hotkeysStart(HotkeysParams params) {\n    return executeCommand(commandObjects.hotkeysStart(params));\n  }\n\n  public String hotkeysStop() {\n    return executeCommand(commandObjects.hotkeysStop());\n  }\n\n  public String hotkeysReset() {\n    return executeCommand(commandObjects.hotkeysReset());\n  }\n\n  public HotkeysInfo hotkeysGet() {\n    return executeCommand(commandObjects.hotkeysGet());\n  }\n\n  // Key commands\n  @Override\n  public boolean exists(String key) {\n    return executeCommand(commandObjects.exists(key));\n  }\n\n  @Override\n  public long exists(String... keys) {\n    return executeCommand(commandObjects.exists(keys));\n  }\n\n  @Override\n  public long persist(String key) {\n    return executeCommand(commandObjects.persist(key));\n  }\n\n  @Override\n  public String type(String key) {\n    return executeCommand(commandObjects.type(key));\n  }\n\n  @Override\n  public boolean exists(byte[] key) {\n    return executeCommand(commandObjects.exists(key));\n  }\n\n  @Override\n  public long exists(byte[]... keys) {\n    return executeCommand(commandObjects.exists(keys));\n  }\n\n  @Override\n  public long persist(byte[] key) {\n    return executeCommand(commandObjects.persist(key));\n  }\n\n  @Override\n  public String type(byte[] key) {\n    return executeCommand(commandObjects.type(key));\n  }\n\n  @Override\n  public byte[] dump(String key) {\n    return executeCommand(commandObjects.dump(key));\n  }\n\n  @Override\n  public String restore(String key, long ttl, byte[] serializedValue) {\n    return executeCommand(commandObjects.restore(key, ttl, serializedValue));\n  }\n\n  @Override\n  public String restore(String key, long ttl, byte[] serializedValue, RestoreParams params) {\n    return executeCommand(commandObjects.restore(key, ttl, serializedValue, params));\n  }\n\n  @Override\n  public byte[] dump(byte[] key) {\n    return executeCommand(commandObjects.dump(key));\n  }\n\n  @Override\n  public String restore(byte[] key, long ttl, byte[] serializedValue) {\n    return executeCommand(commandObjects.restore(key, ttl, serializedValue));\n  }\n\n  @Override\n  public String restore(byte[] key, long ttl, byte[] serializedValue, RestoreParams params) {\n    return executeCommand(commandObjects.restore(key, ttl, serializedValue, params));\n  }\n\n  @Override\n  public long expire(String key, long seconds) {\n    return executeCommand(commandObjects.expire(key, seconds));\n  }\n\n  @Override\n  public long expire(String key, long seconds, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.expire(key, seconds, expiryOption));\n  }\n\n  @Override\n  public long pexpire(String key, long milliseconds) {\n    return executeCommand(commandObjects.pexpire(key, milliseconds));\n  }\n\n  @Override\n  public long pexpire(String key, long milliseconds, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.pexpire(key, milliseconds, expiryOption));\n  }\n\n  @Override\n  public long expireTime(String key) {\n    return executeCommand(commandObjects.expireTime(key));\n  }\n\n  @Override\n  public long pexpireTime(String key) {\n    return executeCommand(commandObjects.pexpireTime(key));\n  }\n\n  @Override\n  public long expireAt(String key, long unixTime) {\n    return executeCommand(commandObjects.expireAt(key, unixTime));\n  }\n\n  @Override\n  public long expireAt(String key, long unixTime, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.expireAt(key, unixTime, expiryOption));\n  }\n\n  @Override\n  public long pexpireAt(String key, long millisecondsTimestamp) {\n    return executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp));\n  }\n\n  @Override\n  public long pexpireAt(String key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption));\n  }\n\n  @Override\n  public long expire(byte[] key, long seconds) {\n    return executeCommand(commandObjects.expire(key, seconds));\n  }\n\n  @Override\n  public long expire(byte[] key, long seconds, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.expire(key, seconds, expiryOption));\n  }\n\n  @Override\n  public long pexpire(byte[] key, long milliseconds) {\n    return executeCommand(commandObjects.pexpire(key, milliseconds));\n  }\n\n  @Override\n  public long pexpire(byte[] key, long milliseconds, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.pexpire(key, milliseconds, expiryOption));\n  }\n\n  @Override\n  public long expireTime(byte[] key) {\n    return executeCommand(commandObjects.expireTime(key));\n  }\n\n  @Override\n  public long pexpireTime(byte[] key) {\n    return executeCommand(commandObjects.pexpireTime(key));\n  }\n\n  @Override\n  public long expireAt(byte[] key, long unixTime) {\n    return executeCommand(commandObjects.expireAt(key, unixTime));\n  }\n\n  @Override\n  public long expireAt(byte[] key, long unixTime, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.expireAt(key, unixTime, expiryOption));\n  }\n\n  @Override\n  public long pexpireAt(byte[] key, long millisecondsTimestamp) {\n    return executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp));\n  }\n\n  @Override\n  public long pexpireAt(byte[] key, long millisecondsTimestamp, ExpiryOption expiryOption) {\n    return executeCommand(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption));\n  }\n\n  @Override\n  public long ttl(String key) {\n    return executeCommand(commandObjects.ttl(key));\n  }\n\n  @Override\n  public long pttl(String key) {\n    return executeCommand(commandObjects.pttl(key));\n  }\n\n  @Override\n  public long touch(String key) {\n    return executeCommand(commandObjects.touch(key));\n  }\n\n  @Override\n  public long touch(String... keys) {\n    return executeCommand(commandObjects.touch(keys));\n  }\n\n  @Override\n  public long ttl(byte[] key) {\n    return executeCommand(commandObjects.ttl(key));\n  }\n\n  @Override\n  public long pttl(byte[] key) {\n    return executeCommand(commandObjects.pttl(key));\n  }\n\n  @Override\n  public long touch(byte[] key) {\n    return executeCommand(commandObjects.touch(key));\n  }\n\n  @Override\n  public long touch(byte[]... keys) {\n    return executeCommand(commandObjects.touch(keys));\n  }\n\n  @Override\n  public List<String> sort(String key) {\n    return executeCommand(commandObjects.sort(key));\n  }\n\n  @Override\n  public List<String> sort(String key, SortingParams sortingParams) {\n    return executeCommand(commandObjects.sort(key, sortingParams));\n  }\n\n  @Override\n  public long sort(String key, String dstkey) {\n    return executeCommand(commandObjects.sort(key, dstkey));\n  }\n\n  @Override\n  public long sort(String key, SortingParams sortingParams, String dstkey) {\n    return executeCommand(commandObjects.sort(key, sortingParams, dstkey));\n  }\n\n  @Override\n  public List<String> sortReadonly(String key, SortingParams sortingParams) {\n    return executeCommand(commandObjects.sortReadonly(key, sortingParams));\n  }\n\n  @Override\n  public List<byte[]> sort(byte[] key) {\n    return executeCommand(commandObjects.sort(key));\n  }\n\n  @Override\n  public List<byte[]> sort(byte[] key, SortingParams sortingParams) {\n    return executeCommand(commandObjects.sort(key, sortingParams));\n  }\n\n  @Override\n  public long sort(byte[] key, byte[] dstkey) {\n    return executeCommand(commandObjects.sort(key, dstkey));\n  }\n\n  @Override\n  public List<byte[]> sortReadonly(byte[] key, SortingParams sortingParams) {\n    return executeCommand(commandObjects.sortReadonly(key, sortingParams));\n  }\n\n  @Override\n  public long sort(byte[] key, SortingParams sortingParams, byte[] dstkey) {\n    return executeCommand(commandObjects.sort(key, sortingParams, dstkey));\n  }\n\n  @Override\n  public long del(String key) {\n    return executeCommand(commandObjects.del(key));\n  }\n\n  @Override\n  public long delex(String key, CompareCondition condition) {\n    return executeCommand(commandObjects.delex(key, condition));\n  }\n\n  @Override\n  public long del(String... keys) {\n    return executeCommand(commandObjects.del(keys));\n  }\n\n  @Override\n  public long unlink(String key) {\n    return executeCommand(commandObjects.unlink(key));\n  }\n\n  @Override\n  public long delex(byte[] key, CompareCondition condition) {\n    return executeCommand(commandObjects.delex(key, condition));\n  }\n\n  @Override\n  public long unlink(String... keys) {\n    return executeCommand(commandObjects.unlink(keys));\n  }\n\n  @Override\n  public long del(byte[] key) {\n    return executeCommand(commandObjects.del(key));\n  }\n\n  @Override\n  public long del(byte[]... keys) {\n    return executeCommand(commandObjects.del(keys));\n  }\n\n  @Override\n  public long unlink(byte[] key) {\n    return executeCommand(commandObjects.unlink(key));\n  }\n\n  @Override\n  public long unlink(byte[]... keys) {\n    return executeCommand(commandObjects.unlink(keys));\n  }\n\n  @Override\n  public Long memoryUsage(String key) {\n    return executeCommand(commandObjects.memoryUsage(key));\n  }\n\n  @Override\n  public Long memoryUsage(String key, int samples) {\n    return executeCommand(commandObjects.memoryUsage(key, samples));\n  }\n\n  @Override\n  public Long memoryUsage(byte[] key) {\n    return executeCommand(commandObjects.memoryUsage(key));\n  }\n\n  @Override\n  public Long memoryUsage(byte[] key, int samples) {\n    return executeCommand(commandObjects.memoryUsage(key, samples));\n  }\n\n  @Override\n  public boolean copy(String srcKey, String dstKey, boolean replace) {\n    return executeCommand(commandObjects.copy(srcKey, dstKey, replace));\n  }\n\n  @Override\n  public String rename(String oldkey, String newkey) {\n    return executeCommand(commandObjects.rename(oldkey, newkey));\n  }\n\n  @Override\n  public long renamenx(String oldkey, String newkey) {\n    return executeCommand(commandObjects.renamenx(oldkey, newkey));\n  }\n\n  @Override\n  public boolean copy(byte[] srcKey, byte[] dstKey, boolean replace) {\n    return executeCommand(commandObjects.copy(srcKey, dstKey, replace));\n  }\n\n  @Override\n  public String rename(byte[] oldkey, byte[] newkey) {\n    return executeCommand(commandObjects.rename(oldkey, newkey));\n  }\n\n  @Override\n  public long renamenx(byte[] oldkey, byte[] newkey) {\n    return executeCommand(commandObjects.renamenx(oldkey, newkey));\n  }\n\n  public long dbSize() {\n    return executeCommand(commandObjects.dbSize());\n  }\n\n  @Override\n  public Set<String> keys(String pattern) {\n    return executeCommand(commandObjects.keys(pattern));\n  }\n\n  @Override\n  public ScanResult<String> scan(String cursor) {\n    return executeCommand(commandObjects.scan(cursor));\n  }\n\n  @Override\n  public ScanResult<String> scan(String cursor, ScanParams params) {\n    return executeCommand(commandObjects.scan(cursor, params));\n  }\n\n  @Override\n  public ScanResult<String> scan(String cursor, ScanParams params, String type) {\n    return executeCommand(commandObjects.scan(cursor, params, type));\n  }\n\n  /**\n   * @param batchCount COUNT for each batch execution\n   * @param match pattern\n   * @return scan iteration\n   */\n  public ScanIteration scanIteration(int batchCount, String match) {\n    return new ScanIteration(provider, batchCount, match);\n  }\n\n  /**\n   * @param batchCount COUNT for each batch execution\n   * @param match pattern\n   * @param type key type\n   * @return scan iteration\n   */\n  public ScanIteration scanIteration(int batchCount, String match, String type) {\n    return new ScanIteration(provider, batchCount, match, type);\n  }\n\n  @Override\n  public Set<byte[]> keys(byte[] pattern) {\n    return executeCommand(commandObjects.keys(pattern));\n  }\n\n  @Override\n  public ScanResult<byte[]> scan(byte[] cursor) {\n    return executeCommand(commandObjects.scan(cursor));\n  }\n\n  @Override\n  public ScanResult<byte[]> scan(byte[] cursor, ScanParams params) {\n    return executeCommand(commandObjects.scan(cursor, params));\n  }\n\n  @Override\n  public ScanResult<byte[]> scan(byte[] cursor, ScanParams params, byte[] type) {\n    return executeCommand(commandObjects.scan(cursor, params, type));\n  }\n\n  @Override\n  public String randomKey() {\n    return executeCommand(commandObjects.randomKey());\n  }\n\n  @Override\n  public byte[] randomBinaryKey() {\n    return executeCommand(commandObjects.randomBinaryKey());\n  }\n  // Key commands\n\n  // String commands\n  @Override\n  public String set(String key, String value) {\n    return executeCommand(commandObjects.set(key, value));\n  }\n\n  @Override\n  public String set(String key, String value, SetParams params) {\n    return executeCommand(commandObjects.set(key, value, params));\n  }\n\n  @Override\n  public String get(String key) {\n    return executeCommand(commandObjects.get(key));\n  }\n\n  @Override\n  public String digestKey(String key) {\n    return executeCommand(commandObjects.digestKey(key));\n  }\n\n  @Override\n  public String setGet(String key, String value) {\n    return executeCommand(commandObjects.setGet(key, value));\n  }\n\n  @Override\n  public String setGet(String key, String value, SetParams params) {\n    return executeCommand(commandObjects.setGet(key, value, params));\n  }\n\n  @Override\n  public String getDel(String key) {\n    return executeCommand(commandObjects.getDel(key));\n  }\n\n  @Override\n  public String getEx(String key, GetExParams params) {\n    return executeCommand(commandObjects.getEx(key, params));\n  }\n\n  @Override\n  public String set(byte[] key, byte[] value) {\n    return executeCommand(commandObjects.set(key, value));\n  }\n\n  @Override\n  public String set(byte[] key, byte[] value, SetParams params) {\n    return executeCommand(commandObjects.set(key, value, params));\n  }\n\n  @Override\n  public byte[] get(byte[] key) {\n    return executeCommand(commandObjects.get(key));\n  }\n\n  @Override\n  public byte[] digestKey(byte[] key) {\n    return executeCommand(commandObjects.digestKey(key));\n  }\n\n  @Override\n  public byte[] setGet(byte[] key, byte[] value) {\n    return executeCommand(commandObjects.setGet(key, value));\n  }\n\n  @Override\n  public byte[] setGet(byte[] key, byte[] value, SetParams params) {\n    return executeCommand(commandObjects.setGet(key, value, params));\n  }\n\n  @Override\n  public byte[] getDel(byte[] key) {\n    return executeCommand(commandObjects.getDel(key));\n  }\n\n  @Override\n  public byte[] getEx(byte[] key, GetExParams params) {\n    return executeCommand(commandObjects.getEx(key, params));\n  }\n\n  @Override\n  public boolean setbit(String key, long offset, boolean value) {\n    return executeCommand(commandObjects.setbit(key, offset, value));\n  }\n\n  @Override\n  public boolean getbit(String key, long offset) {\n    return executeCommand(commandObjects.getbit(key, offset));\n  }\n\n  @Override\n  public long setrange(String key, long offset, String value) {\n    return executeCommand(commandObjects.setrange(key, offset, value));\n  }\n\n  @Override\n  public String getrange(String key, long startOffset, long endOffset) {\n    return executeCommand(commandObjects.getrange(key, startOffset, endOffset));\n  }\n\n  @Override\n  public boolean setbit(byte[] key, long offset, boolean value) {\n    return executeCommand(commandObjects.setbit(key, offset, value));\n  }\n\n  @Override\n  public boolean getbit(byte[] key, long offset) {\n    return executeCommand(commandObjects.getbit(key, offset));\n  }\n\n  @Override\n  public long setrange(byte[] key, long offset, byte[] value) {\n    return executeCommand(commandObjects.setrange(key, offset, value));\n  }\n\n  @Override\n  public byte[] getrange(byte[] key, long startOffset, long endOffset) {\n    return executeCommand(commandObjects.getrange(key, startOffset, endOffset));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#setGet(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  @Override\n  public String getSet(String key, String value) {\n    return executeCommand(commandObjects.getSet(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public long setnx(String key, String value) {\n    return executeCommand(commandObjects.setnx(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String setex(String key, long seconds, String value) {\n    return executeCommand(commandObjects.setex(key, seconds, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#set(String, String, redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String psetex(String key, long milliseconds, String value) {\n    return executeCommand(commandObjects.psetex(key, milliseconds, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#setGet(byte[], byte[])}.\n   */\n  @Deprecated\n  @Override\n  public byte[] getSet(byte[] key, byte[] value) {\n    return executeCommand(commandObjects.getSet(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public long setnx(byte[] key, byte[] value) {\n    return executeCommand(commandObjects.setnx(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String setex(byte[] key, long seconds, byte[] value) {\n    return executeCommand(commandObjects.setex(key, seconds, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#set(byte[], byte[], redis.clients.jedis.params.SetParams)} with {@link redis.clients.jedis.params.SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  @Override\n  public String psetex(byte[] key, long milliseconds, byte[] value) {\n    return executeCommand(commandObjects.psetex(key, milliseconds, value));\n  }\n\n  @Override\n  public long incr(String key) {\n    return executeCommand(commandObjects.incr(key));\n  }\n\n  @Override\n  public long incrBy(String key, long increment) {\n    return executeCommand(commandObjects.incrBy(key, increment));\n  }\n\n  @Override\n  public double incrByFloat(String key, double increment) {\n    return executeCommand(commandObjects.incrByFloat(key, increment));\n  }\n\n  @Override\n  public long decr(String key) {\n    return executeCommand(commandObjects.decr(key));\n  }\n\n  @Override\n  public long decrBy(String key, long decrement) {\n    return executeCommand(commandObjects.decrBy(key, decrement));\n  }\n\n  @Override\n  public long incr(byte[] key) {\n    return executeCommand(commandObjects.incr(key));\n  }\n\n  @Override\n  public long incrBy(byte[] key, long increment) {\n    return executeCommand(commandObjects.incrBy(key, increment));\n  }\n\n  @Override\n  public double incrByFloat(byte[] key, double increment) {\n    return executeCommand(commandObjects.incrByFloat(key, increment));\n  }\n\n  @Override\n  public long decr(byte[] key) {\n    return executeCommand(commandObjects.decr(key));\n  }\n\n  @Override\n  public long decrBy(byte[] key, long decrement) {\n    return executeCommand(commandObjects.decrBy(key, decrement));\n  }\n\n  @Override\n  public List<String> mget(String... keys) {\n    return executeCommand(commandObjects.mget(keys));\n  }\n\n  @Override\n  public String mset(String... keysvalues) {\n    return executeCommand(commandObjects.mset(keysvalues));\n  }\n\n  @Override\n  public long msetnx(String... keysvalues) {\n    return executeCommand(commandObjects.msetnx(keysvalues));\n  }\n\n  @Override\n  public boolean msetex(MSetExParams params, String... keysvalues) {\n    return executeCommand(commandObjects.msetex(params, keysvalues));\n  }\n\n  @Override\n  public List<byte[]> mget(byte[]... keys) {\n    return executeCommand(commandObjects.mget(keys));\n  }\n\n  @Override\n  public String mset(byte[]... keysvalues) {\n    return executeCommand(commandObjects.mset(keysvalues));\n  }\n\n  @Override\n  public boolean msetex(MSetExParams params, byte[]... keysvalues) {\n    return executeCommand(commandObjects.msetex(params, keysvalues));\n  }\n\n  @Override\n  public long msetnx(byte[]... keysvalues) {\n    return executeCommand(commandObjects.msetnx(keysvalues));\n  }\n\n  @Override\n  public long append(String key, String value) {\n    return executeCommand(commandObjects.append(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#getrange(String, long, long)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  @Override\n  public String substr(String key, int start, int end) {\n    return executeCommand(commandObjects.substr(key, start, end));\n  }\n\n  @Override\n  public long strlen(String key) {\n    return executeCommand(commandObjects.strlen(key));\n  }\n\n  @Override\n  public long append(byte[] key, byte[] value) {\n    return executeCommand(commandObjects.append(key, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#getrange(byte[], long, long)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  @Override\n  public byte[] substr(byte[] key, int start, int end) {\n    return executeCommand(commandObjects.substr(key, start, end));\n  }\n\n  @Override\n  public long strlen(byte[] key) {\n    return executeCommand(commandObjects.strlen(key));\n  }\n\n  @Override\n  public long bitcount(String key) {\n    return executeCommand(commandObjects.bitcount(key));\n  }\n\n  @Override\n  public long bitcount(String key, long start, long end) {\n    return executeCommand(commandObjects.bitcount(key, start, end));\n  }\n\n  @Override\n  public long bitcount(String key, long start, long end, BitCountOption option) {\n    return executeCommand(commandObjects.bitcount(key, start, end, option));\n  }\n\n  @Override\n  public long bitpos(String key, boolean value) {\n    return executeCommand(commandObjects.bitpos(key, value));\n  }\n\n  @Override\n  public long bitpos(String key, boolean value, BitPosParams params) {\n    return executeCommand(commandObjects.bitpos(key, value, params));\n  }\n\n  @Override\n  public long bitcount(byte[] key) {\n    return executeCommand(commandObjects.bitcount(key));\n  }\n\n  @Override\n  public long bitcount(byte[] key, long start, long end) {\n    return executeCommand(commandObjects.bitcount(key, start, end));\n  }\n\n  @Override\n  public long bitcount(byte[] key, long start, long end, BitCountOption option) {\n    return executeCommand(commandObjects.bitcount(key, start, end, option));\n  }\n\n  @Override\n  public long bitpos(byte[] key, boolean value) {\n    return executeCommand(commandObjects.bitpos(key, value));\n  }\n\n  @Override\n  public long bitpos(byte[] key, boolean value, BitPosParams params) {\n    return executeCommand(commandObjects.bitpos(key, value, params));\n  }\n\n  @Override\n  public List<Long> bitfield(String key, String... arguments) {\n    return executeCommand(commandObjects.bitfield(key, arguments));\n  }\n\n  @Override\n  public List<Long> bitfieldReadonly(String key, String... arguments) {\n    return executeCommand(commandObjects.bitfieldReadonly(key, arguments));\n  }\n\n  @Override\n  public List<Long> bitfield(byte[] key, byte[]... arguments) {\n    return executeCommand(commandObjects.bitfield(key, arguments));\n  }\n\n  @Override\n  public List<Long> bitfieldReadonly(byte[] key, byte[]... arguments) {\n    return executeCommand(commandObjects.bitfieldReadonly(key, arguments));\n  }\n\n  @Override\n  public long bitop(BitOP op, String destKey, String... srcKeys) {\n    return executeCommand(commandObjects.bitop(op, destKey, srcKeys));\n  }\n\n  @Override\n  public long bitop(BitOP op, byte[] destKey, byte[]... srcKeys) {\n    return executeCommand(commandObjects.bitop(op, destKey, srcKeys));\n  }\n\n  @Override\n  public LCSMatchResult lcs(String keyA, String keyB, LCSParams params) {\n    return executeCommand(commandObjects.lcs(keyA, keyB, params));\n  }\n\n  @Override\n  public LCSMatchResult lcs(byte[] keyA, byte[] keyB, LCSParams params) {\n    return executeCommand(commandObjects.lcs(keyA, keyB, params));\n  }\n  // String commands\n\n  // List commands\n  @Override\n  public long rpush(String key, String... string) {\n    return executeCommand(commandObjects.rpush(key, string));\n  }\n\n  @Override\n  public long lpush(String key, String... string) {\n    return executeCommand(commandObjects.lpush(key, string));\n  }\n\n  @Override\n  public long llen(String key) {\n    return executeCommand(commandObjects.llen(key));\n  }\n\n  @Override\n  public List<String> lrange(String key, long start, long stop) {\n    return executeCommand(commandObjects.lrange(key, start, stop));\n  }\n\n  @Override\n  public String ltrim(String key, long start, long stop) {\n    return executeCommand(commandObjects.ltrim(key, start, stop));\n  }\n\n  @Override\n  public String lindex(String key, long index) {\n    return executeCommand(commandObjects.lindex(key, index));\n  }\n\n  @Override\n  public long rpush(byte[] key, byte[]... args) {\n    return executeCommand(commandObjects.rpush(key, args));\n  }\n\n  @Override\n  public long lpush(byte[] key, byte[]... args) {\n    return executeCommand(commandObjects.lpush(key, args));\n  }\n\n  @Override\n  public long llen(byte[] key) {\n    return executeCommand(commandObjects.llen(key));\n  }\n\n  @Override\n  public List<byte[]> lrange(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.lrange(key, start, stop));\n  }\n\n  @Override\n  public String ltrim(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.ltrim(key, start, stop));\n  }\n\n  @Override\n  public byte[] lindex(byte[] key, long index) {\n    return executeCommand(commandObjects.lindex(key, index));\n  }\n\n  @Override\n  public String lset(String key, long index, String value) {\n    return executeCommand(commandObjects.lset(key, index, value));\n  }\n\n  @Override\n  public long lrem(String key, long count, String value) {\n    return executeCommand(commandObjects.lrem(key, count, value));\n  }\n\n  @Override\n  public String lpop(String key) {\n    return executeCommand(commandObjects.lpop(key));\n  }\n\n  @Override\n  public List<String> lpop(String key, int count) {\n    return executeCommand(commandObjects.lpop(key, count));\n  }\n\n  @Override\n  public String lset(byte[] key, long index, byte[] value) {\n    return executeCommand(commandObjects.lset(key, index, value));\n  }\n\n  @Override\n  public long lrem(byte[] key, long count, byte[] value) {\n    return executeCommand(commandObjects.lrem(key, count, value));\n  }\n\n  @Override\n  public byte[] lpop(byte[] key) {\n    return executeCommand(commandObjects.lpop(key));\n  }\n\n  @Override\n  public List<byte[]> lpop(byte[] key, int count) {\n    return executeCommand(commandObjects.lpop(key, count));\n  }\n\n  @Override\n  public Long lpos(String key, String element) {\n    return executeCommand(commandObjects.lpos(key, element));\n  }\n\n  @Override\n  public Long lpos(String key, String element, LPosParams params) {\n    return executeCommand(commandObjects.lpos(key, element, params));\n  }\n\n  @Override\n  public List<Long> lpos(String key, String element, LPosParams params, long count) {\n    return executeCommand(commandObjects.lpos(key, element, params, count));\n  }\n\n  @Override\n  public Long lpos(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.lpos(key, element));\n  }\n\n  @Override\n  public Long lpos(byte[] key, byte[] element, LPosParams params) {\n    return executeCommand(commandObjects.lpos(key, element, params));\n  }\n\n  @Override\n  public List<Long> lpos(byte[] key, byte[] element, LPosParams params, long count) {\n    return executeCommand(commandObjects.lpos(key, element, params, count));\n  }\n\n  @Override\n  public String rpop(String key) {\n    return executeCommand(commandObjects.rpop(key));\n  }\n\n  @Override\n  public List<String> rpop(String key, int count) {\n    return executeCommand(commandObjects.rpop(key, count));\n  }\n\n  @Override\n  public byte[] rpop(byte[] key) {\n    return executeCommand(commandObjects.rpop(key));\n  }\n\n  @Override\n  public List<byte[]> rpop(byte[] key, int count) {\n    return executeCommand(commandObjects.rpop(key, count));\n  }\n\n  @Override\n  public long linsert(String key, ListPosition where, String pivot, String value) {\n    return executeCommand(commandObjects.linsert(key, where, pivot, value));\n  }\n\n  @Override\n  public long lpushx(String key, String... strings) {\n    return executeCommand(commandObjects.lpushx(key, strings));\n  }\n\n  @Override\n  public long rpushx(String key, String... strings) {\n    return executeCommand(commandObjects.rpushx(key, strings));\n  }\n\n  @Override\n  public long linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value) {\n    return executeCommand(commandObjects.linsert(key, where, pivot, value));\n  }\n\n  @Override\n  public long lpushx(byte[] key, byte[]... args) {\n    return executeCommand(commandObjects.lpushx(key, args));\n  }\n\n  @Override\n  public long rpushx(byte[] key, byte[]... args) {\n    return executeCommand(commandObjects.rpushx(key, args));\n  }\n\n  @Override\n  public List<String> blpop(int timeout, String key) {\n    return executeCommand(commandObjects.blpop(timeout, key));\n  }\n\n  @Override\n  public KeyValue<String, String> blpop(double timeout, String key) {\n    return executeCommand(commandObjects.blpop(timeout, key));\n  }\n\n  @Override\n  public List<String> brpop(int timeout, String key) {\n    return executeCommand(commandObjects.brpop(timeout, key));\n  }\n\n  @Override\n  public KeyValue<String, String> brpop(double timeout, String key) {\n    return executeCommand(commandObjects.brpop(timeout, key));\n  }\n\n  @Override\n  public List<String> blpop(int timeout, String... keys) {\n    return executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, String> blpop(double timeout, String... keys) {\n    return executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public List<String> brpop(int timeout, String... keys) {\n    return executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, String> brpop(double timeout, String... keys) {\n    return executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public List<byte[]> blpop(int timeout, byte[]... keys) {\n    return executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], byte[]> blpop(double timeout, byte[]... keys) {\n    return executeCommand(commandObjects.blpop(timeout, keys));\n  }\n\n  @Override\n  public List<byte[]> brpop(int timeout, byte[]... keys) {\n    return executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], byte[]> brpop(double timeout, byte[]... keys) {\n    return executeCommand(commandObjects.brpop(timeout, keys));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#lmove(String, String, ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public String rpoplpush(String srckey, String dstkey) {\n    return executeCommand(commandObjects.rpoplpush(srckey, dstkey));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#blmove(String, String, ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public String brpoplpush(String source, String destination, int timeout) {\n    return executeCommand(commandObjects.brpoplpush(source, destination, timeout));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#lmove(byte[], byte[], ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public byte[] rpoplpush(byte[] srckey, byte[] dstkey) {\n    return executeCommand(commandObjects.rpoplpush(srckey, dstkey));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#blmove(byte[], byte[], ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public byte[] brpoplpush(byte[] source, byte[] destination, int timeout) {\n    return executeCommand(commandObjects.brpoplpush(source, destination, timeout));\n  }\n\n  @Override\n  public String lmove(String srcKey, String dstKey, ListDirection from, ListDirection to) {\n    return executeCommand(commandObjects.lmove(srcKey, dstKey, from, to));\n  }\n\n  @Override\n  public String blmove(String srcKey, String dstKey, ListDirection from, ListDirection to, double timeout) {\n    return executeCommand(commandObjects.blmove(srcKey, dstKey, from, to, timeout));\n  }\n\n  @Override\n  public byte[] lmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to) {\n    return executeCommand(commandObjects.lmove(srcKey, dstKey, from, to));\n  }\n\n  @Override\n  public byte[] blmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to, double timeout) {\n    return executeCommand(commandObjects.blmove(srcKey, dstKey, from, to, timeout));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> lmpop(ListDirection direction, String... keys) {\n    return executeCommand(commandObjects.lmpop(direction, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> lmpop(ListDirection direction, int count, String... keys) {\n    return executeCommand(commandObjects.lmpop(direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> blmpop(double timeout, ListDirection direction, String... keys) {\n    return executeCommand(commandObjects.blmpop(timeout, direction, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<String>> blmpop(double timeout, ListDirection direction, int count, String... keys) {\n    return executeCommand(commandObjects.blmpop(timeout, direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> lmpop(ListDirection direction, byte[]... keys) {\n    return executeCommand(commandObjects.lmpop(direction, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> lmpop(ListDirection direction, int count, byte[]... keys) {\n    return executeCommand(commandObjects.lmpop(direction, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> blmpop(double timeout, ListDirection direction, byte[]... keys) {\n    return executeCommand(commandObjects.blmpop(timeout, direction, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<byte[]>> blmpop(double timeout, ListDirection direction, int count, byte[]... keys) {\n    return executeCommand(commandObjects.blmpop(timeout, direction, count, keys));\n  }\n  // List commands\n\n  // Hash commands\n  @Override\n  public long hset(String key, String field, String value) {\n    return executeCommand(commandObjects.hset(key, field, value));\n  }\n\n  @Override\n  public long hset(String key, Map<String, String> hash) {\n    return executeCommand(commandObjects.hset(key, hash));\n  }\n\n  @Override\n  public long hsetex(String key, HSetExParams params, String field, String value) {\n   return executeCommand(commandObjects.hsetex(key, params, field, value));\n  }\n\n  @Override\n  public long hsetex(String key, HSetExParams params, Map<String, String> hash) {\n    return executeCommand(commandObjects.hsetex(key, params, hash));\n  }\n\n  @Override\n  public String hget(String key, String field) {\n    return executeCommand(commandObjects.hget(key, field));\n  }\n\n  @Override\n  public List<String> hgetex(String key, HGetExParams params, String... fields) {\n    return executeCommand(commandObjects.hgetex(key, params, fields));\n  }\n\n  @Override\n  public List<String> hgetdel(String key, String... fields) {\n    return executeCommand(commandObjects.hgetdel(key, fields));\n  }\n\n  @Override\n  public long hsetnx(String key, String field, String value) {\n    return executeCommand(commandObjects.hsetnx(key, field, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#hset(String, Map)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  @Override\n  public String hmset(String key, Map<String, String> hash) {\n    return executeCommand(commandObjects.hmset(key, hash));\n  }\n\n  @Override\n  public List<String> hmget(String key, String... fields) {\n    return executeCommand(commandObjects.hmget(key, fields));\n  }\n\n  @Override\n  public long hset(byte[] key, byte[] field, byte[] value) {\n    return executeCommand(commandObjects.hset(key, field, value));\n  }\n\n  @Override\n  public long hset(byte[] key, Map<byte[], byte[]> hash) {\n    return executeCommand(commandObjects.hset(key, hash));\n  }\n\n  @Override\n  public long hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) {\n   return executeCommand(commandObjects.hsetex(key, params, field, value));\n  }\n\n  @Override\n  public long hsetex(byte[] key, HSetExParams params, Map<byte[], byte[]> hash) {\n    return executeCommand(commandObjects.hsetex(key, params, hash));\n  }\n\n  @Override\n  public byte[] hget(byte[] key, byte[] field) {\n    return executeCommand(commandObjects.hget(key, field));\n  }\n\n  @Override\n  public List<byte[]> hgetex(byte[] key, HGetExParams params, byte[]... fields) {\n    return executeCommand(commandObjects.hgetex(key, params, fields));\n  }\n\n  @Override\n  public List<byte[]> hgetdel(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.hgetdel(key, fields));\n  }\n\n  @Override\n  public long hsetnx(byte[] key, byte[] field, byte[] value) {\n    return executeCommand(commandObjects.hsetnx(key, field, value));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#hset(byte[], Map)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  @Override\n  public String hmset(byte[] key, Map<byte[], byte[]> hash) {\n    return executeCommand(commandObjects.hmset(key, hash));\n  }\n\n  @Override\n  public List<byte[]> hmget(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.hmget(key, fields));\n  }\n\n  @Override\n  public long hincrBy(String key, String field, long value) {\n    return executeCommand(commandObjects.hincrBy(key, field, value));\n  }\n\n  @Override\n  public double hincrByFloat(String key, String field, double value) {\n    return executeCommand(commandObjects.hincrByFloat(key, field, value));\n  }\n\n  @Override\n  public boolean hexists(String key, String field) {\n    return executeCommand(commandObjects.hexists(key, field));\n  }\n\n  @Override\n  public long hdel(String key, String... field) {\n    return executeCommand(commandObjects.hdel(key, field));\n  }\n\n  @Override\n  public long hlen(String key) {\n    return executeCommand(commandObjects.hlen(key));\n  }\n\n  @Override\n  public long hincrBy(byte[] key, byte[] field, long value) {\n    return executeCommand(commandObjects.hincrBy(key, field, value));\n  }\n\n  @Override\n  public double hincrByFloat(byte[] key, byte[] field, double value) {\n    return executeCommand(commandObjects.hincrByFloat(key, field, value));\n  }\n\n  @Override\n  public boolean hexists(byte[] key, byte[] field) {\n    return executeCommand(commandObjects.hexists(key, field));\n  }\n\n  @Override\n  public long hdel(byte[] key, byte[]... field) {\n    return executeCommand(commandObjects.hdel(key, field));\n  }\n\n  @Override\n  public long hlen(byte[] key) {\n    return executeCommand(commandObjects.hlen(key));\n  }\n\n  @Override\n  public Set<String> hkeys(String key) {\n    return executeCommand(commandObjects.hkeys(key));\n  }\n\n  @Override\n  public List<String> hvals(String key) {\n    return executeCommand(commandObjects.hvals(key));\n  }\n\n  @Override\n  public Map<String, String> hgetAll(String key) {\n    return executeCommand(commandObjects.hgetAll(key));\n  }\n\n  @Override\n  public Set<byte[]> hkeys(byte[] key) {\n    return executeCommand(commandObjects.hkeys(key));\n  }\n\n  @Override\n  public List<byte[]> hvals(byte[] key) {\n    return executeCommand(commandObjects.hvals(key));\n  }\n\n  @Override\n  public Map<byte[], byte[]> hgetAll(byte[] key) {\n    return executeCommand(commandObjects.hgetAll(key));\n  }\n\n  @Override\n  public String hrandfield(String key) {\n    return executeCommand(commandObjects.hrandfield(key));\n  }\n\n  @Override\n  public List<String> hrandfield(String key, long count) {\n    return executeCommand(commandObjects.hrandfield(key, count));\n  }\n\n  @Override\n  public List<Map.Entry<String, String>> hrandfieldWithValues(String key, long count) {\n    return executeCommand(commandObjects.hrandfieldWithValues(key, count));\n  }\n\n  @Override\n  public ScanResult<Map.Entry<String, String>> hscan(String key, String cursor, ScanParams params) {\n    return executeCommand(commandObjects.hscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<String> hscanNoValues(String key, String cursor, ScanParams params) {\n    return executeCommand(commandObjects.hscanNoValues(key, cursor, params));\n  }\n\n  @Override\n  public long hstrlen(String key, String field) {\n    return executeCommand(commandObjects.hstrlen(key, field));\n  }\n\n  @Override\n  public byte[] hrandfield(byte[] key) {\n    return executeCommand(commandObjects.hrandfield(key));\n  }\n\n  @Override\n  public List<byte[]> hrandfield(byte[] key, long count) {\n    return executeCommand(commandObjects.hrandfield(key, count));\n  }\n\n  @Override\n  public List<Map.Entry<byte[], byte[]>> hrandfieldWithValues(byte[] key, long count) {\n    return executeCommand(commandObjects.hrandfieldWithValues(key, count));\n  }\n\n  @Override\n  public ScanResult<Map.Entry<byte[], byte[]>> hscan(byte[] key, byte[] cursor, ScanParams params) {\n    return executeCommand(commandObjects.hscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<byte[]> hscanNoValues(byte[] key, byte[] cursor, ScanParams params) {\n    return executeCommand(commandObjects.hscanNoValues(key, cursor, params));\n  }\n\n  @Override\n  public long hstrlen(byte[] key, byte[] field) {\n    return executeCommand(commandObjects.hstrlen(key, field));\n  }\n\n  @Override\n  public List<Long> hexpire(String key, long seconds, String... fields) {\n    return executeCommand(commandObjects.hexpire(key, seconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpire(String key, long seconds, ExpiryOption condition, String... fields) {\n    return executeCommand(commandObjects.hexpire(key, seconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(String key, long milliseconds, String... fields) {\n    return executeCommand(commandObjects.hpexpire(key, milliseconds, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) {\n    return executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(String key, long unixTimeSeconds, String... fields) {\n    return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) {\n    return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(String key, long unixTimeMillis, String... fields) {\n    return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) {\n    return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpire(byte[] key, long seconds, byte[]... fields) {\n    return executeCommand(commandObjects.hexpire(key, seconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) {\n    return executeCommand(commandObjects.hexpire(key, seconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(byte[] key, long milliseconds, byte[]... fields) {\n    return executeCommand(commandObjects.hpexpire(key, milliseconds, fields));\n  }\n\n  @Override\n  public List<Long> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) {\n    return executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) {\n    return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields));\n  }\n\n  @Override\n  public List<Long> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) {\n    return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) {\n    return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) {\n    return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields));\n  }\n\n  @Override\n  public List<Long> hexpireTime(String key, String... fields) {\n    return executeCommand(commandObjects.hexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireTime(String key, String... fields) {\n    return executeCommand(commandObjects.hpexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> httl(String key, String... fields) {\n    return executeCommand(commandObjects.httl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpttl(String key, String... fields) {\n    return executeCommand(commandObjects.hpttl(key, fields));\n  }\n\n  @Override\n  public List<Long> hexpireTime(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.hexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> hpexpireTime(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.hpexpireTime(key, fields));\n  }\n\n  @Override\n  public List<Long> httl(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.httl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpttl(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.hpttl(key, fields));\n  }\n\n  @Override\n  public List<Long> hpersist(String key, String... fields) {\n    return executeCommand(commandObjects.hpersist(key, fields));\n  }\n\n  @Override\n  public List<Long> hpersist(byte[] key, byte[]... fields) {\n    return executeCommand(commandObjects.hpersist(key, fields));\n  }\n  // Hash commands\n\n  // Set commands\n  @Override\n  public long sadd(String key, String... members) {\n    return executeCommand(commandObjects.sadd(key, members));\n  }\n\n  @Override\n  public Set<String> smembers(String key) {\n    return executeCommand(commandObjects.smembers(key));\n  }\n\n  @Override\n  public long srem(String key, String... members) {\n    return executeCommand(commandObjects.srem(key, members));\n  }\n\n  @Override\n  public String spop(String key) {\n    return executeCommand(commandObjects.spop(key));\n  }\n\n  @Override\n  public Set<String> spop(String key, long count) {\n    return executeCommand(commandObjects.spop(key, count));\n  }\n\n  @Override\n  public long scard(String key) {\n    return executeCommand(commandObjects.scard(key));\n  }\n\n  @Override\n  public boolean sismember(String key, String member) {\n    return executeCommand(commandObjects.sismember(key, member));\n  }\n\n  @Override\n  public List<Boolean> smismember(String key, String... members) {\n    return executeCommand(commandObjects.smismember(key, members));\n  }\n\n  @Override\n  public long sadd(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.sadd(key, members));\n  }\n\n  @Override\n  public Set<byte[]> smembers(byte[] key) {\n    return executeCommand(commandObjects.smembers(key));\n  }\n\n  @Override\n  public long srem(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.srem(key, members));\n  }\n\n  @Override\n  public byte[] spop(byte[] key) {\n    return executeCommand(commandObjects.spop(key));\n  }\n\n  @Override\n  public Set<byte[]> spop(byte[] key, long count) {\n    return executeCommand(commandObjects.spop(key, count));\n  }\n\n  @Override\n  public long scard(byte[] key) {\n    return executeCommand(commandObjects.scard(key));\n  }\n\n  @Override\n  public boolean sismember(byte[] key, byte[] member) {\n    return executeCommand(commandObjects.sismember(key, member));\n  }\n\n  @Override\n  public List<Boolean> smismember(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.smismember(key, members));\n  }\n\n  @Override\n  public String srandmember(String key) {\n    return executeCommand(commandObjects.srandmember(key));\n  }\n\n  @Override\n  public List<String> srandmember(String key, int count) {\n    return executeCommand(commandObjects.srandmember(key, count));\n  }\n\n  @Override\n  public ScanResult<String> sscan(String key, String cursor, ScanParams params) {\n    return executeCommand(commandObjects.sscan(key, cursor, params));\n  }\n\n  @Override\n  public byte[] srandmember(byte[] key) {\n    return executeCommand(commandObjects.srandmember(key));\n  }\n\n  @Override\n  public List<byte[]> srandmember(byte[] key, int count) {\n    return executeCommand(commandObjects.srandmember(key, count));\n  }\n\n  @Override\n  public ScanResult<byte[]> sscan(byte[] key, byte[] cursor, ScanParams params) {\n    return executeCommand(commandObjects.sscan(key, cursor, params));\n  }\n\n  @Override\n  public Set<String> sdiff(String... keys) {\n    return executeCommand(commandObjects.sdiff(keys));\n  }\n\n  @Override\n  public long sdiffstore(String dstkey, String... keys) {\n    return executeCommand(commandObjects.sdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public Set<String> sinter(String... keys) {\n    return executeCommand(commandObjects.sinter(keys));\n  }\n\n  @Override\n  public long sinterstore(String dstkey, String... keys) {\n    return executeCommand(commandObjects.sinterstore(dstkey, keys));\n  }\n\n  @Override\n  public long sintercard(String... keys) {\n    return executeCommand(commandObjects.sintercard(keys));\n  }\n\n  @Override\n  public long sintercard(int limit, String... keys) {\n    return executeCommand(commandObjects.sintercard(limit, keys));\n  }\n\n  @Override\n  public Set<String> sunion(String... keys) {\n    return executeCommand(commandObjects.sunion(keys));\n  }\n\n  @Override\n  public long sunionstore(String dstkey, String... keys) {\n    return executeCommand(commandObjects.sunionstore(dstkey, keys));\n  }\n\n  @Override\n  public long smove(String srckey, String dstkey, String member) {\n    return executeCommand(commandObjects.smove(srckey, dstkey, member));\n  }\n\n  @Override\n  public Set<byte[]> sdiff(byte[]... keys) {\n    return executeCommand(commandObjects.sdiff(keys));\n  }\n\n  @Override\n  public long sdiffstore(byte[] dstkey, byte[]... keys) {\n    return executeCommand(commandObjects.sdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public Set<byte[]> sinter(byte[]... keys) {\n    return executeCommand(commandObjects.sinter(keys));\n  }\n\n  @Override\n  public long sinterstore(byte[] dstkey, byte[]... keys) {\n    return executeCommand(commandObjects.sinterstore(dstkey, keys));\n  }\n\n  @Override\n  public long sintercard(byte[]... keys) {\n    return executeCommand(commandObjects.sintercard(keys));\n  }\n\n  @Override\n  public long sintercard(int limit, byte[]... keys) {\n    return executeCommand(commandObjects.sintercard(limit, keys));\n  }\n\n  @Override\n  public Set<byte[]> sunion(byte[]... keys) {\n    return executeCommand(commandObjects.sunion(keys));\n  }\n\n  @Override\n  public long sunionstore(byte[] dstkey, byte[]... keys) {\n    return executeCommand(commandObjects.sunionstore(dstkey, keys));\n  }\n\n  @Override\n  public long smove(byte[] srckey, byte[] dstkey, byte[] member) {\n    return executeCommand(commandObjects.smove(srckey, dstkey, member));\n  }\n  // Set commands\n\n  // Sorted Set commands\n  @Override\n  public long zadd(String key, double score, String member) {\n    return executeCommand(commandObjects.zadd(key, score, member));\n  }\n\n  @Override\n  public long zadd(String key, double score, String member, ZAddParams params) {\n    return executeCommand(commandObjects.zadd(key, score, member, params));\n  }\n\n  @Override\n  public long zadd(String key, Map<String, Double> scoreMembers) {\n    return executeCommand(commandObjects.zadd(key, scoreMembers));\n  }\n\n  @Override\n  public long zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {\n    return executeCommand(commandObjects.zadd(key, scoreMembers, params));\n  }\n\n  @Override\n  public Double zaddIncr(String key, double score, String member, ZAddParams params) {\n    return executeCommand(commandObjects.zaddIncr(key, score, member, params));\n  }\n\n  @Override\n  public long zadd(byte[] key, double score, byte[] member) {\n    return executeCommand(commandObjects.zadd(key, score, member));\n  }\n\n  @Override\n  public long zadd(byte[] key, double score, byte[] member, ZAddParams params) {\n    return executeCommand(commandObjects.zadd(key, score, member, params));\n  }\n\n  @Override\n  public long zadd(byte[] key, Map<byte[], Double> scoreMembers) {\n    return executeCommand(commandObjects.zadd(key, scoreMembers));\n  }\n\n  @Override\n  public long zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params) {\n    return executeCommand(commandObjects.zadd(key, scoreMembers, params));\n  }\n\n  @Override\n  public Double zaddIncr(byte[] key, double score, byte[] member, ZAddParams params) {\n    return executeCommand(commandObjects.zaddIncr(key, score, member, params));\n  }\n\n  @Override\n  public long zrem(String key, String... members) {\n    return executeCommand(commandObjects.zrem(key, members));\n  }\n\n  @Override\n  public double zincrby(String key, double increment, String member) {\n    return executeCommand(commandObjects.zincrby(key, increment, member));\n  }\n\n  @Override\n  public Double zincrby(String key, double increment, String member, ZIncrByParams params) {\n    return executeCommand(commandObjects.zincrby(key, increment, member, params));\n  }\n\n  @Override\n  public Long zrank(String key, String member) {\n    return executeCommand(commandObjects.zrank(key, member));\n  }\n\n  @Override\n  public Long zrevrank(String key, String member) {\n    return executeCommand(commandObjects.zrevrank(key, member));\n  }\n\n  @Override\n  public KeyValue<Long, Double> zrankWithScore(String key, String member) {\n    return executeCommand(commandObjects.zrankWithScore(key, member));\n  }\n\n  @Override\n  public KeyValue<Long, Double> zrevrankWithScore(String key, String member) {\n    return executeCommand(commandObjects.zrevrankWithScore(key, member));\n  }\n\n  @Override\n  public long zrem(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.zrem(key, members));\n  }\n\n  @Override\n  public double zincrby(byte[] key, double increment, byte[] member) {\n    return executeCommand(commandObjects.zincrby(key, increment, member));\n  }\n\n  @Override\n  public Double zincrby(byte[] key, double increment, byte[] member, ZIncrByParams params) {\n    return executeCommand(commandObjects.zincrby(key, increment, member, params));\n  }\n\n  @Override\n  public Long zrank(byte[] key, byte[] member) {\n    return executeCommand(commandObjects.zrank(key, member));\n  }\n\n  @Override\n  public Long zrevrank(byte[] key, byte[] member) {\n    return executeCommand(commandObjects.zrevrank(key, member));\n  }\n\n  @Override\n  public KeyValue<Long, Double> zrankWithScore(byte[] key, byte[] member) {\n    return executeCommand(commandObjects.zrankWithScore(key, member));\n  }\n\n  @Override\n  public KeyValue<Long, Double> zrevrankWithScore(byte[] key, byte[] member) {\n    return executeCommand(commandObjects.zrevrankWithScore(key, member));\n  }\n\n  @Override\n  public String zrandmember(String key) {\n    return executeCommand(commandObjects.zrandmember(key));\n  }\n\n  @Override\n  public List<String> zrandmember(String key, long count) {\n    return executeCommand(commandObjects.zrandmember(key, count));\n  }\n\n  @Override\n  public List<Tuple> zrandmemberWithScores(String key, long count) {\n    return executeCommand(commandObjects.zrandmemberWithScores(key, count));\n  }\n\n  @Override\n  public long zcard(String key) {\n    return executeCommand(commandObjects.zcard(key));\n  }\n\n  @Override\n  public Double zscore(String key, String member) {\n    return executeCommand(commandObjects.zscore(key, member));\n  }\n\n  @Override\n  public List<Double> zmscore(String key, String... members) {\n    return executeCommand(commandObjects.zmscore(key, members));\n  }\n\n  @Override\n  public byte[] zrandmember(byte[] key) {\n    return executeCommand(commandObjects.zrandmember(key));\n  }\n\n  @Override\n  public List<byte[]> zrandmember(byte[] key, long count) {\n    return executeCommand(commandObjects.zrandmember(key, count));\n  }\n\n  @Override\n  public List<Tuple> zrandmemberWithScores(byte[] key, long count) {\n    return executeCommand(commandObjects.zrandmemberWithScores(key, count));\n  }\n\n  @Override\n  public long zcard(byte[] key) {\n    return executeCommand(commandObjects.zcard(key));\n  }\n\n  @Override\n  public Double zscore(byte[] key, byte[] member) {\n    return executeCommand(commandObjects.zscore(key, member));\n  }\n\n  @Override\n  public List<Double> zmscore(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.zmscore(key, members));\n  }\n\n  @Override\n  public Tuple zpopmax(String key) {\n    return executeCommand(commandObjects.zpopmax(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmax(String key, int count) {\n    return executeCommand(commandObjects.zpopmax(key, count));\n  }\n\n  @Override\n  public Tuple zpopmin(String key) {\n    return executeCommand(commandObjects.zpopmin(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmin(String key, int count) {\n    return executeCommand(commandObjects.zpopmin(key, count));\n  }\n\n  @Override\n  public long zcount(String key, double min, double max) {\n    return executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public long zcount(String key, String min, String max) {\n    return executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public Tuple zpopmax(byte[] key) {\n    return executeCommand(commandObjects.zpopmax(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmax(byte[] key, int count) {\n    return executeCommand(commandObjects.zpopmax(key, count));\n  }\n\n  @Override\n  public Tuple zpopmin(byte[] key) {\n    return executeCommand(commandObjects.zpopmin(key));\n  }\n\n  @Override\n  public List<Tuple> zpopmin(byte[] key, int count) {\n    return executeCommand(commandObjects.zpopmin(key, count));\n  }\n\n  @Override\n  public long zcount(byte[] key, double min, double max) {\n    return executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public long zcount(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zcount(key, min, max));\n  }\n\n  @Override\n  public List<String> zrange(String key, long start, long stop) {\n    return executeCommand(commandObjects.zrange(key, start, stop));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrange(String key, long start, long stop) {\n    return executeCommand(commandObjects.zrevrange(key, start, stop));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(String key, long start, long stop) {\n    return executeCommand(commandObjects.zrangeWithScores(key, start, stop));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeWithScores(String key, long start, long stop) {\n    return executeCommand(commandObjects.zrevrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public List<String> zrange(String key, ZRangeParams zRangeParams) {\n    return executeCommand(commandObjects.zrange(key, zRangeParams));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(String key, ZRangeParams zRangeParams) {\n    return executeCommand(commandObjects.zrangeWithScores(key, zRangeParams));\n  }\n\n  @Override\n  public long zrangestore(String dest, String src, ZRangeParams zRangeParams) {\n    return executeCommand(commandObjects.zrangestore(dest, src, zRangeParams));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(String key, double min, double max) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(String key, String min, String max) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(String key, double max, double min) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(String key, double min, double max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(String key, String max, String min) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByScore(String key, String min, String max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(String key, double max, double min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(String key, double min, double max) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(String key, double max, double min) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(String key, double min, double max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByScore(String key, String max, String min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(String key, String min, String max) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(String key, String max, String min) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(String key, String min, String max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public List<byte[]> zrange(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.zrange(key, start, stop));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrange(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.zrevrange(key, start, stop));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.zrangeWithScores(key, start, stop));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeWithScores(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.zrevrangeWithScores(key, start, stop));\n  }\n\n  @Override\n  public List<byte[]> zrange(byte[] key, ZRangeParams zRangeParams) {\n    return executeCommand(commandObjects.zrange(key, zRangeParams));\n  }\n\n  @Override\n  public List<Tuple> zrangeWithScores(byte[] key, ZRangeParams zRangeParams) {\n    return executeCommand(commandObjects.zrangeWithScores(key, zRangeParams));\n  }\n\n  @Override\n  public long zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams) {\n    return executeCommand(commandObjects.zrangestore(dest, src, zRangeParams));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrangeByScore(byte[] key, double min, double max) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrangeByScore(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrangeByScore(byte[] key, double max, double min) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrangeByScore(byte[] key, double min, double max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrangeByScore(byte[] key, byte[] max, byte[] min) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrangeByScore(byte[] key, double max, double min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(byte[] key, double min, double max) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(byte[] key, double max, double min) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<Tuple> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n  }\n\n  @Override\n  public long zremrangeByRank(String key, long start, long stop) {\n    return executeCommand(commandObjects.zremrangeByRank(key, start, stop));\n  }\n\n  @Override\n  public long zremrangeByScore(String key, double min, double max) {\n    return executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public long zremrangeByScore(String key, String min, String max) {\n    return executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public long zremrangeByRank(byte[] key, long start, long stop) {\n    return executeCommand(commandObjects.zremrangeByRank(key, start, stop));\n  }\n\n  @Override\n  public long zremrangeByScore(byte[] key, double min, double max) {\n    return executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public long zremrangeByScore(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zremrangeByScore(key, min, max));\n  }\n\n  @Override\n  public long zlexcount(String key, String min, String max) {\n    return executeCommand(commandObjects.zlexcount(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByLex(String key, String min, String max) {\n    return executeCommand(commandObjects.zrangeByLex(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrangeByLex(String key, String min, String max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByLex(String key, String max, String min) {\n    return executeCommand(commandObjects.zrevrangeByLex(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<String> zrevrangeByLex(String key, String max, String min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n  }\n\n  @Override\n  public long zremrangeByLex(String key, String min, String max) {\n    return executeCommand(commandObjects.zremrangeByLex(key, min, max));\n  }\n\n  @Override\n  public long zlexcount(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zlexcount(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrangeByLex(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zrangeByLex(key, min, max));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count) {\n    return executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrangeByLex(byte[] key, byte[] max, byte[] min) {\n    return executeCommand(commandObjects.zrevrangeByLex(key, max, min));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<byte[]> zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count) {\n    return executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n  }\n\n  @Override\n  public long zremrangeByLex(byte[] key, byte[] min, byte[] max) {\n    return executeCommand(commandObjects.zremrangeByLex(key, min, max));\n  }\n\n  @Override\n  public ScanResult<Tuple> zscan(String key, String cursor, ScanParams params) {\n    return executeCommand(commandObjects.zscan(key, cursor, params));\n  }\n\n  @Override\n  public ScanResult<Tuple> zscan(byte[] key, byte[] cursor, ScanParams params) {\n    return executeCommand(commandObjects.zscan(key, cursor, params));\n  }\n\n  @Override\n  public KeyValue<String, Tuple> bzpopmax(double timeout, String... keys) {\n    return executeCommand(commandObjects.bzpopmax(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<String, Tuple> bzpopmin(double timeout, String... keys) {\n    return executeCommand(commandObjects.bzpopmin(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], Tuple> bzpopmax(double timeout, byte[]... keys) {\n    return executeCommand(commandObjects.bzpopmax(timeout, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], Tuple> bzpopmin(double timeout, byte[]... keys) {\n    return executeCommand(commandObjects.bzpopmin(timeout, keys));\n  }\n\n  @Override\n  public List<String> zdiff(String... keys) {\n    return executeCommand(commandObjects.zdiff(keys));\n  }\n\n  @Override\n  public List<Tuple> zdiffWithScores(String... keys) {\n    return executeCommand(commandObjects.zdiffWithScores(keys));\n  }\n\n  @Override\n  @Deprecated\n  public long zdiffStore(String dstkey, String... keys) {\n    return executeCommand(commandObjects.zdiffStore(dstkey, keys));\n  }\n\n  @Override\n  public long zdiffstore(String dstkey, String... keys) {\n    return executeCommand(commandObjects.zdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public List<byte[]> zdiff(byte[]... keys) {\n    return executeCommand(commandObjects.zdiff(keys));\n  }\n\n  @Override\n  public List<Tuple> zdiffWithScores(byte[]... keys) {\n    return executeCommand(commandObjects.zdiffWithScores(keys));\n  }\n\n  @Override\n  @Deprecated\n  public long zdiffStore(byte[] dstkey, byte[]... keys) {\n    return executeCommand(commandObjects.zdiffStore(dstkey, keys));\n  }\n\n  @Override\n  public long zdiffstore(byte[] dstkey, byte[]... keys) {\n    return executeCommand(commandObjects.zdiffstore(dstkey, keys));\n  }\n\n  @Override\n  public long zinterstore(String dstkey, String... sets) {\n    return executeCommand(commandObjects.zinterstore(dstkey, sets));\n  }\n\n  @Override\n  public long zinterstore(String dstkey, ZParams params, String... sets) {\n    return executeCommand(commandObjects.zinterstore(dstkey, params, sets));\n  }\n\n  @Override\n  public List<String> zinter(ZParams params, String... keys) {\n    return executeCommand(commandObjects.zinter(params, keys));\n  }\n\n  @Override\n  public List<Tuple> zinterWithScores(ZParams params, String... keys) {\n    return executeCommand(commandObjects.zinterWithScores(params, keys));\n  }\n\n  @Override\n  public long zinterstore(byte[] dstkey, byte[]... sets) {\n    return executeCommand(commandObjects.zinterstore(dstkey, sets));\n  }\n\n  @Override\n  public long zinterstore(byte[] dstkey, ZParams params, byte[]... sets) {\n    return executeCommand(commandObjects.zinterstore(dstkey, params, sets));\n  }\n\n  @Override\n  public long zintercard(byte[]... keys) {\n    return executeCommand(commandObjects.zintercard(keys));\n  }\n\n  @Override\n  public long zintercard(long limit, byte[]... keys) {\n    return executeCommand(commandObjects.zintercard(limit, keys));\n  }\n\n  @Override\n  public long zintercard(String... keys) {\n    return executeCommand(commandObjects.zintercard(keys));\n  }\n\n  @Override\n  public long zintercard(long limit, String... keys) {\n    return executeCommand(commandObjects.zintercard(limit, keys));\n  }\n\n  @Override\n  public List<byte[]> zinter(ZParams params, byte[]... keys) {\n    return executeCommand(commandObjects.zinter(params, keys));\n  }\n\n  @Override\n  public List<Tuple> zinterWithScores(ZParams params, byte[]... keys) {\n    return executeCommand(commandObjects.zinterWithScores(params, keys));\n  }\n\n  @Override\n  public List<String> zunion(ZParams params, String... keys) {\n    return executeCommand(commandObjects.zunion(params, keys));\n  }\n\n  @Override\n  public List<Tuple> zunionWithScores(ZParams params, String... keys) {\n    return executeCommand(commandObjects.zunionWithScores(params, keys));\n  }\n\n  @Override\n  public long zunionstore(String dstkey, String... sets) {\n    return executeCommand(commandObjects.zunionstore(dstkey, sets));\n  }\n\n  @Override\n  public long zunionstore(String dstkey, ZParams params, String... sets) {\n    return executeCommand(commandObjects.zunionstore(dstkey, params, sets));\n  }\n\n  @Override\n  public List<byte[]> zunion(ZParams params, byte[]... keys) {\n    return executeCommand(commandObjects.zunion(params, keys));\n  }\n\n  @Override\n  public List<Tuple> zunionWithScores(ZParams params, byte[]... keys) {\n    return executeCommand(commandObjects.zunionWithScores(params, keys));\n  }\n\n  @Override\n  public long zunionstore(byte[] dstkey, byte[]... sets) {\n    return executeCommand(commandObjects.zunionstore(dstkey, sets));\n  }\n\n  @Override\n  public long zunionstore(byte[] dstkey, ZParams params, byte[]... sets) {\n    return executeCommand(commandObjects.zunionstore(dstkey, params, sets));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> zmpop(SortedSetOption option, String... keys) {\n    return executeCommand(commandObjects.zmpop(option, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> zmpop(SortedSetOption option, int count, String... keys) {\n    return executeCommand(commandObjects.zmpop(option, count, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> bzmpop(double timeout, SortedSetOption option, String... keys) {\n    return executeCommand(commandObjects.bzmpop(timeout, option, keys));\n  }\n\n  @Override\n  public KeyValue<String, List<Tuple>> bzmpop(double timeout, SortedSetOption option, int count, String... keys) {\n    return executeCommand(commandObjects.bzmpop(timeout, option, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> zmpop(SortedSetOption option, byte[]... keys) {\n    return executeCommand(commandObjects.zmpop(option, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> zmpop(SortedSetOption option, int count, byte[]... keys) {\n    return executeCommand(commandObjects.zmpop(option, count, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> bzmpop(double timeout, SortedSetOption option, byte[]... keys) {\n    return executeCommand(commandObjects.bzmpop(timeout, option, keys));\n  }\n\n  @Override\n  public KeyValue<byte[], List<Tuple>> bzmpop(double timeout, SortedSetOption option, int count, byte[]... keys) {\n    return executeCommand(commandObjects.bzmpop(timeout, option, count, keys));\n  }\n  // Sorted Set commands\n\n  // Geo commands\n  @Override\n  public long geoadd(String key, double longitude, double latitude, String member) {\n    return executeCommand(commandObjects.geoadd(key, longitude, latitude, member));\n  }\n\n  @Override\n  public long geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {\n    return executeCommand(commandObjects.geoadd(key, memberCoordinateMap));\n  }\n\n  @Override\n  public long geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap) {\n    return executeCommand(commandObjects.geoadd(key, params, memberCoordinateMap));\n  }\n\n  @Override\n  public Double geodist(String key, String member1, String member2) {\n    return executeCommand(commandObjects.geodist(key, member1, member2));\n  }\n\n  @Override\n  public Double geodist(String key, String member1, String member2, GeoUnit unit) {\n    return executeCommand(commandObjects.geodist(key, member1, member2, unit));\n  }\n\n  @Override\n  public List<String> geohash(String key, String... members) {\n    return executeCommand(commandObjects.geohash(key, members));\n  }\n\n  @Override\n  public List<GeoCoordinate> geopos(String key, String... members) {\n    return executeCommand(commandObjects.geopos(key, members));\n  }\n\n  @Override\n  public long geoadd(byte[] key, double longitude, double latitude, byte[] member) {\n    return executeCommand(commandObjects.geoadd(key, longitude, latitude, member));\n  }\n\n  @Override\n  public long geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    return executeCommand(commandObjects.geoadd(key, memberCoordinateMap));\n  }\n\n  @Override\n  public long geoadd(byte[] key, GeoAddParams params, Map<byte[], GeoCoordinate> memberCoordinateMap) {\n    return executeCommand(commandObjects.geoadd(key, params, memberCoordinateMap));\n  }\n\n  @Override\n  public Double geodist(byte[] key, byte[] member1, byte[] member2) {\n    return executeCommand(commandObjects.geodist(key, member1, member2));\n  }\n\n  @Override\n  public Double geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) {\n    return executeCommand(commandObjects.geodist(key, member1, member2, unit));\n  }\n\n  @Override\n  public List<byte[]> geohash(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.geohash(key, members));\n  }\n\n  @Override\n  public List<GeoCoordinate> geopos(byte[] key, byte[]... members) {\n    return executeCommand(commandObjects.geopos(key, members));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadius(String key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadius(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(String key, String member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearchStore(String, String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public long georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearchStore(String, String, GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public long georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, String member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, member, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, coord, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, String member, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, member, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, coord, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(String key, GeoSearchParam params) {\n    return executeCommand(commandObjects.geosearch(key, params));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, String member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, member, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, coord, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, String member, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, member, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, coord, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(String dest, String src, GeoSearchParam params) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, params));\n  }\n\n  @Override\n  public long geosearchStoreStoreDist(String dest, String src, GeoSearchParam params) {\n    return executeCommand(commandObjects.geosearchStoreStoreDist(dest, src, params));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearch(byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public List<GeoRadiusResponse> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {\n    return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearchStore(byte[], byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public long georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam));\n  }\n\n  /**\n   * @deprecated Use {@link UnifiedJedis#geosearchStore(byte[], byte[], GeoSearchParam)} instead.\n   * Deprecated since Redis 6.2.0.\n   */\n  @Deprecated\n  @Override\n  public long georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) {\n    return executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, byte[] member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, member, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, coord, radius, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, member, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearch(key, coord, width, height, unit));\n  }\n\n  @Override\n  public List<GeoRadiusResponse> geosearch(byte[] key, GeoSearchParam params) {\n    return executeCommand(commandObjects.geosearch(key, params));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, byte[] member, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, member, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double radius, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, coord, radius, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, byte[] member, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, member, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double width, double height, GeoUnit unit) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, coord, width, height, unit));\n  }\n\n  @Override\n  public long geosearchStore(byte[] dest, byte[] src, GeoSearchParam params) {\n    return executeCommand(commandObjects.geosearchStore(dest, src, params));\n  }\n\n  @Override\n  public long geosearchStoreStoreDist(byte[] dest, byte[] src, GeoSearchParam params) {\n    return executeCommand(commandObjects.geosearchStoreStoreDist(dest, src, params));\n  }\n  // Geo commands\n\n  // Hyper Log Log commands\n  @Override\n  public long pfadd(String key, String... elements) {\n    return executeCommand(commandObjects.pfadd(key, elements));\n  }\n\n  @Override\n  public String pfmerge(String destkey, String... sourcekeys) {\n    return executeCommand(commandObjects.pfmerge(destkey, sourcekeys));\n  }\n\n  @Override\n  public long pfcount(String key) {\n    return executeCommand(commandObjects.pfcount(key));\n  }\n\n  @Override\n  public long pfcount(String... keys) {\n    return executeCommand(commandObjects.pfcount(keys));\n  }\n\n  @Override\n  public long pfadd(byte[] key, byte[]... elements) {\n    return executeCommand(commandObjects.pfadd(key, elements));\n  }\n\n  @Override\n  public String pfmerge(byte[] destkey, byte[]... sourcekeys) {\n    return executeCommand(commandObjects.pfmerge(destkey, sourcekeys));\n  }\n\n  @Override\n  public long pfcount(byte[] key) {\n    return executeCommand(commandObjects.pfcount(key));\n  }\n\n  @Override\n  public long pfcount(byte[]... keys) {\n    return executeCommand(commandObjects.pfcount(keys));\n  }\n  // Hyper Log Log commands\n\n  // Stream commands\n  @Override\n  public StreamEntryID xadd(String key, StreamEntryID id, Map<String, String> hash) {\n    return executeCommand(commandObjects.xadd(key, id, hash));\n  }\n\n  @Override\n  public StreamEntryID xadd(String key, XAddParams params, Map<String, String> hash) {\n    return executeCommand(commandObjects.xadd(key, params, hash));\n  }\n\n  @Override\n  public long xlen(String key) {\n    return executeCommand(commandObjects.xlen(key));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end) {\n    return executeCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end, int count) {\n    return executeCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(String key, StreamEntryID end, StreamEntryID start) {\n    return executeCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(String key, StreamEntryID end, StreamEntryID start, int count) {\n    return executeCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(String key, String start, String end) {\n    return executeCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public List<StreamEntry> xrange(String key, String start, String end, int count) {\n    return executeCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(String key, String end, String start) {\n    return executeCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public List<StreamEntry> xrevrange(String key, String end, String start, int count) {\n    return executeCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public long xack(String key, String group, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xack(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(String key, String group, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xackdel(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xackdel(key, group, trimMode, ids));\n  }\n\n  @Override\n  public String xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream) {\n    return executeCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream));\n  }\n\n  @Override\n  public String xgroupSetID(String key, String groupName, StreamEntryID id) {\n    return executeCommand(commandObjects.xgroupSetID(key, groupName, id));\n  }\n\n  @Override\n  public long xgroupDestroy(String key, String groupName) {\n    return executeCommand(commandObjects.xgroupDestroy(key, groupName));\n  }\n\n  @Override\n  public boolean xgroupCreateConsumer(String key, String groupName, String consumerName) {\n    return executeCommand(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xgroupDelConsumer(String key, String groupName, String consumerName) {\n    return executeCommand(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public StreamPendingSummary xpending(String key, String groupName) {\n    return executeCommand(commandObjects.xpending(key, groupName));\n  }\n\n  @Override\n  public List<StreamPendingEntry> xpending(String key, String groupName, XPendingParams params) {\n    return executeCommand(commandObjects.xpending(key, groupName, params));\n  }\n\n  @Override\n  public long xdel(String key, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xdel(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(String key, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xdelex(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xdelex(key, trimMode, ids));\n  }\n\n  @Override\n  public long xtrim(String key, long maxLen, boolean approximate) {\n    return executeCommand(commandObjects.xtrim(key, maxLen, approximate));\n  }\n\n  @Override\n  public long xtrim(String key, XTrimParams params) {\n    return executeCommand(commandObjects.xtrim(key, params));\n  }\n\n  @Override\n  public List<StreamEntry> xclaim(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public List<StreamEntryID> xclaimJustId(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) {\n    return executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public Map.Entry<StreamEntryID, List<StreamEntry>> xautoclaim(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) {\n    return executeCommand(commandObjects.xautoclaim(key, group, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Map.Entry<StreamEntryID, List<StreamEntryID>> xautoclaimJustId(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) {\n    return executeCommand(commandObjects.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public StreamInfo xinfoStream(String key) {\n    return executeCommand(commandObjects.xinfoStream(key));\n  }\n\n  @Override\n  public StreamFullInfo xinfoStreamFull(String key) {\n    return executeCommand(commandObjects.xinfoStreamFull(key));\n  }\n\n  @Override\n  public StreamFullInfo xinfoStreamFull(String key, int count) {\n    return executeCommand(commandObjects.xinfoStreamFull(key, count));\n  }\n\n  @Override\n  public List<StreamGroupInfo> xinfoGroups(String key) {\n    return executeCommand(commandObjects.xinfoGroups(key));\n  }\n\n  @Override\n  public List<StreamConsumersInfo> xinfoConsumers(String key, String group) {\n    return executeCommand(commandObjects.xinfoConsumers(key, group));\n  }\n\n  @Override\n  public List<StreamConsumerInfo> xinfoConsumers2(String key, String group) {\n    return executeCommand(commandObjects.xinfoConsumers2(key, group));\n  }\n\n  @Override\n  public List<Map.Entry<String, List<StreamEntry>>> xread(XReadParams xReadParams, Map<String, StreamEntryID> streams) {\n    return executeCommand(commandObjects.xread(xReadParams, streams));\n  }\n\n  @Override\n  public Map<String, List<StreamEntry>> xreadAsMap(XReadParams xReadParams, Map<String, StreamEntryID> streams) {\n    return executeCommand(commandObjects.xreadAsMap(xReadParams, streams));\n  }\n\n  @Override\n  public List<Map.Entry<String, List<StreamEntry>>> xreadGroup(String groupName, String consumer, XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams) {\n    return executeCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Map<String, List<StreamEntry>> xreadGroupAsMap(String groupName, String consumer, XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams) {\n    return executeCommand(commandObjects.xreadGroupAsMap(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public byte[] xadd(byte[] key, XAddParams params, Map<byte[], byte[]> hash) {\n    return executeCommand(commandObjects.xadd(key, params, hash));\n  }\n\n  @Override\n  public long xlen(byte[] key) {\n    return executeCommand(commandObjects.xlen(key));\n  }\n\n  @Override\n  public List<Object> xrange(byte[] key, byte[] start, byte[] end) {\n    return executeCommand(commandObjects.xrange(key, start, end));\n  }\n\n  @Override\n  public List<Object> xrange(byte[] key, byte[] start, byte[] end, int count) {\n    return executeCommand(commandObjects.xrange(key, start, end, count));\n  }\n\n  @Override\n  public List<Object> xrevrange(byte[] key, byte[] end, byte[] start) {\n    return executeCommand(commandObjects.xrevrange(key, end, start));\n  }\n\n  @Override\n  public List<Object> xrevrange(byte[] key, byte[] end, byte[] start, int count) {\n    return executeCommand(commandObjects.xrevrange(key, end, start, count));\n  }\n\n  @Override\n  public long xack(byte[] key, byte[] group, byte[]... ids) {\n    return executeCommand(commandObjects.xack(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(byte[] key, byte[] group, byte[]... ids) {\n    return executeCommand(commandObjects.xackdel(key, group, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) {\n    return executeCommand(commandObjects.xackdel(key, group, trimMode, ids));\n  }\n\n  @Override\n  public String xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream) {\n    return executeCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream));\n  }\n\n  @Override\n  public String xgroupSetID(byte[] key, byte[] groupName, byte[] id) {\n    return executeCommand(commandObjects.xgroupSetID(key, groupName, id));\n  }\n\n  @Override\n  public long xgroupDestroy(byte[] key, byte[] groupName) {\n    return executeCommand(commandObjects.xgroupDestroy(key, groupName));\n  }\n\n  @Override\n  public boolean xgroupCreateConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    return executeCommand(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xgroupDelConsumer(byte[] key, byte[] groupName, byte[] consumerName) {\n    return executeCommand(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n  }\n\n  @Override\n  public long xdel(byte[] key, byte[]... ids) {\n    return executeCommand(commandObjects.xdel(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(byte[] key, byte[]... ids) {\n    return executeCommand(commandObjects.xdelex(key, ids));\n  }\n\n  @Override\n  public List<StreamEntryDeletionResult> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) {\n    return executeCommand(commandObjects.xdelex(key, trimMode, ids));\n  }\n\n  @Override\n  public long xtrim(byte[] key, long maxLen, boolean approximateLength) {\n    return executeCommand(commandObjects.xtrim(key, maxLen, approximateLength));\n  }\n\n  @Override\n  public long xtrim(byte[] key, XTrimParams params) {\n    return executeCommand(commandObjects.xtrim(key, params));\n  }\n\n  @Override\n  public Object xpending(byte[] key, byte[] groupName) {\n    return executeCommand(commandObjects.xpending(key, groupName));\n  }\n\n  @Override\n  public List<Object> xpending(byte[] key, byte[] groupName, XPendingParams params) {\n    return executeCommand(commandObjects.xpending(key, groupName, params));\n  }\n\n  @Override\n  public List<byte[]> xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) {\n    return executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public List<byte[]> xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) {\n    return executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids));\n  }\n\n  @Override\n  public List<Object> xautoclaim(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) {\n    return executeCommand(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public List<Object> xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) {\n    return executeCommand(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params));\n  }\n\n  @Override\n  public Object xinfoStream(byte[] key) {\n    return executeCommand(commandObjects.xinfoStream(key));\n  }\n\n  @Override\n  public Object xinfoStreamFull(byte[] key) {\n    return executeCommand(commandObjects.xinfoStreamFull(key));\n  }\n\n  @Override\n  public Object xinfoStreamFull(byte[] key, int count) {\n    return executeCommand(commandObjects.xinfoStreamFull(key, count));\n  }\n\n  @Override\n  public List<Object> xinfoGroups(byte[] key) {\n    return executeCommand(commandObjects.xinfoGroups(key));\n  }\n\n  @Override\n  public List<Object> xinfoConsumers(byte[] key, byte[] group) {\n    return executeCommand(commandObjects.xinfoConsumers(key, group));\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use\n   *     {@link #xreadBinary(XReadParams, Map)} or\n   *     {@link #xreadBinaryAsMap(XReadParams, Map)} for type safety and better stream entry\n   *     parsing.\n   */\n  @Deprecated\n  @Override\n  public List<Object> xread(XReadParams xReadParams, Map.Entry<byte[], byte[]>... streams) {\n    return executeCommand(commandObjects.xread(xReadParams, streams));\n  }\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use\n   *     {@link #xreadGroupBinary(byte[], byte[], XReadGroupParams, Map)} or\n   *     {@link #xreadGroupBinaryAsMap(byte[], byte[], XReadGroupParams, Map)} instead.\n   */\n  @Deprecated\n  @Override\n  public List<Object> xreadGroup(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map.Entry<byte[], byte[]>... streams) {\n    return executeCommand(\n        commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public List<Map.Entry<byte[], List<StreamEntryBinary>>> xreadBinary(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams) {\n    return executeCommand(commandObjects.xreadBinary(xReadParams, streams));\n  }\n\n  @Override\n  public Map<byte[], List<StreamEntryBinary>> xreadBinaryAsMap(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams) {\n    return executeCommand(commandObjects.xreadBinaryAsMap(xReadParams, streams));\n  }\n\n  @Override\n  public List<Map.Entry<byte[], List<StreamEntryBinary>>> xreadGroupBinary(byte[] groupName,\n      byte[] consumer, XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams) {\n    return executeCommand(\n        commandObjects.xreadGroupBinary(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public Map<byte[], List<StreamEntryBinary>> xreadGroupBinaryAsMap(byte[] groupName,\n      byte[] consumer, XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams) {\n    return executeCommand(\n        commandObjects.xreadGroupBinaryAsMap(groupName, consumer, xReadGroupParams, streams));\n  }\n\n  @Override\n  public String xcfgset(String key, XCfgSetParams params) {\n    return executeCommand(commandObjects.xcfgset(key, params));\n  }\n\n  @Override\n  public byte[] xcfgset(byte[] key, XCfgSetParams params) {\n    return executeCommand(commandObjects.xcfgset(key, params));\n  }\n  // Stream commands\n\n  // Scripting commands\n  @Override\n  public Object eval(String script) {\n    return executeCommand(commandObjects.eval(script));\n  }\n\n  @Override\n  public Object eval(String script, int keyCount, String... params) {\n    return executeCommand(commandObjects.eval(script, keyCount, params));\n  }\n\n  @Override\n  public Object eval(String script, List<String> keys, List<String> args) {\n    return executeCommand(commandObjects.eval(script, keys, args));\n  }\n\n  @Override\n  public Object evalReadonly(String script, List<String> keys, List<String> args) {\n    return executeCommand(commandObjects.evalReadonly(script, keys, args));\n  }\n\n  @Override\n  public Object evalsha(String sha1) {\n    return executeCommand(commandObjects.evalsha(sha1));\n  }\n\n  @Override\n  public Object evalsha(String sha1, int keyCount, String... params) {\n    return executeCommand(commandObjects.evalsha(sha1, keyCount, params));\n  }\n\n  @Override\n  public Object evalsha(String sha1, List<String> keys, List<String> args) {\n    return executeCommand(commandObjects.evalsha(sha1, keys, args));\n  }\n\n  @Override\n  public Object evalshaReadonly(String sha1, List<String> keys, List<String> args) {\n    return executeCommand(commandObjects.evalshaReadonly(sha1, keys, args));\n  }\n\n  @Override\n  public Object eval(byte[] script) {\n    return executeCommand(commandObjects.eval(script));\n  }\n\n  @Override\n  public Object eval(byte[] script, int keyCount, byte[]... params) {\n    return executeCommand(commandObjects.eval(script, keyCount, params));\n  }\n\n  @Override\n  public Object eval(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    return executeCommand(commandObjects.eval(script, keys, args));\n  }\n\n  @Override\n  public Object evalReadonly(byte[] script, List<byte[]> keys, List<byte[]> args) {\n    return executeCommand(commandObjects.evalReadonly(script, keys, args));\n  }\n\n  @Override\n  public Object evalsha(byte[] sha1) {\n    return executeCommand(commandObjects.evalsha(sha1));\n  }\n\n  @Override\n  public Object evalsha(byte[] sha1, int keyCount, byte[]... params) {\n    return executeCommand(commandObjects.evalsha(sha1, keyCount, params));\n  }\n\n  @Override\n  public Object evalsha(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    return executeCommand(commandObjects.evalsha(sha1, keys, args));\n  }\n\n  @Override\n  public Object evalshaReadonly(byte[] sha1, List<byte[]> keys, List<byte[]> args) {\n    return executeCommand(commandObjects.evalshaReadonly(sha1, keys, args));\n  }\n\n  @Override\n  public Object fcall(String name, List<String> keys, List<String> args) {\n    return executeCommand(commandObjects.fcall(name, keys, args));\n  }\n\n  @Override\n  public Object fcallReadonly(String name, List<String> keys, List<String> args) {\n    return executeCommand(commandObjects.fcallReadonly(name, keys, args));\n  }\n\n  @Override\n  public String functionDelete(String libraryName) {\n    return executeCommand(commandObjects.functionDelete(libraryName));\n  }\n\n  @Override\n  public String functionFlush() {\n    return executeCommand(commandObjects.functionFlush());\n  }\n\n  @Override\n  public String functionFlush(FlushMode mode) {\n    return executeCommand(commandObjects.functionFlush(mode));\n  }\n\n  @Override\n  public String functionKill() {\n    return executeCommand(commandObjects.functionKill());\n  }\n\n  @Override\n  public List<LibraryInfo> functionList() {\n    return executeCommand(commandObjects.functionList());\n  }\n\n  @Override\n  public List<LibraryInfo> functionList(String libraryNamePattern) {\n    return executeCommand(commandObjects.functionList(libraryNamePattern));\n  }\n\n  @Override\n  public List<LibraryInfo> functionListWithCode() {\n    return executeCommand(commandObjects.functionListWithCode());\n  }\n\n  @Override\n  public List<LibraryInfo> functionListWithCode(String libraryNamePattern) {\n    return executeCommand(commandObjects.functionListWithCode(libraryNamePattern));\n  }\n\n  @Override\n  public String functionLoad(String functionCode) {\n    return executeCommand(commandObjects.functionLoad(functionCode));\n  }\n\n  @Override\n  public String functionLoadReplace(String functionCode) {\n    return executeCommand(commandObjects.functionLoadReplace(functionCode));\n  }\n\n  @Override\n  public FunctionStats functionStats() {\n    return executeCommand(commandObjects.functionStats());\n  }\n\n  @Override\n  public Object fcall(byte[] name, List<byte[]> keys, List<byte[]> args) {\n    return executeCommand(commandObjects.fcall(name, keys, args));\n  }\n\n  @Override\n  public Object fcallReadonly(byte[] name, List<byte[]> keys, List<byte[]> args) {\n    return executeCommand(commandObjects.fcallReadonly(name, keys, args));\n  }\n\n  @Override\n  public String functionDelete(byte[] libraryName) {\n    return executeCommand(commandObjects.functionDelete(libraryName));\n  }\n\n  @Override\n  public byte[] functionDump() {\n    return executeCommand(commandObjects.functionDump());\n  }\n\n  @Override\n  public List<Object> functionListBinary() {\n    return executeCommand(commandObjects.functionListBinary());\n  }\n\n  @Override\n  public List<Object> functionList(final byte[] libraryNamePattern) {\n    return executeCommand(commandObjects.functionList(libraryNamePattern));\n  }\n\n  @Override\n  public List<Object> functionListWithCodeBinary() {\n    return executeCommand(commandObjects.functionListWithCodeBinary());\n  }\n\n  @Override\n  public List<Object> functionListWithCode(final byte[] libraryNamePattern) {\n    return executeCommand(commandObjects.functionListWithCode(libraryNamePattern));\n  }\n\n  @Override\n  public String functionLoad(byte[] functionCode) {\n    return executeCommand(commandObjects.functionLoad(functionCode));\n  }\n\n  @Override\n  public String functionLoadReplace(byte[] functionCode) {\n    return executeCommand(commandObjects.functionLoadReplace(functionCode));\n  }\n\n  @Override\n  public String functionRestore(byte[] serializedValue) {\n    return executeCommand(commandObjects.functionRestore(serializedValue));\n  }\n\n  @Override\n  public String functionRestore(byte[] serializedValue, FunctionRestorePolicy policy) {\n    return executeCommand(commandObjects.functionRestore(serializedValue, policy));\n  }\n\n  @Override\n  public Object functionStatsBinary() {\n    return executeCommand(commandObjects.functionStatsBinary());\n  }\n  // Scripting commands\n\n  // Other key commands\n  @Override\n  public Long objectRefcount(String key) {\n    return executeCommand(commandObjects.objectRefcount(key));\n  }\n\n  @Override\n  public String objectEncoding(String key) {\n    return executeCommand(commandObjects.objectEncoding(key));\n  }\n\n  @Override\n  public Long objectIdletime(String key) {\n    return executeCommand(commandObjects.objectIdletime(key));\n  }\n\n  @Override\n  public Long objectFreq(String key) {\n    return executeCommand(commandObjects.objectFreq(key));\n  }\n\n  @Override\n  public Long objectRefcount(byte[] key) {\n    return executeCommand(commandObjects.objectRefcount(key));\n  }\n\n  @Override\n  public byte[] objectEncoding(byte[] key) {\n    return executeCommand(commandObjects.objectEncoding(key));\n  }\n\n  @Override\n  public Long objectIdletime(byte[] key) {\n    return executeCommand(commandObjects.objectIdletime(key));\n  }\n\n  @Override\n  public Long objectFreq(byte[] key) {\n    return executeCommand(commandObjects.objectFreq(key));\n  }\n\n  @Override\n  public String migrate(String host, int port, String key, int timeout) {\n    return executeCommand(commandObjects.migrate(host, port, key, timeout));\n  }\n\n  @Override\n  public String migrate(String host, int port, int timeout, MigrateParams params, String... keys) {\n    return executeCommand(commandObjects.migrate(host, port, timeout, params, keys));\n  }\n\n  @Override\n  public String migrate(String host, int port, byte[] key, int timeout) {\n    return executeCommand(commandObjects.migrate(host, port, key, timeout));\n  }\n\n  @Override\n  public String migrate(String host, int port, int timeout, MigrateParams params, byte[]... keys) {\n    return executeCommand(commandObjects.migrate(host, port, timeout, params, keys));\n  }\n  // Other key commands\n\n  // Sample key commands\n  @Override\n  public long waitReplicas(String sampleKey, int replicas, long timeout) {\n    return executeCommand(commandObjects.waitReplicas(sampleKey, replicas, timeout));\n  }\n\n  @Override\n  public long waitReplicas(byte[] sampleKey, int replicas, long timeout) {\n    return executeCommand(commandObjects.waitReplicas(sampleKey, replicas, timeout));\n  }\n\n  @Override\n  public KeyValue<Long, Long> waitAOF(String sampleKey, long numLocal, long numReplicas, long timeout) {\n    return executeCommand(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout));\n  }\n\n  @Override\n  public KeyValue<Long, Long> waitAOF(byte[] sampleKey, long numLocal, long numReplicas, long timeout) {\n    return executeCommand(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout));\n  }\n\n  @Override\n  public Object eval(String script, String sampleKey) {\n    return executeCommand(commandObjects.eval(script, sampleKey));\n  }\n\n  @Override\n  public Object evalsha(String sha1, String sampleKey) {\n    return executeCommand(commandObjects.evalsha(sha1, sampleKey));\n  }\n\n  @Override\n  public Object eval(byte[] script, byte[] sampleKey) {\n    return executeCommand(commandObjects.eval(script, sampleKey));\n  }\n\n  @Override\n  public Object evalsha(byte[] sha1, byte[] sampleKey) {\n    return executeCommand(commandObjects.evalsha(sha1, sampleKey));\n  }\n\n  public List<Boolean> scriptExists(List<String> sha1s) {\n    return executeCommand(commandObjects.scriptExists(sha1s));\n  }\n\n  @Override\n  public Boolean scriptExists(String sha1, String sampleKey) {\n    return scriptExists(sampleKey, new String[] { sha1 }).get(0);\n  }\n\n  @Override\n  public List<Boolean> scriptExists(String sampleKey, String... sha1s) {\n    return executeCommand(commandObjects.scriptExists(sampleKey, sha1s));\n  }\n\n  @Override\n  public Boolean scriptExists(byte[] sha1, byte[] sampleKey) {\n    return scriptExists(sampleKey, new byte[][] { sha1 }).get(0);\n  }\n\n  @Override\n  public List<Boolean> scriptExists(byte[] sampleKey, byte[]... sha1s) {\n    return executeCommand(commandObjects.scriptExists(sampleKey, sha1s));\n  }\n\n  public String scriptLoad(String script) {\n    return executeCommand(commandObjects.scriptLoad(script));\n  }\n\n  @Override\n  public String scriptLoad(String script, String sampleKey) {\n    return executeCommand(commandObjects.scriptLoad(script, sampleKey));\n  }\n\n  public String scriptFlush() {\n    return executeCommand(commandObjects.scriptFlush());\n  }\n\n  @Override\n  public String scriptFlush(String sampleKey) {\n    return executeCommand(commandObjects.scriptFlush(sampleKey));\n  }\n\n  @Override\n  public String scriptFlush(String sampleKey, FlushMode flushMode) {\n    return executeCommand(commandObjects.scriptFlush(sampleKey, flushMode));\n  }\n\n  public String scriptKill() {\n    return executeCommand(commandObjects.scriptKill());\n  }\n\n  @Override\n  public String scriptKill(String sampleKey) {\n    return executeCommand(commandObjects.scriptKill(sampleKey));\n  }\n\n  @Override\n  public byte[] scriptLoad(byte[] script, byte[] sampleKey) {\n    return executeCommand(commandObjects.scriptLoad(script, sampleKey));\n  }\n\n  @Override\n  public String scriptFlush(byte[] sampleKey) {\n    return executeCommand(commandObjects.scriptFlush(sampleKey));\n  }\n\n  @Override\n  public String scriptFlush(byte[] sampleKey, FlushMode flushMode) {\n    return executeCommand(commandObjects.scriptFlush(sampleKey, flushMode));\n  }\n\n  @Override\n  public String scriptKill(byte[] sampleKey) {\n    return executeCommand(commandObjects.scriptKill(sampleKey));\n  }\n\n  public String slowlogReset() {\n    return executeCommand(commandObjects.slowlogReset());\n  }\n  // Sample key commands\n\n  // Random node commands\n  public long publish(String channel, String message) {\n    return executeCommand(commandObjects.publish(channel, message));\n  }\n\n  public long publish(byte[] channel, byte[] message) {\n    return executeCommand(commandObjects.publish(channel, message));\n  }\n\n  public void subscribe(final JedisPubSub jedisPubSub, final String... channels) {\n    try (Connection connection = this.provider.getConnection()) {\n      jedisPubSub.proceed(connection, channels);\n    }\n  }\n\n  public void psubscribe(final JedisPubSub jedisPubSub, final String... patterns) {\n    try (Connection connection = this.provider.getConnection()) {\n      jedisPubSub.proceedWithPatterns(connection, patterns);\n    }\n  }\n\n  public void subscribe(BinaryJedisPubSub jedisPubSub, final byte[]... channels) {\n    try (Connection connection = this.provider.getConnection()) {\n      jedisPubSub.proceed(connection, channels);\n    }\n  }\n\n  public void psubscribe(BinaryJedisPubSub jedisPubSub, final byte[]... patterns) {\n    try (Connection connection = this.provider.getConnection()) {\n      jedisPubSub.proceedWithPatterns(connection, patterns);\n    }\n  }\n  // Random node commands\n\n  // RediSearch commands\n  public long hsetObject(String key, String field, Object value) {\n    return executeCommand(commandObjects.hsetObject(key, field, value));\n  }\n\n  public long hsetObject(String key, Map<String, Object> hash) {\n    return executeCommand(commandObjects.hsetObject(key, hash));\n  }\n\n  @Override\n  public String ftCreate(String indexName, IndexOptions indexOptions, Schema schema) {\n    return executeCommand(commandObjects.ftCreate(indexName, indexOptions, schema));\n  }\n\n  @Override\n  public String ftCreate(String indexName, FTCreateParams createParams, Iterable<SchemaField> schemaFields) {\n    return executeCommand(commandObjects.ftCreate(indexName, createParams, schemaFields));\n  }\n\n  @Override\n  public String ftAlter(String indexName, Schema schema) {\n    return executeCommand(commandObjects.ftAlter(indexName, schema));\n  }\n\n  @Override\n  public String ftAlter(String indexName, Iterable<SchemaField> schemaFields) {\n    return executeCommand(commandObjects.ftAlter(indexName, schemaFields));\n  }\n\n  @Override\n  public String ftAliasAdd(String aliasName, String indexName) {\n    return executeCommand(commandObjects.ftAliasAdd(aliasName, indexName));\n  }\n\n  @Override\n  public String ftAliasUpdate(String aliasName, String indexName) {\n    return executeCommand(commandObjects.ftAliasUpdate(aliasName, indexName));\n  }\n\n  @Override\n  public String ftAliasDel(String aliasName) {\n    return executeCommand(commandObjects.ftAliasDel(aliasName));\n  }\n\n  @Override\n  public String ftDropIndex(String indexName) {\n    return executeCommand(commandObjects.ftDropIndex(indexName));\n  }\n\n  @Override\n  public String ftDropIndexDD(String indexName) {\n    return executeCommand(commandObjects.ftDropIndexDD(indexName));\n  }\n\n  @Override\n  public SearchResult ftSearch(String indexName, String query) {\n    return executeCommand(commandObjects.ftSearch(indexName, query));\n  }\n\n  @Override\n  public SearchResult ftSearch(String indexName, String query, FTSearchParams params) {\n    return executeCommand(commandObjects.ftSearch(indexName, query, params));\n  }\n\n  /**\n   * {@link FTSearchParams#limit(int, int)} will be ignored.\n   *\n   * @param batchSize batch size\n   * @param indexName index name\n   * @param query query\n   * @param params limit will be ignored\n   * @return search iteration\n   */\n  public FtSearchIteration ftSearchIteration(int batchSize, String indexName, String query, FTSearchParams params) {\n    return new FtSearchIteration(provider, commandObjects.getProtocol(), batchSize, indexName, query, params);\n  }\n\n  @Override\n  public SearchResult ftSearch(String indexName, Query query) {\n    return executeCommand(commandObjects.ftSearch(indexName, query));\n  }\n\n  /**\n   * {@link Query#limit(java.lang.Integer, java.lang.Integer)} will be ignored.\n   * @param batchSize batch size\n   * @param indexName index name\n   * @param query limit will be ignored\n   * @return search iteration\n   */\n  public FtSearchIteration ftSearchIteration(int batchSize, String indexName, Query query) {\n    return new FtSearchIteration(provider, commandObjects.getProtocol(), batchSize, indexName, query);\n  }\n\n  @Override\n  @Deprecated\n  public SearchResult ftSearch(byte[] indexName, Query query) {\n    return executeCommand(commandObjects.ftSearch(indexName, query));\n  }\n\n  @Override\n  public String ftExplain(String indexName, Query query) {\n    return executeCommand(commandObjects.ftExplain(indexName, query));\n  }\n\n  @Override\n  public List<String> ftExplainCLI(String indexName, Query query) {\n    return executeCommand(commandObjects.ftExplainCLI(indexName, query));\n  }\n\n  @Override\n  public AggregationResult ftAggregate(String indexName, AggregationBuilder aggr) {\n    return executeCommand(commandObjects.ftAggregate(indexName, aggr));\n  }\n\n  @Override\n  public AggregationResult ftCursorRead(String indexName, long cursorId, int count) {\n    return executeCommand(commandObjects.ftCursorRead(indexName, cursorId, count));\n  }\n\n  @Override\n  public String ftCursorDel(String indexName, long cursorId) {\n    return executeCommand(commandObjects.ftCursorDel(indexName, cursorId));\n  }\n\n  /**\n   * {@link AggregationBuilder#cursor(int, long) CURSOR} must be set.\n   * @param indexName index name\n   * @param aggr cursor must be set\n   * @return aggregate iteration\n   */\n  public FtAggregateIteration ftAggregateIteration(String indexName, AggregationBuilder aggr) {\n    return new FtAggregateIteration(provider, indexName, aggr);\n  }\n\n  @Override\n  public Map.Entry<AggregationResult, ProfilingInfo> ftProfileAggregate(String indexName,\n      FTProfileParams profileParams, AggregationBuilder aggr) {\n    return executeCommand(commandObjects.ftProfileAggregate(indexName, profileParams, aggr));\n  }\n\n  @Override\n  public Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,\n      FTProfileParams profileParams, Query query) {\n    return executeCommand(commandObjects.ftProfileSearch(indexName, profileParams, query));\n  }\n\n  @Override\n  public Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,\n      FTProfileParams profileParams, String query, FTSearchParams searchParams) {\n    return executeCommand(commandObjects.ftProfileSearch(indexName, profileParams, query, searchParams));\n  }\n\n  @Override\n  public String ftSynUpdate(String indexName, String synonymGroupId, String... terms) {\n    return executeCommand(commandObjects.ftSynUpdate(indexName, synonymGroupId, terms));\n  }\n\n  @Override\n  public Map<String, List<String>> ftSynDump(String indexName) {\n    return executeCommand(commandObjects.ftSynDump(indexName));\n  }\n\n  @Override\n  public long ftDictAdd(String dictionary, String... terms) {\n    return executeCommand(commandObjects.ftDictAdd(dictionary, terms));\n  }\n\n  @Override\n  public long ftDictDel(String dictionary, String... terms) {\n    return executeCommand(commandObjects.ftDictDel(dictionary, terms));\n  }\n\n  @Override\n  public Set<String> ftDictDump(String dictionary) {\n    return executeCommand(commandObjects.ftDictDump(dictionary));\n  }\n\n  @Override\n  public long ftDictAddBySampleKey(String indexName, String dictionary, String... terms) {\n    return executeCommand(commandObjects.ftDictAddBySampleKey(indexName, dictionary, terms));\n  }\n\n  @Override\n  public long ftDictDelBySampleKey(String indexName, String dictionary, String... terms) {\n    return executeCommand(commandObjects.ftDictDelBySampleKey(indexName, dictionary, terms));\n  }\n\n  @Override\n  public Set<String> ftDictDumpBySampleKey(String indexName, String dictionary) {\n    return executeCommand(commandObjects.ftDictDumpBySampleKey(indexName, dictionary));\n  }\n\n  @Override\n  public Map<String, Map<String, Double>> ftSpellCheck(String index, String query) {\n    return executeCommand(commandObjects.ftSpellCheck(index, query));\n  }\n\n  @Override\n  public Map<String, Map<String, Double>> ftSpellCheck(String index, String query,\n      FTSpellCheckParams spellCheckParams) {\n    return executeCommand(commandObjects.ftSpellCheck(index, query, spellCheckParams));\n  }\n\n  @Override\n  public Map<String, Object> ftInfo(String indexName) {\n    return executeCommand(commandObjects.ftInfo(indexName));\n  }\n\n  @Override\n  public Set<String> ftTagVals(String indexName, String fieldName) {\n    return executeCommand(commandObjects.ftTagVals(indexName, fieldName));\n  }\n\n  @Override\n  @Experimental\n  public HybridResult ftHybrid(String indexName, FTHybridParams hybridParams) {\n    return executeCommand(commandObjects.ftHybrid(indexName, hybridParams));\n  }\n\n  @Override\n  @Deprecated\n  public Map<String, Object> ftConfigGet(String option) {\n    return executeCommand(commandObjects.ftConfigGet(option));\n  }\n\n  @Override\n  @Deprecated\n  public Map<String, Object> ftConfigGet(String indexName, String option) {\n    return executeCommand(commandObjects.ftConfigGet(indexName, option));\n  }\n\n  @Override\n  @Deprecated\n  public String ftConfigSet(String option, String value) {\n    return executeCommand(commandObjects.ftConfigSet(option, value));\n  }\n\n  @Override\n  @Deprecated\n  public String ftConfigSet(String indexName, String option, String value) {\n    return executeCommand(commandObjects.ftConfigSet(indexName, option, value));\n  }\n\n  @Override\n  public long ftSugAdd(String key, String string, double score) {\n    return executeCommand(commandObjects.ftSugAdd(key, string, score));\n  }\n\n  @Override\n  public long ftSugAddIncr(String key, String string, double score) {\n    return executeCommand(commandObjects.ftSugAddIncr(key, string, score));\n  }\n\n  @Override\n  public List<String> ftSugGet(String key, String prefix) {\n    return executeCommand(commandObjects.ftSugGet(key, prefix));\n  }\n\n  @Override\n  public List<String> ftSugGet(String key, String prefix, boolean fuzzy, int max) {\n    return executeCommand(commandObjects.ftSugGet(key, prefix, fuzzy, max));\n  }\n\n  @Override\n  public List<Tuple> ftSugGetWithScores(String key, String prefix) {\n    return executeCommand(commandObjects.ftSugGetWithScores(key, prefix));\n  }\n\n  @Override\n  public List<Tuple> ftSugGetWithScores(String key, String prefix, boolean fuzzy, int max) {\n    return executeCommand(commandObjects.ftSugGetWithScores(key, prefix, fuzzy, max));\n  }\n\n  @Override\n  public boolean ftSugDel(String key, String string) {\n    return executeCommand(commandObjects.ftSugDel(key, string));\n  }\n\n  @Override\n  public long ftSugLen(String key) {\n    return executeCommand(commandObjects.ftSugLen(key));\n  }\n\n  @Override\n  public Set<String> ftList() {\n    return executeCommand(commandObjects.ftList());\n  }\n  // RediSearch commands\n\n  // RedisJSON commands\n  @Override\n  public String jsonSet(String key, Path2 path, Object object) {\n    return executeCommand(commandObjects.jsonSet(key, path, object));\n  }\n\n  @Override\n  public String jsonSetWithEscape(String key, Path2 path, Object object) {\n    return executeCommand(commandObjects.jsonSetWithEscape(key, path, object));\n  }\n\n  @Override\n  @Deprecated\n  public String jsonSet(String key, Path path, Object pojo) {\n    return executeCommand(commandObjects.jsonSet(key, path, pojo));\n  }\n\n  @Override\n  @Deprecated\n  public String jsonSetWithPlainString(String key, Path path, String string) {\n    return executeCommand(commandObjects.jsonSetWithPlainString(key, path, string));\n  }\n\n  @Override\n  public String jsonSet(String key, Path2 path, Object pojo, JsonSetParams params) {\n    return executeCommand(commandObjects.jsonSet(key, path, pojo, params));\n  }\n\n  @Override\n  public String jsonSetWithEscape(String key, Path2 path, Object pojo, JsonSetParams params) {\n    return executeCommand(commandObjects.jsonSetWithEscape(key, path, pojo, params));\n  }\n\n  @Override\n  @Deprecated\n  public String jsonSet(String key, Path path, Object pojo, JsonSetParams params) {\n    return executeCommand(commandObjects.jsonSet(key, path, pojo, params));\n  }\n\n  @Override\n  public String jsonMerge(String key, Path2 path, Object object) {\n    return executeCommand(commandObjects.jsonMerge(key, path, object));\n  }\n\n  @Override\n  @Deprecated\n  public String jsonMerge(String key, Path path, Object pojo) {\n    return executeCommand(commandObjects.jsonMerge(key, path, pojo));\n  }\n\n  @Override\n  public Object jsonGet(String key) {\n    return executeCommand(commandObjects.jsonGet(key));\n  }\n\n  @Override\n  @Deprecated\n  public <T> T jsonGet(String key, Class<T> clazz) {\n    return executeCommand(commandObjects.jsonGet(key, clazz));\n  }\n\n  @Override\n  public Object jsonGet(String key, Path2... paths) {\n    return executeCommand(commandObjects.jsonGet(key, paths));\n  }\n\n  @Override\n  @Deprecated\n  public Object jsonGet(String key, Path... paths) {\n    return executeCommand(commandObjects.jsonGet(key, paths));\n  }\n\n  @Override\n  @Deprecated\n  public String jsonGetAsPlainString(String key, Path path) {\n    return executeCommand(commandObjects.jsonGetAsPlainString(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public <T> T jsonGet(String key, Class<T> clazz, Path... paths) {\n    return executeCommand(commandObjects.jsonGet(key, clazz, paths));\n  }\n\n  @Override\n  public List<JSONArray> jsonMGet(Path2 path, String... keys) {\n    return executeCommand(commandObjects.jsonMGet(path, keys));\n  }\n\n  @Override\n  @Deprecated\n  public <T> List<T> jsonMGet(Path path, Class<T> clazz, String... keys) {\n    return executeCommand(commandObjects.jsonMGet(path, clazz, keys));\n  }\n\n  @Override\n  public long jsonDel(String key) {\n    return executeCommand(commandObjects.jsonDel(key));\n  }\n\n  @Override\n  public long jsonDel(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonDel(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonDel(String key, Path path) {\n    return executeCommand(commandObjects.jsonDel(key, path));\n  }\n\n  @Override\n  public long jsonClear(String key) {\n    return executeCommand(commandObjects.jsonClear(key));\n  }\n\n  @Override\n  public long jsonClear(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonClear(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonClear(String key, Path path) {\n    return executeCommand(commandObjects.jsonClear(key, path));\n  }\n\n  @Override\n  public List<Boolean> jsonToggle(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonToggle(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public String jsonToggle(String key, Path path) {\n    return executeCommand(commandObjects.jsonToggle(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public Class<?> jsonType(String key) {\n    return executeCommand(commandObjects.jsonType(key));\n  }\n\n  @Override\n  public List<Class<?>> jsonType(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonType(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public Class<?> jsonType(String key, Path path) {\n    return executeCommand(commandObjects.jsonType(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonStrAppend(String key, Object string) {\n    return executeCommand(commandObjects.jsonStrAppend(key, string));\n  }\n\n  @Override\n  public List<Long> jsonStrAppend(String key, Path2 path, Object string) {\n    return executeCommand(commandObjects.jsonStrAppend(key, path, string));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonStrAppend(String key, Path path, Object string) {\n    return executeCommand(commandObjects.jsonStrAppend(key, path, string));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonStrLen(String key) {\n    return executeCommand(commandObjects.jsonStrLen(key));\n  }\n\n  @Override\n  public List<Long> jsonStrLen(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonStrLen(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonStrLen(String key, Path path) {\n    return executeCommand(commandObjects.jsonStrLen(key, path));\n  }\n\n  @Override\n  public Object jsonNumIncrBy(String key, Path2 path, double value) {\n    return executeCommand(commandObjects.jsonNumIncrBy(key, path, value));\n  }\n\n  @Override\n  @Deprecated\n  public double jsonNumIncrBy(String key, Path path, double value) {\n    return executeCommand(commandObjects.jsonNumIncrBy(key, path, value));\n  }\n\n  @Override\n  public List<Long> jsonArrAppend(String key, Path2 path, Object... objects) {\n    return executeCommand(commandObjects.jsonArrAppend(key, path, objects));\n  }\n\n  @Override\n  public List<Long> jsonArrAppendWithEscape(String key, Path2 path, Object... objects) {\n    return executeCommand(commandObjects.jsonArrAppendWithEscape(key, path, objects));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonArrAppend(String key, Path path, Object... pojos) {\n    return executeCommand(commandObjects.jsonArrAppend(key, path, pojos));\n  }\n\n  @Override\n  public List<Long> jsonArrIndex(String key, Path2 path, Object scalar) {\n    return executeCommand(commandObjects.jsonArrIndex(key, path, scalar));\n  }\n\n  @Override\n  public List<Long> jsonArrIndexWithEscape(String key, Path2 path, Object scalar) {\n    return executeCommand(commandObjects.jsonArrIndexWithEscape(key, path, scalar));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonArrIndex(String key, Path path, Object scalar) {\n    return executeCommand(commandObjects.jsonArrIndex(key, path, scalar));\n  }\n\n  @Override\n  public List<Long> jsonArrInsert(String key, Path2 path, int index, Object... objects) {\n    return executeCommand(commandObjects.jsonArrInsert(key, path, index, objects));\n  }\n\n  @Override\n  public List<Long> jsonArrInsertWithEscape(String key, Path2 path, int index, Object... objects) {\n    return executeCommand(commandObjects.jsonArrInsertWithEscape(key, path, index, objects));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonArrInsert(String key, Path path, int index, Object... pojos) {\n    return executeCommand(commandObjects.jsonArrInsert(key, path, index, pojos));\n  }\n\n  @Override\n  @Deprecated\n  public Object jsonArrPop(String key) {\n    return executeCommand(commandObjects.jsonArrPop(key));\n  }\n\n  @Override\n  @Deprecated\n  public <T> T jsonArrPop(String key, Class<T> clazz) {\n    return executeCommand(commandObjects.jsonArrPop(key, clazz));\n  }\n\n  @Override\n  public List<Object> jsonArrPop(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonArrPop(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public Object jsonArrPop(String key, Path path) {\n    return executeCommand(commandObjects.jsonArrPop(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public <T> T jsonArrPop(String key, Class<T> clazz, Path path) {\n    return executeCommand(commandObjects.jsonArrPop(key, clazz, path));\n  }\n\n  @Override\n  public List<Object> jsonArrPop(String key, Path2 path, int index) {\n    return executeCommand(commandObjects.jsonArrPop(key, path, index));\n  }\n\n  @Override\n  @Deprecated\n  public Object jsonArrPop(String key, Path path, int index) {\n    return executeCommand(commandObjects.jsonArrPop(key, path, index));\n  }\n\n  @Override\n  @Deprecated\n  public <T> T jsonArrPop(String key, Class<T> clazz, Path path, int index) {\n    return executeCommand(commandObjects.jsonArrPop(key, clazz, path, index));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonArrLen(String key) {\n    return executeCommand(commandObjects.jsonArrLen(key));\n  }\n\n  @Override\n  public List<Long> jsonArrLen(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonArrLen(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonArrLen(String key, Path path) {\n    return executeCommand(commandObjects.jsonArrLen(key, path));\n  }\n\n  @Override\n  public List<Long> jsonArrTrim(String key, Path2 path, int start, int stop) {\n    return executeCommand(commandObjects.jsonArrTrim(key, path, start, stop));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonArrTrim(String key, Path path, int start, int stop) {\n    return executeCommand(commandObjects.jsonArrTrim(key, path, start, stop));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonObjLen(String key) {\n    return executeCommand(commandObjects.jsonObjLen(key));\n  }\n\n  @Override\n  @Deprecated\n  public Long jsonObjLen(String key, Path path) {\n    return executeCommand(commandObjects.jsonObjLen(key, path));\n  }\n\n  @Override\n  public List<Long> jsonObjLen(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonObjLen(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public List<String> jsonObjKeys(String key) {\n    return executeCommand(commandObjects.jsonObjKeys(key));\n  }\n\n  @Override\n  @Deprecated\n  public List<String> jsonObjKeys(String key, Path path) {\n    return executeCommand(commandObjects.jsonObjKeys(key, path));\n  }\n\n  @Override\n  public List<List<String>> jsonObjKeys(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonObjKeys(key, path));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonDebugMemory(String key) {\n    return executeCommand(commandObjects.jsonDebugMemory(key));\n  }\n\n  @Override\n  @Deprecated\n  public long jsonDebugMemory(String key, Path path) {\n    return executeCommand(commandObjects.jsonDebugMemory(key, path));\n  }\n\n  @Override\n  public List<Long> jsonDebugMemory(String key, Path2 path) {\n    return executeCommand(commandObjects.jsonDebugMemory(key, path));\n  }\n  // RedisJSON commands\n\n  // RedisTimeSeries commands\n  @Override\n  public String tsCreate(String key) {\n    return executeCommand(commandObjects.tsCreate(key));\n  }\n\n  @Override\n  public String tsCreate(String key, TSCreateParams createParams) {\n    return executeCommand(commandObjects.tsCreate(key, createParams));\n  }\n\n  @Override\n  public long tsDel(String key, long fromTimestamp, long toTimestamp) {\n    return executeCommand(commandObjects.tsDel(key, fromTimestamp, toTimestamp));\n  }\n\n  @Override\n  public String tsAlter(String key, TSAlterParams alterParams) {\n    return executeCommand(commandObjects.tsAlter(key, alterParams));\n  }\n\n  @Override\n  public long tsAdd(String key, double value) {\n    return executeCommand(commandObjects.tsAdd(key, value));\n  }\n\n  @Override\n  public long tsAdd(String key, long timestamp, double value) {\n    return executeCommand(commandObjects.tsAdd(key, timestamp, value));\n  }\n\n  @Override\n  public long tsAdd(String key, long timestamp, double value, TSCreateParams createParams) {\n    return executeCommand(commandObjects.tsAdd(key, timestamp, value, createParams));\n  }\n\n  @Override\n  public long tsAdd(String key, long timestamp, double value, TSAddParams addParams) {\n    return executeCommand(commandObjects.tsAdd(key, timestamp, value, addParams));\n  }\n\n  @Override\n  public List<Long> tsMAdd(Map.Entry<String, TSElement>... entries) {\n    return executeCommand(commandObjects.tsMAdd(entries));\n  }\n\n  @Override\n  public long tsIncrBy(String key, double value) {\n    return executeCommand(commandObjects.tsIncrBy(key, value));\n  }\n\n  @Override\n  public long tsIncrBy(String key, double value, long timestamp) {\n    return executeCommand(commandObjects.tsIncrBy(key, value, timestamp));\n  }\n\n  @Override\n  public long tsIncrBy(String key, double addend, TSIncrByParams incrByParams) {\n    return executeCommand(commandObjects.tsIncrBy(key, addend, incrByParams));\n  }\n\n  @Override\n  public long tsDecrBy(String key, double value) {\n    return executeCommand(commandObjects.tsDecrBy(key, value));\n  }\n\n  @Override\n  public long tsDecrBy(String key, double value, long timestamp) {\n    return executeCommand(commandObjects.tsDecrBy(key, value, timestamp));\n  }\n\n  @Override\n  public long tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) {\n    return executeCommand(commandObjects.tsDecrBy(key, subtrahend, decrByParams));\n  }\n\n  @Override\n  public List<TSElement> tsRange(String key, long fromTimestamp, long toTimestamp) {\n    return executeCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp));\n  }\n\n  @Override\n  public List<TSElement> tsRange(String key, TSRangeParams rangeParams) {\n    return executeCommand(commandObjects.tsRange(key, rangeParams));\n  }\n\n  @Override\n  public List<TSElement> tsRevRange(String key, long fromTimestamp, long toTimestamp) {\n    return executeCommand(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp));\n  }\n\n  @Override\n  public List<TSElement> tsRevRange(String key, TSRangeParams rangeParams) {\n    return executeCommand(commandObjects.tsRevRange(key, rangeParams));\n  }\n\n  @Override\n  public Map<String, TSMRangeElements> tsMRange(long fromTimestamp, long toTimestamp, String... filters) {\n    return executeCommand(commandObjects.tsMRange(fromTimestamp, toTimestamp, filters));\n  }\n\n  @Override\n  public Map<String, TSMRangeElements> tsMRange(TSMRangeParams multiRangeParams) {\n    return executeCommand(commandObjects.tsMRange(multiRangeParams));\n  }\n\n  @Override\n  public Map<String, TSMRangeElements> tsMRevRange(long fromTimestamp, long toTimestamp, String... filters) {\n    return executeCommand(commandObjects.tsMRevRange(fromTimestamp, toTimestamp, filters));\n  }\n\n  @Override\n  public Map<String, TSMRangeElements> tsMRevRange(TSMRangeParams multiRangeParams) {\n    return executeCommand(commandObjects.tsMRevRange(multiRangeParams));\n  }\n\n  @Override\n  public TSElement tsGet(String key) {\n    return executeCommand(commandObjects.tsGet(key));\n  }\n\n  @Override\n  public TSElement tsGet(String key, TSGetParams getParams) {\n    return executeCommand(commandObjects.tsGet(key, getParams));\n  }\n\n  @Override\n  public Map<String, TSMGetElement> tsMGet(TSMGetParams multiGetParams, String... filters) {\n    return executeCommand(commandObjects.tsMGet(multiGetParams, filters));\n  }\n\n  @Override\n  public String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long timeBucket) {\n    return executeCommand(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, timeBucket));\n  }\n\n  @Override\n  public String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, long alignTimestamp) {\n    return executeCommand(\n        commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp));\n  }\n\n  @Override\n  public String tsDeleteRule(String sourceKey, String destKey) {\n    return executeCommand(commandObjects.tsDeleteRule(sourceKey, destKey));\n  }\n\n  @Override\n  public List<String> tsQueryIndex(String... filters) {\n    return executeCommand(commandObjects.tsQueryIndex(filters));\n  }\n\n  @Override\n  public TSInfo tsInfo(String key) {\n    return executeCommand(commandObjects.tsInfo(key));\n  }\n\n  @Override\n  public TSInfo tsInfoDebug(String key) {\n    return executeCommand(commandObjects.tsInfoDebug(key));\n  }\n  // RedisTimeSeries commands\n\n  // RedisBloom commands\n  @Override\n  public String bfReserve(String key, double errorRate, long capacity) {\n    return executeCommand(commandObjects.bfReserve(key, errorRate, capacity));\n  }\n\n  @Override\n  public String bfReserve(String key, double errorRate, long capacity, BFReserveParams reserveParams) {\n    return executeCommand(commandObjects.bfReserve(key, errorRate, capacity, reserveParams));\n  }\n\n  @Override\n  public boolean bfAdd(String key, String item) {\n    return executeCommand(commandObjects.bfAdd(key, item));\n  }\n\n  @Override\n  public List<Boolean> bfMAdd(String key, String... items) {\n    return executeCommand(commandObjects.bfMAdd(key, items));\n  }\n\n  @Override\n  public List<Boolean> bfInsert(String key, String... items) {\n    return executeCommand(commandObjects.bfInsert(key, items));\n  }\n\n  @Override\n  public List<Boolean> bfInsert(String key, BFInsertParams insertParams, String... items) {\n    return executeCommand(commandObjects.bfInsert(key, insertParams, items));\n  }\n\n  @Override\n  public boolean bfExists(String key, String item) {\n    return executeCommand(commandObjects.bfExists(key, item));\n  }\n\n  @Override\n  public List<Boolean> bfMExists(String key, String... items) {\n    return executeCommand(commandObjects.bfMExists(key, items));\n  }\n\n  @Override\n  public Map.Entry<Long, byte[]> bfScanDump(String key, long iterator) {\n    return executeCommand(commandObjects.bfScanDump(key, iterator));\n  }\n\n  @Override\n  public String bfLoadChunk(String key, long iterator, byte[] data) {\n    return executeCommand(commandObjects.bfLoadChunk(key, iterator, data));\n  }\n\n  @Override\n  public long bfCard(String key) {\n    return executeCommand(commandObjects.bfCard(key));\n  }\n\n  @Override\n  public Map<String, Object> bfInfo(String key) {\n    return executeCommand(commandObjects.bfInfo(key));\n  }\n\n  @Override\n  public String cfReserve(String key, long capacity) {\n    return executeCommand(commandObjects.cfReserve(key, capacity));\n  }\n\n  @Override\n  public String cfReserve(String key, long capacity, CFReserveParams reserveParams) {\n    return executeCommand(commandObjects.cfReserve(key, capacity, reserveParams));\n  }\n\n  @Override\n  public boolean cfAdd(String key, String item) {\n    return executeCommand(commandObjects.cfAdd(key, item));\n  }\n\n  @Override\n  public boolean cfAddNx(String key, String item) {\n    return executeCommand(commandObjects.cfAddNx(key, item));\n  }\n\n  @Override\n  public List<Boolean> cfInsert(String key, String... items) {\n    return executeCommand(commandObjects.cfInsert(key, items));\n  }\n\n  @Override\n  public List<Boolean> cfInsert(String key, CFInsertParams insertParams, String... items) {\n    return executeCommand(commandObjects.cfInsert(key, insertParams, items));\n  }\n\n  @Override\n  public List<Boolean> cfInsertNx(String key, String... items) {\n    return executeCommand(commandObjects.cfInsertNx(key, items));\n  }\n\n  @Override\n  public List<Boolean> cfInsertNx(String key, CFInsertParams insertParams, String... items) {\n    return executeCommand(commandObjects.cfInsertNx(key, insertParams, items));\n  }\n\n  @Override\n  public boolean cfExists(String key, String item) {\n    return executeCommand(commandObjects.cfExists(key, item));\n  }\n\n  @Override\n  public List<Boolean> cfMExists(String key, String... items) {\n    return executeCommand(commandObjects.cfMExists(key, items));\n  }\n\n  @Override\n  public boolean cfDel(String key, String item) {\n    return executeCommand(commandObjects.cfDel(key, item));\n  }\n\n  @Override\n  public long cfCount(String key, String item) {\n    return executeCommand(commandObjects.cfCount(key, item));\n  }\n\n  @Override\n  public Map.Entry<Long, byte[]> cfScanDump(String key, long iterator) {\n    return executeCommand(commandObjects.cfScanDump(key, iterator));\n  }\n\n  @Override\n  public String cfLoadChunk(String key, long iterator, byte[] data) {\n    return executeCommand(commandObjects.cfLoadChunk(key, iterator, data));\n  }\n\n  @Override\n  public Map<String, Object> cfInfo(String key) {\n    return executeCommand(commandObjects.cfInfo(key));\n  }\n\n  @Override\n  public String cmsInitByDim(String key, long width, long depth) {\n    return executeCommand(commandObjects.cmsInitByDim(key, width, depth));\n  }\n\n  @Override\n  public String cmsInitByProb(String key, double error, double probability) {\n    return executeCommand(commandObjects.cmsInitByProb(key, error, probability));\n  }\n\n  @Override\n  public List<Long> cmsIncrBy(String key, Map<String, Long> itemIncrements) {\n    return executeCommand(commandObjects.cmsIncrBy(key, itemIncrements));\n  }\n\n  @Override\n  public List<Long> cmsQuery(String key, String... items) {\n    return executeCommand(commandObjects.cmsQuery(key, items));\n  }\n\n  @Override\n  public String cmsMerge(String destKey, String... keys) {\n    return executeCommand(commandObjects.cmsMerge(destKey, keys));\n  }\n\n  @Override\n  public String cmsMerge(String destKey, Map<String, Long> keysAndWeights) {\n    return executeCommand(commandObjects.cmsMerge(destKey, keysAndWeights));\n  }\n\n  @Override\n  public Map<String, Object> cmsInfo(String key) {\n    return executeCommand(commandObjects.cmsInfo(key));\n  }\n\n  @Override\n  public String topkReserve(String key, long topk) {\n    return executeCommand(commandObjects.topkReserve(key, topk));\n  }\n\n  @Override\n  public String topkReserve(String key, long topk, long width, long depth, double decay) {\n    return executeCommand(commandObjects.topkReserve(key, topk, width, depth, decay));\n  }\n\n  @Override\n  public List<String> topkAdd(String key, String... items) {\n    return executeCommand(commandObjects.topkAdd(key, items));\n  }\n\n  @Override\n  public List<String> topkIncrBy(String key, Map<String, Long> itemIncrements) {\n    return executeCommand(commandObjects.topkIncrBy(key, itemIncrements));\n  }\n\n  @Override\n  public List<Boolean> topkQuery(String key, String... items) {\n    return executeCommand(commandObjects.topkQuery(key, items));\n  }\n\n  @Override\n  public List<String> topkList(String key) {\n    return executeCommand(commandObjects.topkList(key));\n  }\n\n  @Override\n  public Map<String, Long> topkListWithCount(String key) {\n    return executeCommand(commandObjects.topkListWithCount(key));\n  }\n\n  @Override\n  public Map<String, Object> topkInfo(String key) {\n    return executeCommand(commandObjects.topkInfo(key));\n  }\n\n  @Override\n  public String tdigestCreate(String key) {\n    return executeCommand(commandObjects.tdigestCreate(key));\n  }\n\n  @Override\n  public String tdigestCreate(String key, int compression) {\n    return executeCommand(commandObjects.tdigestCreate(key, compression));\n  }\n\n  @Override\n  public String tdigestReset(String key) {\n    return executeCommand(commandObjects.tdigestReset(key));\n  }\n\n  @Override\n  public String tdigestMerge(String destinationKey, String... sourceKeys) {\n    return executeCommand(commandObjects.tdigestMerge(destinationKey, sourceKeys));\n  }\n\n  @Override\n  public String tdigestMerge(TDigestMergeParams mergeParams, String destinationKey, String... sourceKeys) {\n    return executeCommand(commandObjects.tdigestMerge(mergeParams, destinationKey, sourceKeys));\n  }\n\n  @Override\n  public Map<String, Object> tdigestInfo(String key) {\n    return executeCommand(commandObjects.tdigestInfo(key));\n  }\n\n  @Override\n  public String tdigestAdd(String key, double... values) {\n    return executeCommand(commandObjects.tdigestAdd(key, values));\n  }\n\n  @Override\n  public List<Double> tdigestCDF(String key, double... values) {\n    return executeCommand(commandObjects.tdigestCDF(key, values));\n  }\n\n  @Override\n  public List<Double> tdigestQuantile(String key, double... quantiles) {\n    return executeCommand(commandObjects.tdigestQuantile(key, quantiles));\n  }\n\n  @Override\n  public double tdigestMin(String key) {\n    return executeCommand(commandObjects.tdigestMin(key));\n  }\n\n  @Override\n  public double tdigestMax(String key) {\n    return executeCommand(commandObjects.tdigestMax(key));\n  }\n\n  @Override\n  public double tdigestTrimmedMean(String key, double lowCutQuantile, double highCutQuantile) {\n    return executeCommand(commandObjects.tdigestTrimmedMean(key, lowCutQuantile, highCutQuantile));\n  }\n\n  @Override\n  public List<Long> tdigestRank(String key, double... values) {\n    return executeCommand(commandObjects.tdigestRank(key, values));\n  }\n\n  @Override\n  public List<Long> tdigestRevRank(String key, double... values) {\n    return executeCommand(commandObjects.tdigestRevRank(key, values));\n  }\n\n  @Override\n  public List<Double> tdigestByRank(String key, long... ranks) {\n    return executeCommand(commandObjects.tdigestByRank(key, ranks));\n  }\n\n  @Override\n  public List<Double> tdigestByRevRank(String key, long... ranks) {\n    return executeCommand(commandObjects.tdigestByRevRank(key, ranks));\n  }\n  // RedisBloom commands\n\n  /**\n   * @return pipeline object\n   */\n  public AbstractPipeline pipelined() {\n    if (provider == null) {\n      throw new IllegalStateException(\"It is not allowed to create Pipeline from this \" + getClass());\n    } else if (provider instanceof MultiDbConnectionProvider) {\n      return new MultiDbPipeline((MultiDbConnectionProvider) provider, commandObjects);\n    } else {\n      return new Pipeline(provider.getConnection(), true, commandObjects);\n    }\n  }\n\n  /**\n   * @return transaction object\n   */\n  public AbstractTransaction multi() {\n    return transaction(true);\n  }\n\n  /**\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @return transaction object\n   */\n  public AbstractTransaction transaction(boolean doMulti) {\n    if (provider == null) {\n      throw new IllegalStateException(\"It is not allowed to create Transaction from this \" + getClass());\n    } else if (provider instanceof MultiDbConnectionProvider) {\n      return new MultiDbTransaction((MultiDbConnectionProvider) provider, doMulti, commandObjects);\n    } else {\n      return new Transaction(provider.getConnection(), doMulti, true, commandObjects);\n    }\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(PING));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendCommand(ProtocolCommand cmd) {\n    return executeCommand(commandObjects.commandArguments(cmd));\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(GET).key(\"mykey\".getBytes()));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendCommand(ProtocolCommand cmd, byte[]... args) {\n    return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args));\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(BLPOP).key(\"mykey\".getBytes()).add(\"0\".getBytes()).blocking());\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendBlockingCommand(ProtocolCommand cmd, byte[]... args) {\n    return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking());\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(SET).key(\"x\").add(\"1\"));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendCommand(ProtocolCommand cmd, String... args) {\n    return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args));\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(BLPOP).key(\"mykey\").add(\"0\").blocking());\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendBlockingCommand(ProtocolCommand cmd, String... args) {\n    return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking());\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(GET).key(\"mykey\".getBytes()).processKey(sampleKey));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendCommand(byte[] sampleKey, ProtocolCommand cmd, byte[]... args) {\n    return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args).addHashSlotKey(sampleKey));\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(BLPOP).key(\"mykey\".getBytes()).add(\"0\".getBytes()).blocking().processKey(sampleKey));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendBlockingCommand(byte[] sampleKey, ProtocolCommand cmd, byte[]... args) {\n    return executeCommand(\n        commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking().addHashSlotKey(sampleKey));\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(GET).key(\"mykey\").processKey(sampleKey));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendCommand(String sampleKey, ProtocolCommand cmd, String... args) {\n    return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args).addHashSlotKey(sampleKey));\n  }\n\n  /**\n   * @deprecated Deprecated in Jedis 7.4.0.\n   * Use {@link #executeCommand(CommandArguments)} with a {@link CommandArguments} object directly.\n   * <pre>{@code\n   * jedis.executeCommand(new CommandArguments(BLPOP).key(\"mykey\").add(\"0\").blocking().processKey(sampleKey));\n   * }</pre>\n   */\n  @Deprecated\n  public Object sendBlockingCommand(String sampleKey, ProtocolCommand cmd, String... args) {\n    return executeCommand(\n        commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking().addHashSlotKey(sampleKey));\n  }\n\n  public Object executeCommand(CommandArguments args) {\n    return executeCommand(new CommandObject<>(args, BuilderFactory.RAW_OBJECT));\n  }\n\n  @Experimental\n  public void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) {\n    this.commandObjects.setKeyArgumentPreProcessor(keyPreProcessor);\n  }\n\n  public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {\n    this.commandObjects.setJsonObjectMapper(jsonObjectMapper);\n  }\n\n  public void setDefaultSearchDialect(int dialect) {\n    this.commandObjects.setDefaultSearchDialect(dialect);\n  }\n\n  // Vector Set commands\n  @Override\n  public boolean vadd(String key, float[] vector, String element) {\n    return executeCommand(commandObjects.vadd(key, vector, element));\n  }\n\n  @Override\n  public boolean vadd(String key, float[] vector, String element, VAddParams params) {\n    return executeCommand(commandObjects.vadd(key, vector, element, params));\n  }\n\n  @Override\n  public boolean vaddFP32(String key, byte[] vectorBlob, String element) {\n    return executeCommand(commandObjects.vaddFP32(key, vectorBlob, element));\n  }\n\n  @Override\n  public boolean vaddFP32(String key, byte[] vectorBlob, String element, VAddParams params) {\n    return executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, params));\n  }\n\n  @Override\n  public boolean vadd(String key, float[] vector, String element, int reduceDim, VAddParams params) {\n    return executeCommand(commandObjects.vadd(key, vector, element, reduceDim, params));\n  }\n\n  @Override\n  public boolean vaddFP32(String key, byte[] vectorBlob, String element, int reduceDim, VAddParams params) {\n    return executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, reduceDim, params));\n  }\n\n  @Override\n  public List<String> vsim(String key, float[] vector) {\n    return executeCommand(commandObjects.vsim(key, vector));\n  }\n\n  @Override\n  public List<String> vsim(String key, float[] vector, VSimParams params) {\n    return executeCommand(commandObjects.vsim(key, vector, params));\n  }\n\n  @Override\n  public Map<String, Double> vsimWithScores(String key, float[] vector, VSimParams params) {\n    return executeCommand(commandObjects.vsimWithScores(key, vector, params));\n  }\n\n  @Override\n  public Map<String, VSimScoreAttribs> vsimWithScoresAndAttribs(String key, float[] vector, VSimParams params) {\n    return executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params));\n  }\n\n  @Override\n  public List<String> vsimByElement(String key, String element) {\n    return executeCommand(commandObjects.vsimByElement(key, element));\n  }\n\n  @Override\n  public List<String> vsimByElement(String key, String element, VSimParams params) {\n    return executeCommand(commandObjects.vsimByElement(key, element, params));\n  }\n\n  @Override\n  public Map<String, Double> vsimByElementWithScores(String key, String element, VSimParams params) {\n    return executeCommand(commandObjects.vsimByElementWithScores(key, element, params));\n  }\n\n  @Override\n  public Map<String, VSimScoreAttribs> vsimByElementWithScoresAndAttribs(String key, String element, VSimParams params) {\n    return executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params));\n  }\n\n  @Override\n  public long vdim(String key) {\n    return executeCommand(commandObjects.vdim(key));\n  }\n\n  @Override\n  public long vcard(String key) {\n    return executeCommand(commandObjects.vcard(key));\n  }\n\n  @Override\n  public List<Double> vemb(String key, String element) {\n    return executeCommand(commandObjects.vemb(key, element));\n  }\n\n  @Override\n  public RawVector vembRaw(String key, String element) {\n    return executeCommand(commandObjects.vembRaw(key, element));\n  }\n\n  @Override\n  public boolean vrem(String key, String element) {\n    return executeCommand(commandObjects.vrem(key, element));\n  }\n\n  @Override\n  public List<List<String>> vlinks(String key, String element) {\n    return executeCommand(commandObjects.vlinks(key, element));\n  }\n\n  @Override\n  public List<Map<String, Double>> vlinksWithScores(String key, String element) {\n    return executeCommand(commandObjects.vlinksWithScores(key, element));\n  }\n\n  @Override\n  public String vrandmember(String key) {\n    return executeCommand(commandObjects.vrandmember(key));\n  }\n\n  @Override\n  public List<String> vrandmember(String key, int count) {\n    return executeCommand(commandObjects.vrandmember(key, count));\n  }\n\n  @Override\n  public String vgetattr(String key, String element) {\n    return executeCommand(commandObjects.vgetattr(key, element));\n  }\n\n  @Override\n  public boolean vsetattr(String key, String element, String attributes) {\n    return executeCommand(commandObjects.vsetattr(key, element, attributes));\n  }\n\n  @Override\n  public VectorInfo vinfo(String key) {\n    return executeCommand(commandObjects.vinfo(key));\n  }\n\n  // Binary vector set commands\n  @Override\n  public boolean vadd(byte[] key, float[] vector, byte[] element) {\n    return executeCommand(commandObjects.vadd(key, vector, element));\n  }\n\n  @Override\n  public boolean vadd(byte[] key, float[] vector, byte[] element, VAddParams params) {\n    return executeCommand(commandObjects.vadd(key, vector, element, params));\n  }\n\n  @Override\n  public boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element) {\n    return executeCommand(commandObjects.vaddFP32(key, vectorBlob, element));\n  }\n\n  @Override\n  public boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, VAddParams params) {\n    return executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, params));\n  }\n\n  @Override\n  public boolean vadd(byte[] key, float[] vector, byte[] element, int reduceDim, VAddParams params) {\n    return executeCommand(commandObjects.vadd(key, vector, element, reduceDim, params));\n  }\n\n  @Override\n  public boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, int reduceDim, VAddParams params) {\n    return executeCommand(commandObjects.vaddFP32(key, vectorBlob, element, reduceDim, params));\n  }\n\n  @Override\n  public List<byte[]> vsim(byte[] key, float[] vector) {\n    return executeCommand(commandObjects.vsim(key, vector));\n  }\n\n  @Override\n  public List<byte[]> vsim(byte[] key, float[] vector, VSimParams params) {\n    return executeCommand(commandObjects.vsim(key, vector, params));\n  }\n\n  @Override\n  public Map<byte[], Double> vsimWithScores(byte[] key, float[] vector, VSimParams params) {\n    return executeCommand(commandObjects.vsimWithScores(key, vector, params));\n  }\n\n  @Override\n  public Map<byte[], VSimScoreAttribs> vsimWithScoresAndAttribs(byte[] key, float[] vector, VSimParams params) {\n    return executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params));\n  }\n\n  @Override\n  public List<byte[]> vsimByElement(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vsimByElement(key, element));\n  }\n\n  @Override\n  public List<byte[]> vsimByElement(byte[] key, byte[] element, VSimParams params) {\n    return executeCommand(commandObjects.vsimByElement(key, element, params));\n  }\n\n  @Override\n  public Map<byte[], Double> vsimByElementWithScores(byte[] key, byte[] element, VSimParams params) {\n    return executeCommand(commandObjects.vsimByElementWithScores(key, element, params));\n  }\n\n  @Override\n  public Map<byte[], VSimScoreAttribs> vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, VSimParams params) {\n    return executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params));\n  }\n\n  @Override\n  public long vdim(byte[] key) {\n    return executeCommand(commandObjects.vdim(key));\n  }\n\n  @Override\n  public long vcard(byte[] key) {\n    return executeCommand(commandObjects.vcard(key));\n  }\n\n  @Override\n  public List<Double> vemb(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vemb(key, element));\n  }\n\n  @Override\n  public RawVector vembRaw(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vembRaw(key, element));\n  }\n\n  @Override\n  public boolean vrem(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vrem(key, element));\n  }\n\n  @Override\n  public List<List<byte[]>> vlinks(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vlinks(key, element));\n  }\n\n  @Override\n  public List<Map<byte[], Double>> vlinksWithScores(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vlinksWithScores(key, element));\n  }\n\n  @Override\n  public byte[] vrandmember(byte[] key) {\n    return executeCommand(commandObjects.vrandmember(key));\n  }\n\n  @Override\n  public List<byte[]> vrandmember(byte[] key, int count) {\n    return executeCommand(commandObjects.vrandmember(key, count));\n  }\n\n  @Override\n  public byte[] vgetattr(byte[] key, byte[] element) {\n    return executeCommand(commandObjects.vgetattr(key, element));\n  }\n\n  @Override\n  public boolean vsetattr(byte[] key, byte[] element, byte[] attributes) {\n    return executeCommand(commandObjects.vsetattr(key, element, attributes));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/annots/Experimental.java",
    "content": "package redis.clients.jedis.annots;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation to mark classes for experimental development.\n * <p>\n * Classes with this annotation may be renamed, changed or even removed in a future version. This\n * annotation doesn't mean that the implementation has an 'experimental' quality.\n * <p>\n * If a type is marked with this annotation, all its members are considered experimental.\n */\n@Documented\n@Target({ ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD,\n    ElementType.CONSTRUCTOR })\npublic @interface Experimental {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/annots/Internal.java",
    "content": "package redis.clients.jedis.annots;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation to mark classes or methods as an internal development API. It indicates that the\n * annotated element must not be considered as a public API.\n * <p>\n * Classes or methods with this annotation may change across releases.\n * <p>\n * If a type is marked with this annotation, all its members are considered internal.\n */\n@Documented\n@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD })\npublic @interface Internal {\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/annots/VisibleForTesting.java",
    "content": "package redis.clients.jedis.annots;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Target;\n\n/**\n * A member or type annotated with {@link VisibleForTesting} declares that it is only visible for\n * testing purposes.\n */\n@Documented\n@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE })\npublic @interface VisibleForTesting {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/BitCountOption.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * The options for {@code BITCOUNT} command.\n */\npublic enum BitCountOption implements Rawable {\n\n  BYTE, BIT;\n\n  private final byte[] raw;\n\n  private BitCountOption() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/BitOP.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Bit operations for {@code BITOP} command.\n */\npublic enum BitOP implements Rawable {\n\n  AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR, ONE;\n\n  private final byte[] raw;\n\n  private BitOP() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ClientAttributeOption.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * CLIENT SETINFO command attr option\n * since redis 7.2\n */\npublic enum ClientAttributeOption implements Rawable {\n    LIB_NAME(\"LIB-NAME\"),\n    LIB_VER(\"LIB-VER\");\n\n    private final byte[] raw;\n\n    private ClientAttributeOption(String str) {\n        this.raw = SafeEncoder.encode(str);\n    }\n\n    @Override\n    public byte[] getRaw() {\n        return raw;\n    }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ClientPauseMode.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Client pause supported modes.\n */\npublic enum ClientPauseMode implements Rawable {\n\n  ALL, WRITE;\n\n  private final byte[] raw;\n\n  private ClientPauseMode() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ClientType.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum ClientType implements Rawable {\n\n  NORMAL, MASTER, SLAVE, REPLICA, PUBSUB;\n\n  private final byte[] raw;\n\n  private ClientType() {\n    raw = SafeEncoder.encode(name().toLowerCase());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ClusterFailoverOption.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Enumeration of cluster failover options.\n * <p>\n * Used by {@link redis.clients.jedis.commands.ClusterCommands#clusterFailover(ClusterFailoverOption)}.\n */\npublic enum ClusterFailoverOption implements Rawable {\n\n  FORCE, TAKEOVER;\n\n  private final byte[] raw;\n\n  private ClusterFailoverOption() {\n    this.raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ClusterResetType.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Reset type for command cluster reset. Before reset cluster status you should make true no data in Redis.\n * It is generally used to create/expand clusters.\n */\npublic enum ClusterResetType implements Rawable {\n\n  /**\n   * Soft reset: Reset only the cluster info.\n   */\n  SOFT,\n\n  /**\n   * Hard reset: Reset the cluster info, set epochs to 0, change node ID.\n   */\n  HARD;\n\n  private final byte[] raw;\n\n  private ClusterResetType() {\n    this.raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ExpiryOption.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Enumeration of setting expiration.\n */\npublic enum ExpiryOption implements Rawable {\n\n  /**\n   * Set expiry only when the key has no expiry.\n   */\n  NX,\n\n  /**\n   * Set expiry only when the key has an existing expiry.\n   */\n  XX,\n\n  /**\n   * Set expiry only when the new expiry is greater than the existing one.\n   */\n  GT,\n\n  /**\n   * Set expiry only when the new expiry is less than the existing one.\n   */\n  LT;\n\n  private final byte[] raw;\n\n  private ExpiryOption() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/FlushMode.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Enum object describing flushing mode.\n */\npublic enum FlushMode implements Rawable {\n\n  /**\n   * flushes synchronously\n   */\n  SYNC,\n\n  /**\n   * flushes asynchronously\n   */\n  ASYNC;\n\n  private final byte[] raw;\n\n  private FlushMode() {\n    raw = SafeEncoder.encode(this.name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/FunctionRestorePolicy.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum FunctionRestorePolicy implements Rawable {\n  /**\n   * Delete all existing libraries before restoring the payload\n   */\n  FLUSH,\n\n  /**\n   * Append the restored libraries to the existing libraries and\n   * aborts on collision. This is the default policy.\n   */\n  APPEND,\n\n  /**\n   * Append the restored libraries to the existing libraries, replacing\n   * any existing ones in case of name collisions. Note that this policy\n   * doesn't prevent function name collisions, only libraries.\n   */\n  REPLACE;\n\n  private final byte[] raw;\n\n  private FunctionRestorePolicy() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/GeoUnit.java",
    "content": "package redis.clients.jedis.args;\n\nimport java.util.Locale;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum GeoUnit implements Rawable {\n\n  M, KM, MI, FT;\n\n  private final byte[] raw;\n\n  private GeoUnit() {\n    raw = SafeEncoder.encode(name().toLowerCase(Locale.ENGLISH));\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/HotkeysMetric.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Enum representing the metrics that can be tracked by the HOTKEYS command.\n */\npublic enum HotkeysMetric implements Rawable {\n\n  /**\n   * Track CPU time consumption per key.\n   */\n  CPU,\n\n  /**\n   * Track network bytes transferred per key.\n   */\n  NET;\n\n  private final byte[] raw;\n\n  private HotkeysMetric() {\n    raw = SafeEncoder.encode(this.name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/LatencyEvent.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum LatencyEvent implements Rawable {\n\n    ACTIVE_DEFRAG_CYCLE(\"active-defrag-cycle\"), AOF_FSYNC_ALWAYS(\"aof-fsync-always\"), AOF_STAT(\"aof-stat\"),\n    AOF_REWRITE_DIFF_WRITE(\"aof-rewrite-diff-write\"), AOF_RENAME(\"aof-rename\"), AOF_WRITE(\"aof-write\"),\n    AOF_WRITE_ACTIVE_CHILD(\"aof-write-active-child\"), AOF_WRITE_ALONE(\"aof-write-alone\"),\n    AOF_WRITE_PENDING_FSYNC(\"aof-write-pending-fsync\"), COMMAND(\"command\"), EXPIRE_CYCLE(\"expire-cycle\"),\n    EVICTION_CYCLE(\"eviction-cycle\"), EVICTION_DEL(\"eviction-del\"), FAST_COMMAND(\"fast-command\"),\n    FORK(\"fork\"), RDB_UNLINK_TEMP_FILE(\"rdb-unlink-temp-file\");\n\n    private final byte[] raw;\n\n    private LatencyEvent(String s) {\n        raw = SafeEncoder.encode(s);\n    }\n\n    @Override\n    public byte[] getRaw() {\n        return raw;\n    }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ListDirection.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Direction for {@code LMOVE} and {@code BLMOVE} command.\n */\npublic enum ListDirection implements Rawable {\n\n  LEFT, RIGHT;\n\n  private final byte[] raw;\n\n  private ListDirection() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/ListPosition.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum ListPosition implements Rawable {\n\n  BEFORE, AFTER;\n\n  private final byte[] raw;\n\n  private ListPosition() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/Rawable.java",
    "content": "package redis.clients.jedis.args;\n\n/**\n * Byte array representation of arguments to write in socket input stream.\n */\npublic interface Rawable {\n\n  /**\n   * Get byte array.\n   * @return binary\n   */\n  byte[] getRaw();\n\n  @Override\n  int hashCode();\n\n  @Override\n  boolean equals(Object o);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/RawableFactory.java",
    "content": "package redis.clients.jedis.args;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\n\nimport java.util.Arrays;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Factory class to get {@link Rawable} objects.\n */\npublic final class RawableFactory {\n\n  /**\n   * Get a {@link Rawable} from a {@code boolean}.\n   * @param b boolean value\n   * @return raw\n   */\n  public static Rawable from(boolean b) {\n    return from(toByteArray(b));\n  }\n\n  /**\n   * Get a {@link Rawable} from an {@code int}.\n   * @param i integer value\n   * @return raw\n   */\n  public static Rawable from(int i) {\n    return from(toByteArray(i));\n  }\n\n  /**\n   * Get a {@link Rawable} from a {@code long}.\n   * @param l long value\n   * @return raw\n   */\n  public static Rawable from(long l) {\n    return from(toByteArray(l));\n  }\n\n  /**\n   * Get a {@link Rawable} from a {@code double}.\n   * @param d numeric value\n   * @return raw\n   */\n  public static Rawable from(double d) {\n    return from(toByteArray(d));\n  }\n\n  /**\n   * Get a {@link Rawable} from a byte array.\n   * @param binary value\n   * @return raw\n   */\n  public static Rawable from(byte[] binary) {\n    return new Raw(binary);\n  }\n\n  /**\n   * Get a {@link Rawable} from a {@link String}.\n   * @param string value\n   * @return raw\n   */\n  public static Rawable from(String string) {\n    return new RawString(string);\n  }\n\n  /**\n   * Default implementation of {@link Rawable}.\n   */\n  public static class Raw implements Rawable {\n\n    private final byte[] raw;\n\n    public Raw(byte[] raw) {\n      this.raw = Arrays.copyOf(raw, raw.length);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) return true;\n      if (o == null || getClass() != o.getClass()) return false;\n      return Arrays.equals(raw, ((Raw) o).raw);\n    }\n\n    @Override\n    public int hashCode() {\n      return Arrays.hashCode(raw);\n    }\n  }\n\n  /**\n   * A {@link Rawable} wrapping a {@link String}.\n   */\n  public static class RawString extends Raw {\n\n    // TODO: private final String str; ^ implements Rawable\n\n    public RawString(String str) {\n      super(SafeEncoder.encode(str));\n    }\n  }\n\n  private RawableFactory() {\n    throw new InstantiationError();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/SaveMode.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum SaveMode implements Rawable {\n\n  /**\n   * Prevent a DB saving operation even if one or more save points are configured.\n   */\n  NOSAVE,\n\n  /**\n   * Force a DB saving operation even if no save points are configured.\n   */\n  SAVE;\n\n  private final byte[] raw;\n\n  private SaveMode() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/SortedSetOption.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum SortedSetOption implements Rawable {\n\n  MIN, MAX;\n\n  private final byte[] raw;\n\n  private SortedSetOption() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/SortingOrder.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum SortingOrder implements Rawable {\n\n  ASC, DESC;\n\n  private final byte[] raw;\n\n  private SortingOrder() {\n    raw = SafeEncoder.encode(this.name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Deletion policy for stream commands that handle consumer group references. Used with XDELEX,\n * XACKDEL, and enhanced XADD/XTRIM commands.\n */\npublic enum StreamDeletionPolicy implements Rawable {\n\n  /**\n   * Preserves existing references to entries in all consumer groups' PEL. This is the default\n   * behavior similar to XDEL.\n   */\n  KEEP_REFERENCES(\"KEEPREF\"),\n\n  /**\n   * Removes all references to entries from all consumer groups' pending entry lists, effectively\n   * cleaning up all traces of the messages.\n   */\n  DELETE_REFERENCES(\"DELREF\"),\n\n  /**\n   * Only operates on entries that were read and acknowledged by all consumer groups.\n   */\n  ACKNOWLEDGED(\"ACKED\");\n\n  private final byte[] raw;\n\n  StreamDeletionPolicy(String redisParamName) {\n    raw = SafeEncoder.encode(redisParamName);\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/UnblockType.java",
    "content": "package redis.clients.jedis.args;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Unblock type for {@code CLIENT UNBLOCK} command.\n */\npublic enum UnblockType implements Rawable {\n\n  TIMEOUT, ERROR;\n\n  private final byte[] raw;\n\n  private UnblockType() {\n    raw = SafeEncoder.encode(this.name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/args/package-info.java",
    "content": "/**\n * This package contains the classes that represent arguments of Redis core commands.\n */\npackage redis.clients.jedis.args;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/authentication/AuthXEventListener.java",
    "content": "package redis.clients.jedis.authentication;\n\npublic interface AuthXEventListener {\n\n    static AuthXEventListener NOOP_LISTENER = new AuthXEventListener() {\n\n        @Override\n        public void onIdentityProviderError(Exception reason) {\n        }\n\n        @Override\n        public void onConnectionAuthenticationError(Exception reason) {\n        }\n\n    };\n\n    public void onIdentityProviderError(Exception reason);\n\n    public void onConnectionAuthenticationError(Exception reason);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/authentication/AuthXManager.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.authentication.core.Token;\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.authentication.core.TokenListener;\nimport redis.clients.authentication.core.TokenManager;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.RedisCredentials;\n\npublic final class AuthXManager implements Supplier<RedisCredentials> {\n\n    private static final Logger log = LoggerFactory.getLogger(AuthXManager.class);\n\n    private TokenManager tokenManager;\n    private List<WeakReference<Connection>> connections = Collections\n            .synchronizedList(new ArrayList<>());\n    private Token currentToken;\n    private AuthXEventListener listener = AuthXEventListener.NOOP_LISTENER;\n    private List<Consumer<Token>> postAuthenticateHooks = new ArrayList<>();\n    private AtomicReference<CompletableFuture<Void>> uniqueStarterTask = new AtomicReference<>();\n\n    protected AuthXManager(TokenManager tokenManager) {\n        this.tokenManager = tokenManager;\n    }\n\n    public AuthXManager(TokenAuthConfig tokenAuthConfig) {\n        this(new TokenManager(tokenAuthConfig.getIdentityProviderConfig().getProvider(),\n                tokenAuthConfig.getTokenManagerConfig()));\n    }\n\n    public void start() {\n        Future<Void> safeStarter = safeStart(this::tokenManagerStart);\n        try {\n            safeStarter.get();\n        } catch (InterruptedException | ExecutionException e) {\n            log.error(\"AuthXManager failed to start!\", e);\n            throw new JedisAuthenticationException(\"AuthXManager failed to start!\",\n                    (e instanceof ExecutionException) ? e.getCause() : e);\n        }\n    }\n\n    private Future<Void> safeStart(Runnable starter) {\n        if (uniqueStarterTask.compareAndSet(null, new CompletableFuture<Void>())) {\n            try {\n                starter.run();\n                uniqueStarterTask.get().complete(null);\n            } catch (Exception e) {\n                uniqueStarterTask.get().completeExceptionally(e);\n            }\n        }\n        return uniqueStarterTask.get();\n    }\n\n    private void tokenManagerStart() {\n        tokenManager.start(new TokenListener() {\n            @Override\n            public void onTokenRenewed(Token token) {\n                currentToken = token;\n                authenticateConnections(token);\n            }\n\n            @Override\n            public void onError(Exception reason) {\n                listener.onIdentityProviderError(reason);\n            }\n        }, true);\n    }\n\n    public void authenticateConnections(Token token) {\n        RedisCredentials credentialsFromToken = new TokenCredentials(token);\n        for (WeakReference<Connection> connectionRef : connections) {\n            Connection connection = connectionRef.get();\n            if (connection != null) {\n                connection.setCredentials(credentialsFromToken);\n            } else {\n                connections.remove(connectionRef);\n            }\n        }\n        postAuthenticateHooks.forEach(hook -> hook.accept(token));\n    }\n\n    public Connection addConnection(Connection connection) {\n        connections.add(new WeakReference<>(connection));\n        return connection;\n    }\n\n    public void stop() {\n        tokenManager.stop();\n    }\n\n    public void setListener(AuthXEventListener listener) {\n        if (listener != null) {\n            this.listener = listener;\n        }\n    }\n\n    public void addPostAuthenticationHook(Consumer<Token> postAuthenticateHook) {\n        postAuthenticateHooks.add(postAuthenticateHook);\n    }\n\n    public void removePostAuthenticationHook(Consumer<Token> postAuthenticateHook) {\n        postAuthenticateHooks.remove(postAuthenticateHook);\n    }\n\n    public AuthXEventListener getListener() {\n        return listener;\n    }\n\n    @Override\n    public RedisCredentials get() {\n        return new TokenCredentials(this.currentToken);\n    }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/authentication/JedisAuthenticationException.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport redis.clients.jedis.exceptions.JedisException;\n\npublic class JedisAuthenticationException extends JedisException {\n\n    public JedisAuthenticationException(String message) {\n        super(message);\n    }\n\n    public JedisAuthenticationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/authentication/TokenCredentials.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport redis.clients.authentication.core.Token;\nimport redis.clients.jedis.RedisCredentials;\n\nclass TokenCredentials implements RedisCredentials {\n    private final String user;\n    private final char[] password;\n\n    public TokenCredentials(Token token) {\n        user = token.getUser();\n        password = token.getValue().toCharArray();\n    }\n\n    @Override\n    public String getUser() {\n        return user;\n    }\n\n    @Override\n    public char[] getPassword() {\n        return password;\n    }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/BFInsertParams.java",
    "content": "package redis.clients.jedis.bloom;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.CAPACITY;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.ERROR;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.EXPANSION;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.NOCREATE;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.NONSCALING;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n// [CAPACITY {cap}] [ERROR {error}] [EXPANSION {expansion}] [NOCREATE] [NONSCALING]\npublic class BFInsertParams implements IParams {\n\n  private Long capacity;\n  private Double errorRate;\n  private Integer expansion;\n  private boolean noCreate = false;\n  private boolean nonScaling = false;\n\n  public static BFInsertParams insertParams() {\n    return new BFInsertParams();\n  }\n\n  public BFInsertParams capacity(long capacity) {\n    this.capacity = capacity;\n    return this;\n  }\n\n  public BFInsertParams error(double errorRate) {\n    this.errorRate = errorRate;\n    return this;\n  }\n\n  public BFInsertParams expansion(int expansion) {\n    this.expansion = expansion;\n    return this;\n  }\n\n  public BFInsertParams noCreate() {\n    this.noCreate = true;\n    return this;\n  }\n\n  public BFInsertParams nonScaling() {\n    this.nonScaling = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (capacity != null) {\n      args.add(CAPACITY).add(toByteArray(capacity));\n    }\n    if (errorRate != null) {\n      args.add(ERROR).add(toByteArray(errorRate));\n    }\n    if (expansion != null) {\n      args.add(EXPANSION).add(toByteArray(expansion));\n    }\n    if (noCreate) {\n      args.add(NOCREATE);\n    }\n    if (nonScaling) {\n      args.add(NONSCALING);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/BFReserveParams.java",
    "content": "package redis.clients.jedis.bloom;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.EXPANSION;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.NONSCALING;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\npublic class BFReserveParams implements IParams {\n\n  private Integer expansion;\n  private boolean nonScaling = false;\n\n  public static BFReserveParams reserveParams() {\n    return new BFReserveParams();\n  }\n\n  public BFReserveParams expansion(int expansion) {\n    this.expansion = expansion;\n    return this;\n  }\n\n  public BFReserveParams nonScaling() {\n    this.nonScaling = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (expansion != null) {\n      args.add(EXPANSION).add(toByteArray(expansion));\n    }\n    if (nonScaling) {\n      args.add(NONSCALING);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/CFInsertParams.java",
    "content": "package redis.clients.jedis.bloom;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.CAPACITY;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.NOCREATE;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n// [CAPACITY {capacity}] [NOCREATE]\npublic class CFInsertParams implements IParams {\n\n  private Long capacity;\n  private boolean noCreate = false;\n\n  public static CFInsertParams insertParams() {\n    return new CFInsertParams();\n  }\n\n  public CFInsertParams capacity(long capacity) {\n    this.capacity = capacity;\n    return this;\n  }\n\n  public CFInsertParams noCreate() {\n    this.noCreate = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (capacity != null) {\n      args.add(CAPACITY).add(toByteArray(capacity));\n    }\n    if (noCreate) {\n      args.add(NOCREATE);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/CFReserveParams.java",
    "content": "package redis.clients.jedis.bloom;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.BUCKETSIZE;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.EXPANSION;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.MAXITERATIONS;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n// [BUCKETSIZE {bucketsize}] [MAXITERATIONS {maxiterations}] [EXPANSION {expansion}]\npublic class CFReserveParams implements IParams {\n\n  private Long bucketSize;\n  private Integer maxIterations;\n  private Integer expansion;\n\n  public static CFReserveParams reserveParams() {\n    return new CFReserveParams();\n  }\n\n  public CFReserveParams bucketSize(long bucketSize) {\n    this.bucketSize = bucketSize;\n    return this;\n  }\n\n  public CFReserveParams maxIterations(int maxIterations) {\n    this.maxIterations = maxIterations;\n    return this;\n  }\n\n  public CFReserveParams expansion(int expansion) {\n    this.expansion = expansion;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (bucketSize != null) {\n      args.add(BUCKETSIZE).add(toByteArray(bucketSize));\n    }\n    if (maxIterations != null) {\n      args.add(MAXITERATIONS).add(toByteArray(maxIterations));\n    }\n    if (expansion != null) {\n      args.add(EXPANSION).add(toByteArray(expansion));\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/RedisBloomProtocol.java",
    "content": "package redis.clients.jedis.bloom;\n\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class RedisBloomProtocol {\n\n  public enum BloomFilterCommand implements ProtocolCommand {\n\n    RESERVE(\"BF.RESERVE\"),\n    ADD(\"BF.ADD\"),\n    MADD(\"BF.MADD\"),\n    EXISTS(\"BF.EXISTS\"),\n    MEXISTS(\"BF.MEXISTS\"),\n    INSERT(\"BF.INSERT\"),\n    SCANDUMP(\"BF.SCANDUMP\"),\n    LOADCHUNK(\"BF.LOADCHUNK\"),\n    CARD(\"BF.CARD\"),\n    INFO(\"BF.INFO\");\n\n    private final byte[] raw;\n\n    private BloomFilterCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum CuckooFilterCommand implements ProtocolCommand {\n\n    RESERVE(\"CF.RESERVE\"), //\n    ADD(\"CF.ADD\"), //\n    ADDNX(\"CF.ADDNX\"), //\n    INSERT(\"CF.INSERT\"), //\n    INSERTNX(\"CF.INSERTNX\"), //\n    EXISTS(\"CF.EXISTS\"), //\n    MEXISTS(\"CF.MEXISTS\"), //\n    DEL(\"CF.DEL\"), //\n    COUNT(\"CF.COUNT\"), //\n    SCANDUMP(\"CF.SCANDUMP\"), //\n    LOADCHUNK(\"CF.LOADCHUNK\"), //\n    INFO(\"CF.INFO\");\n\n    private final byte[] raw;\n\n    private CuckooFilterCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum CountMinSketchCommand implements ProtocolCommand {\n\n    INITBYDIM(\"CMS.INITBYDIM\"), //\n    INITBYPROB(\"CMS.INITBYPROB\"), //\n    INCRBY(\"CMS.INCRBY\"), //\n    QUERY(\"CMS.QUERY\"), //\n    MERGE(\"CMS.MERGE\"), //\n    INFO(\"CMS.INFO\");\n\n    private final byte[] raw;\n\n    private CountMinSketchCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum TopKCommand implements ProtocolCommand {\n\n    RESERVE(\"TOPK.RESERVE\"),\n    ADD(\"TOPK.ADD\"),\n    INCRBY(\"TOPK.INCRBY\"),\n    QUERY(\"TOPK.QUERY\"),\n    LIST(\"TOPK.LIST\"),\n    INFO(\"TOPK.INFO\");\n\n    private final byte[] raw;\n\n    private TopKCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum TDigestCommand implements ProtocolCommand {\n\n    CREATE, INFO, ADD, RESET, MERGE, CDF, QUANTILE, MIN, MAX, TRIMMED_MEAN,\n    RANK, REVRANK, BYRANK, BYREVRANK;\n\n    private final byte[] raw;\n\n    private TDigestCommand() {\n      raw = SafeEncoder.encode(\"TDIGEST.\" + name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum RedisBloomKeyword implements Rawable {\n\n    CAPACITY, ERROR, NOCREATE, EXPANSION, NONSCALING, BUCKETSIZE, MAXITERATIONS, ITEMS, WEIGHTS,\n    COMPRESSION, OVERRIDE, WITHCOUNT;\n\n    private final byte[] raw;\n\n    private RedisBloomKeyword() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/TDigestMergeParams.java",
    "content": "package redis.clients.jedis.bloom;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.COMPRESSION;\nimport static redis.clients.jedis.bloom.RedisBloomProtocol.RedisBloomKeyword.OVERRIDE;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\npublic class TDigestMergeParams implements IParams {\n\n  private Integer compression;\n  private boolean override = false;\n\n  public static TDigestMergeParams mergeParams() {\n    return new TDigestMergeParams();\n  }\n\n  public TDigestMergeParams compression(int compression) {\n    this.compression = compression;\n    return this;\n  }\n\n  public TDigestMergeParams override() {\n    this.override = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (compression != null) {\n      args.add(COMPRESSION).add(toByteArray(compression));\n    }\n    if (override) {\n      args.add(OVERRIDE);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/BloomFilterCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.bloom.BFInsertParams;\nimport redis.clients.jedis.bloom.BFReserveParams;\n\npublic interface BloomFilterCommands {\n\n  /**\n   * {@code BF.RESERVE {key} {error_rate} {capacity}}\n   *\n   * @param key\n   * @param errorRate\n   * @param capacity\n   * @return OK\n   */\n  String bfReserve(String key, double errorRate, long capacity);\n\n  /**\n   * {@code BF.RESERVE {key} {error_rate} {capacity} [EXPANSION {expansion}] [NONSCALING]}\n   *\n   * @param key\n   * @param errorRate\n   * @param capacity\n   * @param reserveParams\n   * @return OK\n   */\n  String bfReserve(String key, double errorRate, long capacity, BFReserveParams reserveParams);\n\n  /**\n   * {@code BF.ADD {key} {item}}\n   *\n   * @param key\n   * @param item\n   */\n  boolean bfAdd(String key, String item);\n\n  /**\n   * {@code BF.MADD {key} {item ...}}\n   *\n   * @param key\n   * @param items\n   */\n  List<Boolean> bfMAdd(String key, String... items);\n\n  /**\n   * {@code BF.INSERT {key} ITEMS {item ...}}\n   *\n   * @param key\n   * @param items\n   */\n  List<Boolean> bfInsert(String key, String... items);\n\n  /**\n   * {@code BF.INSERT {key} [CAPACITY {cap}] [ERROR {error}] [EXPANSION {expansion}] [NOCREATE]\n   * [NONSCALING] ITEMS {item ...}}\n   *\n   * @param key\n   * @param insertParams\n   * @param items\n   */\n  List<Boolean> bfInsert(String key, BFInsertParams insertParams, String... items);\n\n  /**\n   * {@code BF.EXISTS {key} {item}}\n   *\n   * @param key\n   * @param item\n   * @return if the item may exist\n   */\n  boolean bfExists(String key, String item);\n\n  /**\n   * {@code BF.MEXISTS {key} {item ...}}\n   *\n   * @param key\n   * @param items\n   */\n  List<Boolean> bfMExists(String key, String... items);\n\n  /**\n   * {@code BF.SCANDUMP {key} {iterator}}\n   *\n   * @param key\n   * @param iterator\n   * @return Pair of next iterator and current data\n   */\n  Map.Entry<Long, byte[]> bfScanDump(String key, long iterator);\n\n  /**\n   * {@code BF.LOADCHUNK {key} {iterator} {data}}\n   *\n   * @param key\n   * @param iterator\n   * @param data\n   * @return OK\n   */\n  String bfLoadChunk(String key, long iterator, byte[] data);\n\n  long bfCard(String key);\n\n  Map<String, Object> bfInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/BloomFilterPipelineCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.bloom.BFInsertParams;\nimport redis.clients.jedis.bloom.BFReserveParams;\n\npublic interface BloomFilterPipelineCommands {\n\n  Response<String> bfReserve(String key, double errorRate, long capacity);\n\n  Response<String> bfReserve(String key, double errorRate, long capacity, BFReserveParams reserveParams);\n\n  Response<Boolean> bfAdd(String key, String item);\n\n  Response<List<Boolean>> bfMAdd(String key, String... items);\n\n  Response<List<Boolean>> bfInsert(String key, String... items);\n\n  Response<List<Boolean>> bfInsert(String key, BFInsertParams insertParams, String... items);\n\n  Response<Boolean> bfExists(String key, String item);\n\n  Response<List<Boolean>> bfMExists(String key, String... items);\n\n  Response<Map.Entry<Long, byte[]>> bfScanDump(String key, long iterator);\n\n  Response<String> bfLoadChunk(String key, long iterator, byte[] data);\n\n  Response<Long> bfCard(String key);\n\n  Response<Map<String, Object>> bfInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/CountMinSketchCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Interface for RedisBloom Count-Min Sketch Commands\n * \n * @see <a href=\n *      \"https://oss.redislabs.com/redisbloom/CountMinSketch_Commands/\">RedisBloom\n *      Count-Min Sketch Documentation</a>\n */\npublic interface CountMinSketchCommands {\n  /**\n   * CMS.INITBYDIM Initializes a Count-Min Sketch to dimensions specified by user.\n   * \n   * @param key   The name of the sketch\n   * @param width Number of counter in each array. Reduces the error size\n   * @param depth Number of counter-arrays. Reduces the probability for an error\n   *              of a certain size (percentage of total count\n   * @return OK\n   */\n  String cmsInitByDim(String key, long width, long depth);\n\n  /**\n   * CMS.INITBYPROB Initializes a Count-Min Sketch to accommodate requested\n   * capacity.\n   * \n   * @param key         The name of the sketch.\n   * @param error       Estimate size of error. The error is a percent of total\n   *                    counted items. This effects the width of the sketch.\n   * @param probability The desired probability for inflated count. This should be\n   *                    a decimal value between 0 and 1. This effects the depth of\n   *                    the sketch. For example, for a desired false positive rate\n   *                    of 0.1% (1 in 1000), error_rate should be set to 0.001.\n   *                    The closer this number is to zero, the greater the memory\n   *                    consumption per item and the more CPU usage per operation.\n   * @return OK\n   */\n  String cmsInitByProb(String key, double error, double probability);\n\n  /**\n   * CMS.INCRBY Increases the count of item by increment\n   * \n   * @param key       The name of the sketch\n   * @param item      The item which counter to be increased\n   * @param increment Counter to be increased by this integer\n   * @return Count for the item after increment\n   */\n  // long cmsIncrBy(String key, String item, long increment);\n  default long cmsIncrBy(String key, String item, long increment) {\n    return cmsIncrBy(key, java.util.Collections.singletonMap(item, increment)).get(0);\n  }\n\n  /**\n   * CMS.INCRBY Increases the count of one or more item.\n   * \n   * @param key            The name of the sketch\n   * @param itemIncrements a Map of the items to be increased and their integer\n   *                       increment\n   * @return Count of each item after increment\n   */\n  List<Long> cmsIncrBy(String key, Map<String, Long> itemIncrements);\n\n  /**\n   * CMS.QUERY Returns count for item. Multiple items can be queried with one\n   * call.\n   * \n   * @param key   The name of the sketch\n   * @param items The items for which to retrieve the counts\n   * @return Count for one or more items\n   */\n  List<Long> cmsQuery(String key, String... items);\n\n  /**\n   * CMS.MERGE Merges several sketches into one sketch. All sketches must have\n   * identical width and depth.\n   * \n   * @param destKey The name of destination sketch. Must be initialized.\n   * @param keys    The sketches to be merged\n   * @return OK\n   */\n  String cmsMerge(String destKey, String... keys);\n\n  /**\n   * CMS.MERGE Merges several sketches into one sketch. All sketches must have\n   * identical width and depth. Weights can be used to multiply certain sketches.\n   * Default weight is 1.\n   * \n   * @param destKey        The name of destination sketch. Must be initialized.\n   * @param keysAndWeights A map of keys and weights used to multiply the sketch.\n   * @return OK\n   */\n  String cmsMerge(String destKey, Map<String, Long> keysAndWeights);\n\n  /**\n   * CMS.INFO Returns width, depth and total count of the sketch.\n   * \n   * @param key The name of the sketch\n   * @return A Map with width, depth and total count.\n   */\n  Map<String, Object> cmsInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/CountMinSketchPipelineCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Response;\n\npublic interface CountMinSketchPipelineCommands {\n\n  Response<String> cmsInitByDim(String key, long width, long depth);\n\n  Response<String> cmsInitByProb(String key, double error, double probability);\n\n  Response<List<Long>> cmsIncrBy(String key, Map<String, Long> itemIncrements);\n\n  Response<List<Long>> cmsQuery(String key, String... items);\n\n  Response<String> cmsMerge(String destKey, String... keys);\n\n  Response<String> cmsMerge(String destKey, Map<String, Long> keysAndWeights);\n\n  Response<Map<String, Object>> cmsInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/CuckooFilterCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.bloom.CFInsertParams;\nimport redis.clients.jedis.bloom.CFReserveParams;\n\n/**\n * Interface for RedisBloom Cuckoo Filter Commands\n *\n * @see <a href=\n *      \"https://oss.redislabs.com/redisbloom/Cuckoo_Commands/\">RedisBloom\n *      Cuckoo Filter Documentation</a>\n */\npublic interface CuckooFilterCommands {\n\n  /**\n   * CF.RESERVE Creates a Cuckoo Filter under key with the given parameters\n   *\n   * @param key The name of the filter\n   * @param capacity\n   * @return OK\n   */\n  String cfReserve(String key, long capacity);\n\n  /**\n   * CF.RESERVE Creates a Cuckoo Filter under key with the given parameters\n   *\n   * @param key The name of the filter\n   * @param capacity\n   * @param reserveParams An instance of CFReserveParams containing the options\n   * @return OK\n   */\n  String cfReserve(String key, long capacity, CFReserveParams reserveParams);\n\n  /**\n   * CF.ADD Adds an item to the cuckoo filter, creating the filter if it does not\n   * exist\n   *\n   * @param key  The name of the filter\n   * @param item The item to add\n   * @return true on success, false otherwise\n   */\n  boolean cfAdd(String key, String item);\n\n  /**\n   * CF.ADDNX Adds an item to the cuckoo filter, only if it does not exist yet\n   *\n   * @param key  The name of the filter\n   * @param item The item to add\n   * @return true if the item was added to the filter, false if the item already\n   *         exists.\n   */\n  boolean cfAddNx(String key, String item);\n\n  /**\n   * CF.INSERT Adds one or more items to a cuckoo filter, creating it if it does\n   * not exist yet.\n   *\n   * @param key   The name of the filter\n   * @param items One or more items to add\n   * @return true if the item was successfully inserted, false if an error\n   *         occurred\n   */\n  List<Boolean> cfInsert(String key, String... items);\n\n  /**\n   * CF.INSERT Adds one or more items to a cuckoo filter, using the passed\n   * options\n   *\n   * @param key     The name of the filter\n   * @param insertParams An instance of CFInsertParams containing the options\n   * @param items    One or more items to add\n   * @return true if the item was successfully inserted, false if an error\n   *         occurred\n   */\n  List<Boolean> cfInsert(String key, CFInsertParams insertParams, String... items);\n\n  /**\n   * CF.INSERTNX Adds one or more items to a cuckoo filter, only if it does not\n   * exist yet\n   *\n   * @param key   The name of the filter\n   * @param items One or more items to add\n   * @return true if the item was added to the filter, false if the item already\n   *         exists.\n   */\n  List<Boolean> cfInsertNx(String key, String... items);\n\n  /**\n   * CF.INSERTNX Adds one or more items to a cuckoo filter, using the passed\n   * options\n   *\n   * @param key     The name of the filter\n   * @param insertParams An instance of CFInsertParams containing the options\n   *                (CAPACITY/NOCREATE)\n   * @param items   One or more items to add\n   * @return true if the item was added to the filter, false if the item already\n   *         exists.\n   */\n  List<Boolean> cfInsertNx(String key, CFInsertParams insertParams, String... items);\n\n  /**\n   * CF.EXISTS Check if an item exists in a Cuckoo Filter\n   *\n   * @param key  The name of the filter\n   * @param item The item to check for\n   * @return false if the item certainly does not exist, true if the item may\n   *         exist.\n   */\n  boolean cfExists(String key, String item);\n\n  /**\n   * {@code CF.MEXISTS {key} {item ...}}\n   *\n   * @param key   The name of the filter\n   * @param items Items to check for (non empty sequence)\n   * @return a list of booleans where false if the item certainly does not exist,\n   *         true if the item may exist.\n   */\n  List<Boolean> cfMExists(String key, String... items);\n\n  /**\n   * CF.DEL Deletes an item once from the filter. If the item exists only once, it\n   * will be removed from the filter. If the item was added multiple times, it\n   * will still be present.\n   *\n   * @param key  The name of the filter\n   * @param item The item to delete from the filter\n   * @return true if the item has been deleted, false if the item was not found.\n   */\n  boolean cfDel(String key, String item);\n\n  /**\n   * CF.COUNT Returns the number of times an item may be in the filter.\n   *\n   * @param key  The name of the filter\n   * @param item The item to count\n   * @return The number of times the item exists in the filter\n   */\n  long cfCount(String key, String item);\n\n  /**\n   * CF.SCANDUMP Begins an incremental save of the cuckoo filter. This is useful\n   * for large cuckoo filters which cannot fit into the normal SAVE and RESTORE\n   * model.\n   *\n   * The Iterator is passed as input to the next invocation of SCANDUMP . If\n   * Iterator is 0, the iteration has completed.\n   *\n   * @param key      Name of the filter\n   * @param iterator This is either 0, or the iterator from a previous invocation\n   *                 of this command\n   * @return a Map.Entry containing the Iterator and Data.\n   */\n  Map.Entry<Long, byte[]> cfScanDump(String key, long iterator);\n//\n//  /**\n//   * CF.SCANDUMP Begins an incremental save of the cuckoo filter. This is useful\n//   * for large cuckoo filters which cannot fit into the normal SAVE and RESTORE\n//   * model.\n//   *\n//   * Returns an iterator over the Map.Entry containing the Iterator and Data in\n//   * proper sequence.\n//   *\n//   * @param key Name of the filter\n//   * @return An iterator over the Pair containing the chunks of byte[] representing the filter\n//   */\n//  Iterator<Map.Entry<Long, byte[]>> cfScanDumpIterator(String key);\n//\n//  /**\n//   * CF.SCANDUMP Begins an incremental save of the cuckoo filter. This is useful\n//   * for large cuckoo filters which cannot fit into the normal SAVE and RESTORE\n//   * model.\n//   *\n//   * Returns a sequential Stream with the Map.Entry containing the Iterator and\n//   * Data as its source.\n//   *\n//   * @return A sequential Stream of Pair of iterator and data\n//   */\n//  Stream<Map.Entry<Long, byte[]>> cfScanDumpStream(String key);\n\n  /**\n   * CF.LOADCHUNK Restores a filter previously saved using SCANDUMP.\n   *\n   * @param key       Name of the filter to restore\n   * @param iterator Iterator from CF.SCANDUMP\n   * @param data     Data from CF.SCANDUMP\n   * @return OK\n   */\n  String cfLoadChunk(String key, long iterator, byte[] data);\n//\n//  /**\n//   * CF.LOADCHUNK Restores a filter previously saved using SCANDUMP . See the\n//   * SCANDUMP command for example usage.\n//   *\n//   * @param key         Name of the filter to restore\n//   * @param iterAndData Pair of iterator and data\n//   */\n//  void cfLoadChunk(String key, Map.Entry<Long, byte[]> iterAndData);\n\n  /**\n   * CF.INFO Return information about filter\n   *\n   * @param key Name of the filter to restore\n   * @return A Map containing Size, Number of buckets, Number of filter, Number of\n   *         items inserted, Number of items deleted, Bucket size, Expansion rate,\n   *         Max iteration\n   */\n  Map<String, Object> cfInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/CuckooFilterPipelineCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.bloom.CFInsertParams;\nimport redis.clients.jedis.bloom.CFReserveParams;\n\npublic interface CuckooFilterPipelineCommands {\n\n  Response<String> cfReserve(String key, long capacity);\n\n  Response<String> cfReserve(String key, long capacity, CFReserveParams reserveParams);\n\n  Response<Boolean> cfAdd(String key, String item);\n\n  Response<Boolean> cfAddNx(String key, String item);\n\n  Response<List<Boolean>> cfInsert(String key, String... items);\n\n  Response<List<Boolean>> cfInsert(String key, CFInsertParams insertParams, String... items);\n\n  Response<List<Boolean>> cfInsertNx(String key, String... items);\n\n  Response<List<Boolean>> cfInsertNx(String key, CFInsertParams insertParams, String... items);\n\n  Response<Boolean> cfExists(String key, String item);\n\n  Response<List<Boolean>> cfMExists(String key, String... items);\n\n  Response<Boolean> cfDel(String key, String item);\n\n  Response<Long> cfCount(String key, String item);\n\n  Response<Map.Entry<Long, byte[]>> cfScanDump(String key, long iterator);\n\n  Response<String> cfLoadChunk(String key, long iterator, byte[] data);\n\n  Response<Map<String, Object>> cfInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/RedisBloomCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\npublic interface RedisBloomCommands extends BloomFilterCommands, CuckooFilterCommands,\n    CountMinSketchCommands, TopKFilterCommands, TDigestSketchCommands {\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/RedisBloomPipelineCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\npublic interface RedisBloomPipelineCommands extends BloomFilterPipelineCommands,\n    CuckooFilterPipelineCommands, CountMinSketchPipelineCommands, TopKFilterPipelineCommands,\n    TDigestSketchPipelineCommands {\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/TDigestSketchCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.bloom.TDigestMergeParams;\n\npublic interface TDigestSketchCommands {\n\n  /**\n   * {@code TDIGEST.CREATE key}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @return OK\n   */\n  String tdigestCreate(String key);\n\n  /**\n   * {@code TDIGEST.CREATE key [compression]}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @param compression The compression parameter. 100 is a common value for normal uses. 1000 is extremely large.\n   * @return OK\n   */\n  String tdigestCreate(String key, int compression);\n\n  /**\n   * {@code TDIGEST.RESET key}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @return OK\n   */\n  String tdigestReset(String key);\n\n  /**\n   * {@code TDIGEST.MERGE destination-key numkeys source-key [source-key ...]}\n   *\n   * @param destinationKey Sketch to copy observation values to (a t-digest data structure)\n   * @param sourceKeys Sketch(es) to copy observation values from (a t-digest data structure)\n   * @return OK\n   */\n  String tdigestMerge(String destinationKey, String... sourceKeys);\n\n  /**\n   * {@code TDIGEST.MERGE destination-key numkeys source-key [source-key ...]\n   * [COMPRESSION compression] [OVERRIDE]}\n   *\n   * @param mergeParams compression and override options\n   * @param destinationKey Sketch to copy observation values to (a t-digest data structure)\n   * @param sourceKeys Sketch(es) to copy observation values from (a t-digest data structure)\n   * @return OK\n   */\n  String tdigestMerge(TDigestMergeParams mergeParams, String destinationKey, String... sourceKeys);\n\n  /**\n   * {@code TDIGEST.INFO key}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @return information about the sketch\n   */\n  Map<String, Object> tdigestInfo(String key);\n\n  /**\n   * {@code TDIGEST.ADD key value weight [ value weight ...]}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @param values The value of the observation (floating-point)\n   * @return OK\n   */\n  String tdigestAdd(String key, double... values);\n\n  /**\n   * {@code TDIGEST.CDF key value [value ...]}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @param values upper limit of observation value, for which the fraction of all observations added which are &le; value\n   * @return estimation of the fraction of all observations added which are &le; value\n   */\n  List<Double> tdigestCDF(String key, double... values);\n\n  /**\n   * {@code TDIGEST.QUANTILE key quantile [quantile ...]}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @param quantiles The desired fraction(s) (between 0 and 1 inclusively)\n   * @return results\n   */\n  List<Double> tdigestQuantile(String key, double... quantiles);\n\n  /**\n   * {@code TDIGEST.MIN key}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @return minimum observation value from the sketch\n   */\n  double tdigestMin(String key);\n\n  /**\n   * {@code TDIGEST.MAX key}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @return maximum observation value from the sketch\n   */\n  double tdigestMax(String key);\n\n  /**\n   * {@code TDIGEST.TRIMMED_MEAN key low_cut_quantile high_cut_quantile}\n   *\n   * @param key The name of the sketch (a t-digest data structure)\n   * @param lowCutQuantile Exclude observation values lower than this quantile\n   * @param highCutQuantile Exclude observation values higher than this quantile\n   * @return estimation of the mean value\n   */\n  double tdigestTrimmedMean(String key, double lowCutQuantile, double highCutQuantile);\n\n  List<Long> tdigestRank(String key, double... values);\n\n  List<Long> tdigestRevRank(String key, double... values);\n\n  List<Double> tdigestByRank(String key, long... ranks);\n\n  List<Double> tdigestByRevRank(String key, long... ranks);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/TDigestSketchPipelineCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.bloom.TDigestMergeParams;\n\npublic interface TDigestSketchPipelineCommands {\n\n  Response<String> tdigestCreate(String key);\n\n  Response<String> tdigestCreate(String key, int compression);\n\n  Response<String> tdigestReset(String key);\n\n  Response<String> tdigestMerge(String destinationKey, String... sourceKeys);\n\n  Response<String> tdigestMerge(TDigestMergeParams mergeParams, String destinationKey, String... sourceKeys);\n\n  Response<Map<String, Object>> tdigestInfo(String key);\n\n  Response<String> tdigestAdd(String key, double... values);\n\n  Response<List<Double>> tdigestCDF(String key, double... values);\n\n  Response<List<Double>> tdigestQuantile(String key, double... quantiles);\n\n  Response<Double> tdigestMin(String key);\n\n  Response<Double> tdigestMax(String key);\n\n  Response<Double> tdigestTrimmedMean(String key, double lowCutQuantile, double highCutQuantile);\n\n  Response<List<Long>> tdigestRank(String key, double... values);\n\n  Response<List<Long>> tdigestRevRank(String key, double... values);\n\n  Response<List<Double>> tdigestByRank(String key, long... ranks);\n\n  Response<List<Double>> tdigestByRevRank(String key, long... ranks);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/TopKFilterCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\npublic interface TopKFilterCommands {\n\n  /**\n   * {@code TOPK.RESERVE {key} {topk}}\n   *\n   * @param key\n   * @param topk\n   * @return OK\n   */\n  String topkReserve(String key, long topk);\n\n  /**\n   * {@code TOPK.RESERVE {key} {topk} [{width} {depth} {decay}]}\n   *\n   * @param key\n   * @param topk\n   * @param width\n   * @param depth\n   * @param decay\n   * @return OK\n   */\n  String topkReserve(String key, long topk, long width, long depth, double decay);\n\n  /**\n   * {@code TOPK.ADD {key} {item ...}}\n   *\n   * @param key\n   * @param items\n   * @return items dropped from list\n   */\n  List<String> topkAdd(String key, String... items);\n\n  /**\n   * {@code TOPK.INCRBY {key} {item} {increment}}\n   *\n   * @param key\n   * @param item\n   * @param increment\n   * @return item dropped from list\n   */\n  default String topkIncrBy(String key, String item, long increment) {\n    return topkIncrBy(key, Collections.singletonMap(item, increment)).get(0);\n  }\n\n  /**\n   * {@code TOPK.INCRBY {key} {item} {increment} [{item} {increment} ...]}\n   *\n   * @param key\n   * @param itemIncrements item and increment pairs\n   * @return item dropped from list\n   */\n  List<String> topkIncrBy(String key, Map<String, Long> itemIncrements);\n\n  /**\n   * {@code TOPK.QUERY {key} {item ...}}\n   *\n   * @param key\n   * @param items\n   * @return if item is in Top-K\n   */\n  List<Boolean> topkQuery(String key, String... items);\n\n  /**\n   * {@code TOPK.LIST {key}}\n   *\n   * @param key\n   * @return k (or less) items in Top K list\n   */\n  List<String> topkList(String key);\n\n  /**\n   * {@code TOPK.LIST {key} WITHCOUNT}\n   *\n   * @param key\n   * @return k (or less) items in Top K list\n   */\n  Map<String, Long> topkListWithCount(String key);\n\n  /**\n   * {@code TOPK.INFO {key}}\n   *\n   * @param key\n   * @return information\n   */\n  Map<String, Object> topkInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/TopKFilterPipelineCommands.java",
    "content": "package redis.clients.jedis.bloom.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Response;\n\npublic interface TopKFilterPipelineCommands {\n\n  Response<String> topkReserve(String key, long topk);\n\n  Response<String> topkReserve(String key, long topk, long width, long depth, double decay);\n\n  Response<List<String>> topkAdd(String key, String... items);\n\n  Response<List<String>> topkIncrBy(String key, Map<String, Long> itemIncrements);\n\n  Response<List<Boolean>> topkQuery(String key, String... items);\n\n  Response<List<String>> topkList(String key);\n\n  Response<Map<String, Long>> topkListWithCount(String key);\n\n  Response<Map<String, Object>> topkInfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/commands/package-info.java",
    "content": "/**\n * This package contains the interfaces that contain methods representing RedisBloom commands.\n */\npackage redis.clients.jedis.bloom.commands;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/bloom/package-info.java",
    "content": "/**\n * This package contains the classes related to RedisBloom module.\n */\npackage redis.clients.jedis.bloom;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/builders/AbstractClientBuilder.java",
    "content": "package redis.clients.jedis.builders;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.csc.CacheFactory;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.executors.DefaultCommandExecutor;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.search.SearchProtocol;\n\n/**\n * Abstract base class for Redis client builders that provides common configuration options.\n * <p>\n * This class contains shared configuration fields and methods that are common across different\n * Redis client builders (RedisClient.Builder, RedisSentinelClient.Builder, etc.). It helps\n * eliminate code duplication and provides a consistent API for common features.\n * <p>\n * Common features provided:\n * <ul>\n * <li>Connection pool configuration</li>\n * <li>Client-side caching</li>\n * <li>Custom connection providers</li>\n * <li>Key preprocessing</li>\n * <li>JSON object mapping</li>\n * <li>Search dialect configuration</li>\n * </ul>\n * @param <T> the concrete builder type for method chaining\n * @param <C> the client type that this builder creates\n */\npublic abstract class AbstractClientBuilder<T extends AbstractClientBuilder<T, C>, C> {\n\n  // Common configuration fields\n  protected GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n  protected Cache cache = null;\n  protected CacheConfig cacheConfig = null;\n  protected CommandExecutor commandExecutor = null;\n  protected CommandObjects commandObjects = null;\n  protected ConnectionProvider connectionProvider = null;\n  protected CommandKeyArgumentPreProcessor keyPreProcessor = null;\n  protected JsonObjectMapper jsonObjectMapper = null;\n  protected int searchDialect = SearchProtocol.DEFAULT_DIALECT;\n\n  protected JedisClientConfig clientConfig = null;\n\n  /**\n   * Sets the client configuration for Redis connections.\n   * <p>\n   * The client configuration includes authentication, timeouts, SSL settings, and other\n   * connection-specific parameters.\n   * @param clientConfig the client configuration\n   * @return this builder\n   */\n  public T clientConfig(JedisClientConfig clientConfig) {\n    this.clientConfig = clientConfig;\n    return self();\n  }\n\n  /**\n   * Returns the concrete builder instance for method chaining. This method must be implemented by\n   * subclasses to return their own type.\n   * @return the concrete builder instance\n   */\n  protected abstract T self();\n\n  /**\n   * Creates a default connection provider based on the current configuration.\n   * @return ConnectionProvider\n   */\n  protected abstract ConnectionProvider createDefaultConnectionProvider();\n\n  /**\n   * Creates a default command executor based on the current configuration.\n   * @return CommandExecutor\n   */\n  protected CommandExecutor createDefaultCommandExecutor() {\n    return new DefaultCommandExecutor(this.connectionProvider);\n  }\n\n  /**\n   * Creates a default client configuration based on the current configuration.\n   * @return JedisClientConfig\n   */\n  protected JedisClientConfig createDefaultClientConfig() {\n    return DefaultJedisClientConfig.builder().build();\n  }\n\n  /**\n   * Factory method for creating CommandObjects. Subclasses may override to provide specialized\n   * CommandObjects implementations (e.g., ClusterCommandObjects).\n   */\n  protected CommandObjects createDefaultCommandObjects() {\n    return new CommandObjects();\n  }\n\n  /**\n   * Creates the specific client instance with the provided components.\n   * <p>\n   * This method is called by the generic build() method to instantiate the concrete client type.\n   * Each builder implementation should create their specific client type (JedisPooled,\n   * JedisCluster, etc.) using the parameters provided to the builder.\n   * @return the configured Redis client instance\n   */\n  protected abstract C createClient();\n\n  /**\n   * Validates the builder-specific configuration.\n   * <p>\n   * This method is called by the generic build() method to validate configuration specific to each\n   * builder type. Implementations should call validateCommonConfiguration() and then perform their\n   * own specific validation.\n   * @throws IllegalArgumentException if the configuration is invalid\n   */\n  protected abstract void validateSpecificConfiguration();\n\n  /**\n   * Builds the Redis client instance using the common build pattern.\n   * <p>\n   * This method implements the common build pattern shared across all builder types:\n   * <ol>\n   * <li>Validates configuration (both common and builder-specific)</li>\n   * <li>Creates cache from cacheConfig if provided</li>\n   * <li>Creates default connection provider if not already set</li>\n   * <li>Creates default command executor if not already set</li>\n   * <li>Applies common configuration to command objects</li>\n   * <li>Creates and returns the specific client instance</li>\n   * </ol>\n   * @return the configured Redis client instance\n   */\n  public C build() {\n    // Validate configuration\n    validateSpecificConfiguration();\n\n    // Create cache from config if provided\n    if (this.cacheConfig != null) {\n      this.cache = CacheFactory.getCache(this.cacheConfig);\n    }\n\n    if (this.clientConfig == null) {\n      this.clientConfig = createDefaultClientConfig();\n    }\n\n    // Create default connection provider if not set\n    if (this.connectionProvider == null) {\n      this.connectionProvider = createDefaultConnectionProvider();\n    }\n\n    // Create default command executor if not set\n    if (this.commandExecutor == null) {\n      this.commandExecutor = createDefaultCommandExecutor();\n    }\n\n    // Ensure CommandObjects are created (and allow subclasses to override the type)\n    if (this.commandObjects == null) {\n      this.commandObjects = createDefaultCommandObjects();\n    }\n\n    // Apply common configuration\n    this.applyCommandObjectsConfiguration(commandObjects);\n\n    // Create and return the specific client instance\n    return createClient();\n  }\n\n  /**\n   * Sets the connection pool configuration.\n   * <p>\n   * The pool configuration controls how connections are managed, including maximum number of\n   * connections, idle timeout, and other pooling parameters.\n   * @param poolConfig the pool configuration\n   * @return this builder\n   */\n  public T poolConfig(GenericObjectPoolConfig<Connection> poolConfig) {\n    this.poolConfig = poolConfig;\n    return self();\n  }\n\n  /**\n   * Sets the client-side cache for caching Redis responses.\n   * <p>\n   * Client-side caching can improve performance by storing frequently accessed data locally,\n   * reducing the number of round trips to the Redis server.\n   * @param cache the cache instance\n   * @return this builder\n   */\n  public T cache(Cache cache) {\n    this.cache = cache;\n    return self();\n  }\n\n  /**\n   * Sets the cache configuration for client-side caching.\n   * <p>\n   * Client-side caching can improve performance by storing frequently accessed data locally. The\n   * cache will be created from this configuration during the build process.\n   * @param cacheConfig the cache configuration\n   * @return this builder\n   */\n  public T cacheConfig(CacheConfig cacheConfig) {\n    this.cacheConfig = cacheConfig;\n    return self();\n  }\n\n  /**\n   * Sets a custom connection provider.\n   * <p>\n   * When a custom connection provider is set, other connection-related configuration may be ignored\n   * as the provider is responsible for managing connections. The specific behavior depends on the\n   * concrete builder implementation.\n   * @param connectionProvider the connection provider\n   * @return this builder\n   */\n  public T connectionProvider(ConnectionProvider connectionProvider) {\n    this.connectionProvider = connectionProvider;\n    return self();\n  }\n\n  /**\n   * Sets a custom command executor for executing Redis commands.\n   * <p>\n   * The command executor is responsible for sending commands to Redis and processing the responses.\n   * @param commandExecutor the command executor\n   * @return this builder\n   */\n  public T commandExecutor(CommandExecutor commandExecutor) {\n    this.commandExecutor = commandExecutor;\n    return self();\n  }\n\n  /**\n   * Sets a key preprocessor for transforming Redis keys before sending commands.\n   * <p>\n   * The key preprocessor allows you to modify keys before they are sent to Redis, for example to\n   * add prefixes, apply transformations, or implement key routing logic.\n   * <p>\n   * Example usage:\n   *\n   * <pre>\n   * {@code\n   * CommandKeyArgumentPreProcessor keyProcessor = key -> \"myapp:\" + key;\n   * builder.keyPreProcessor(keyProcessor);\n   * }\n   * </pre>\n   *\n   * @param keyPreProcessor the key preprocessor\n   * @return this builder\n   */\n  public T keyPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) {\n    this.keyPreProcessor = keyPreProcessor;\n    return self();\n  }\n\n  /**\n   * Sets a custom JSON object mapper for JSON operations.\n   * <p>\n   * The JSON object mapper is used for serializing and deserializing objects in JSON commands\n   * (RedisJSON module). If not set, a default Gson-based mapper will be used.\n   * <p>\n   * Example usage:\n   *\n   * <pre>\n   * {\n   *   &#64;code\n   *   JsonObjectMapper customMapper = new MyCustomJsonMapper();\n   *   builder.jsonObjectMapper(customMapper);\n   * }\n   * </pre>\n   *\n   * @param jsonObjectMapper the JSON object mapper\n   * @return this builder\n   */\n  public T jsonObjectMapper(JsonObjectMapper jsonObjectMapper) {\n    this.jsonObjectMapper = jsonObjectMapper;\n    return self();\n  }\n\n  /**\n   * Sets the default search dialect for RediSearch operations.\n   * <p>\n   * The search dialect determines the query syntax and features available for RediSearch commands.\n   * Different dialects support different query features and syntax variations.\n   * <p>\n   * Default is {@value redis.clients.jedis.search.SearchProtocol#DEFAULT_DIALECT}.\n   * @param searchDialect the search dialect version\n   * @return this builder\n   * @throws IllegalArgumentException if dialect is 0 (not allowed)\n   */\n  public T searchDialect(int searchDialect) {\n    if (searchDialect == 0) {\n      throw new IllegalArgumentException(\"DIALECT=0 cannot be set.\");\n    }\n    this.searchDialect = searchDialect;\n    return self();\n  }\n\n  /**\n   * Applies common configuration to the CommandObjects instance.\n   * <p>\n   * This method is called by concrete builders to configure the CommandObjects with the common\n   * settings like key preprocessor, JSON mapper, and search dialect.\n   * @param commandObjects the CommandObjects instance to configure\n   */\n  public void applyCommandObjectsConfiguration(CommandObjects commandObjects) {\n    if (keyPreProcessor != null) {\n      commandObjects.setKeyArgumentPreProcessor(keyPreProcessor);\n    }\n\n    if (jsonObjectMapper != null) {\n      commandObjects.setJsonObjectMapper(jsonObjectMapper);\n    }\n\n    if (searchDialect != SearchProtocol.DEFAULT_DIALECT) {\n      commandObjects.setDefaultSearchDialect(searchDialect);\n    }\n  }\n\n  /**\n   * Validates common configuration parameters.\n   * <p>\n   * This method can be called by concrete builders to validate the common configuration before\n   * building the client.\n   * @throws IllegalArgumentException if any common configuration is invalid\n   */\n  protected void validateCommonConfiguration() {\n    if (cache != null || cacheConfig != null) {\n      if (clientConfig != null && clientConfig.getRedisProtocol() != RedisProtocol.RESP3) {\n        throw new IllegalArgumentException(\"Client-side caching is only supported with RESP3.\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/builders/ClusterClientBuilder.java",
    "content": "package redis.clients.jedis.builders;\n\nimport java.time.Duration;\nimport java.util.Set;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.executors.ClusterCommandExecutor;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n/**\n * Builder for creating JedisCluster instances (Redis Cluster connections).\n * <p>\n * This builder provides methods specific to Redis Cluster deployments, including cluster nodes\n * configuration, retry settings, and topology refresh configuration.\n * </p>\n */\npublic abstract class ClusterClientBuilder<C>\n    extends AbstractClientBuilder<ClusterClientBuilder<C>, C> {\n\n  // Cluster-specific configuration fields\n  private Set<HostAndPort> nodes = null;\n  private int maxAttempts = JedisCluster.DEFAULT_MAX_ATTEMPTS;\n  private Duration maxTotalRetriesDuration;\n  private Duration topologyRefreshPeriod = null;\n  private CommandFlagsRegistry commandFlags = null;\n\n  /**\n   * Sets the cluster nodes to connect to.\n   * <p>\n   * At least one node must be specified. The client will discover other nodes in the cluster\n   * automatically.\n   * @param nodes the set of cluster nodes\n   * @return this builder\n   */\n  public ClusterClientBuilder<C> nodes(Set<HostAndPort> nodes) {\n    this.nodes = nodes;\n    return this;\n  }\n\n  /**\n   * Sets the maximum number of attempts for cluster operations.\n   * <p>\n   * When a cluster operation fails (e.g., due to node failure or slot migration), the client will\n   * retry up to this many times before giving up.\n   * @param maxAttempts the maximum number of attempts (must be positive)\n   * @return this builder\n   */\n  public ClusterClientBuilder<C> maxAttempts(int maxAttempts) {\n    this.maxAttempts = maxAttempts;\n    return this;\n  }\n\n  /**\n   * Sets the maximum total duration for retries across all attempts.\n   * <p>\n   * This provides a time-based limit on retries in addition to the attempt-based limit. If not set,\n   * it will be calculated as socketTimeout * maxAttempts.\n   * @param maxTotalRetriesDuration the maximum total retry duration\n   * @return this builder\n   */\n  public ClusterClientBuilder<C> maxTotalRetriesDuration(Duration maxTotalRetriesDuration) {\n    this.maxTotalRetriesDuration = maxTotalRetriesDuration;\n    return this;\n  }\n\n  /**\n   * Sets the topology refresh period for cluster slot mapping updates.\n   * <p>\n   * The client will periodically refresh its view of the cluster topology to handle slot migrations\n   * and node changes. A shorter period provides faster adaptation to cluster changes but increases\n   * overhead.\n   * @param topologyRefreshPeriod the topology refresh period\n   * @return this builder\n   */\n  public ClusterClientBuilder<C> topologyRefreshPeriod(Duration topologyRefreshPeriod) {\n    this.topologyRefreshPeriod = topologyRefreshPeriod;\n    return this;\n  }\n\n  /**\n   * Overrides the default command flags registry.\n   * @param commandFlags custom command flags registry\n   * @return this builder\n   */\n  public ClusterClientBuilder<C> commandFlags(CommandFlagsRegistry commandFlags) {\n    this.commandFlags = commandFlags;\n    return this;\n  }\n\n  /**\n   * Gets the command flags registry, initializing it if necessary.\n   * @return the command flags registry\n   */\n  protected CommandFlagsRegistry getCommandFlags() {\n    if (this.commandFlags == null) {\n      this.commandFlags = createDefaultCommandFlagsRegistry();\n    }\n    return this.commandFlags;\n  }\n\n  @Override\n  protected ClusterClientBuilder<C> self() {\n    return this;\n  }\n\n  @Override\n  protected ConnectionProvider createDefaultConnectionProvider() {\n    return new ClusterConnectionProvider(this.nodes, this.clientConfig, this.cache, this.poolConfig,\n        this.topologyRefreshPeriod);\n  }\n\n  /**\n   * Creates a default command flags registry based on the current configuration.\n   * @return CommandFlagsRegistry\n   */\n  protected CommandFlagsRegistry createDefaultCommandFlagsRegistry() {\n    return StaticCommandFlagsRegistry.registry();\n  }\n\n  @Override\n  protected CommandExecutor createDefaultCommandExecutor() {\n    if (this.commandFlags == null) {\n      this.commandFlags = createDefaultCommandFlagsRegistry();\n    }\n\n    Duration effectiveMaxTotalRetriesDuration = (this.maxTotalRetriesDuration == null)\n        ? Duration.ofMillis((long) this.clientConfig.getSocketTimeoutMillis() * this.maxAttempts)\n        : this.maxTotalRetriesDuration;\n\n    return new ClusterCommandExecutor((ClusterConnectionProvider) this.connectionProvider,\n        this.maxAttempts, effectiveMaxTotalRetriesDuration, this.commandFlags);\n  }\n\n  @Override\n  protected CommandObjects createDefaultCommandObjects() {\n    return new ClusterCommandObjects();\n  }\n\n  @Override\n  protected void validateSpecificConfiguration() {\n    validateCommonConfiguration();\n\n    if (nodes == null || nodes.isEmpty()) {\n      throw new IllegalArgumentException(\n          \"At least one cluster node must be specified for cluster mode\");\n    }\n\n    if (maxAttempts <= 0) {\n      throw new IllegalArgumentException(\"Max attempts must be positive for cluster mode\");\n    }\n\n    if (maxTotalRetriesDuration != null && maxTotalRetriesDuration.isNegative()) {\n      throw new IllegalArgumentException(\n          \"Max total retries duration cannot be negative for cluster mode\");\n    }\n\n    if (topologyRefreshPeriod != null && topologyRefreshPeriod.isNegative()) {\n      throw new IllegalArgumentException(\n          \"Topology refresh period cannot be negative for cluster mode\");\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/builders/MultiDbClientBuilder.java",
    "content": "package redis.clients.jedis.builders;\n\nimport java.util.function.Consumer;\n\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.mcf.MultiDbCommandExecutor;\nimport redis.clients.jedis.mcf.DatabaseSwitchEvent;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n/**\n * Builder for creating multi-db Redis clients with multi-endpoint support.\n * <p>\n * This builder provides methods specific to multi-db Redis deployments, including multiple weighted\n * endpoints, circuit breaker configuration, health checks, and automatic failover/failback\n * capabilities.\n * </p>\n * <p>\n * <strong>Key Features:</strong>\n * </p>\n * <ul>\n * <li><strong>Multi-Endpoint Configuration:</strong> Add multiple Redis endpoints with individual\n * weights</li>\n * <li><strong>Circuit Breaker Integration:</strong> Built-in circuit breaker with configurable\n * thresholds</li>\n * <li><strong>Health Monitoring:</strong> Automatic health checks with configurable strategies</li>\n * <li><strong>Event Handling:</strong> Listen to database switch events for monitoring and\n * alerting</li>\n * <li><strong>Flexible Configuration:</strong> Support for both simple and advanced multi-database\n * configurations</li>\n * </ul>\n * <p>\n * <strong>Usage Examples:</strong>\n * </p>\n *\n * <pre>\n * MultiDbClient client = MultiDbClient.builder()\n *                 .multiDbConfig(\n *                         MultiDbConfig.builder()\n *                                 .database(\n *                                         DatabaseConfig.builder(\n *                                                         east,\n *                                                         DefaultJedisClientConfig.builder().credentials(credentialsEast).build())\n *                                                 .weight(100.0f)\n *                                                 .build())\n *                                 .database(DatabaseConfig.builder(\n *                                                 west,\n *                                                 DefaultJedisClientConfig.builder().credentials(credentialsWest).build())\n *                                         .weight(50.0f).build())\n *                                 .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n *                                         .failureRateThreshold(50.0f)\n *                                         .build())\n *                                 .commandRetry(MultiDbConfig.RetryConfig.builder()\n *                                         .maxAttempts(3)\n *                                         .build())\n *                                 .build()\n *                 )\n *                 .databaseSwitchListener(event -&gt;\n *                     System.out.println(\"Switched to: \" + event.getEndpoint()))\n *                 .build();\n * </pre>\n * \n * @param <C> the client type that this builder creates\n * @author Ivo Gaydazhiev\n * @since 7.0.0\n */\n@Experimental\npublic abstract class MultiDbClientBuilder<C>\n    extends AbstractClientBuilder<MultiDbClientBuilder<C>, C> {\n\n  // Multi-db specific configuration fields\n  private MultiDbConfig multiDbConfig = null;\n  private Consumer<DatabaseSwitchEvent> databaseSwitchListener = null;\n\n  /**\n   * Sets the multi-database configuration.\n   * <p>\n   * This configuration controls circuit breaker behavior, retry logic, health checks, failback\n   * settings, and other resilience features. If not provided, default configuration will be used.\n   * </p>\n   * @param config the multi-database configuration\n   * @return this builder\n   */\n  public MultiDbClientBuilder<C> multiDbConfig(MultiDbConfig config) {\n    this.multiDbConfig = config;\n    return this;\n  }\n\n  /**\n   * Sets a listener for database switch events.\n   * <p>\n   * The listener will be called whenever the client switches from one endpoint to another,\n   * providing information about the switch reason and the new active endpoint. This is useful for\n   * monitoring, alerting, and logging purposes.\n   * </p>\n   * @param listener the database switch event listener\n   * @return this builder\n   */\n  public MultiDbClientBuilder<C> databaseSwitchListener(Consumer<DatabaseSwitchEvent> listener) {\n    this.databaseSwitchListener = listener;\n    return this;\n  }\n\n  @Override\n  protected MultiDbClientBuilder<C> self() {\n    return this;\n  }\n\n  @Override\n  protected ConnectionProvider createDefaultConnectionProvider() {\n\n    if (this.multiDbConfig == null || this.multiDbConfig.getDatabaseConfigs() == null\n        || this.multiDbConfig.getDatabaseConfigs().length < 1) {\n      throw new IllegalArgumentException(\"At least one endpoint must be specified\");\n    }\n\n    // Create the multi-database connection provider\n    MultiDbConnectionProvider provider = new MultiDbConnectionProvider(multiDbConfig);\n\n    // Set database switch listener if provided\n    if (this.databaseSwitchListener != null) {\n      provider.setDatabaseSwitchListener(this.databaseSwitchListener);\n    }\n\n    return provider;\n  }\n\n  @Override\n  protected CommandExecutor createDefaultCommandExecutor() {\n    // For multi-db clients, we always use MultiDbCommandExecutor\n    return new MultiDbCommandExecutor((MultiDbConnectionProvider) this.connectionProvider);\n  }\n\n  @Override\n  protected void validateSpecificConfiguration() {\n\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/builders/SentinelClientBuilder.java",
    "content": "package redis.clients.jedis.builders;\n\nimport java.time.Duration;\nimport java.util.Set;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.SentineledConnectionProvider;\nimport redis.clients.jedis.util.Delay;\nimport redis.clients.jedis.util.JedisAsserts;\n\n/**\n * Builder for creating JedisSentineled instances (Redis Sentinel connections).\n * <p>\n * This builder provides methods specific to Redis Sentinel deployments, including master name\n * configuration, sentinel nodes configuration, and separate client configurations for master and\n * sentinel connections.\n * </p>\n */\npublic abstract class SentinelClientBuilder<C>\n    extends AbstractClientBuilder<SentinelClientBuilder<C>, C> {\n\n  // Sentinel-specific configuration fields\n  private String masterName = null;\n  private Set<HostAndPort> sentinels = null;\n  private JedisClientConfig sentinelClientConfig = null;\n\n  // delay between re-subscribing to sentinel nodes after a disconnection\n  private Delay sentinelReconnectDelay = SentineledConnectionProvider.DEFAULT_RESUBSCRIBE_DELAY;\n\n  /**\n   * Sets the master name for the Redis Sentinel configuration.\n   * <p>\n   * This is the name of the Redis master as configured in the Sentinel instances. The Sentinel will\n   * monitor this master and provide failover capabilities.\n   * @param masterName the master name (must not be null or empty)\n   * @return this builder\n   */\n  public SentinelClientBuilder<C> masterName(String masterName) {\n    this.masterName = masterName;\n    return this;\n  }\n\n  /**\n   * Sets the sentinel nodes to connect to.\n   * <p>\n   * At least one sentinel must be specified. The client will use these sentinels to discover the\n   * current master and monitor for failover events.\n   * @param sentinels the set of sentinel nodes\n   * @return this builder\n   */\n  public SentinelClientBuilder<C> sentinels(Set<HostAndPort> sentinels) {\n    this.sentinels = sentinels;\n    return this;\n  }\n\n  /**\n   * Sets the client configuration for Sentinel connections.\n   * <p>\n   * This configuration is used for connections to the Sentinel instances. It may have different\n   * authentication credentials and settings than the master connections.\n   * @param sentinelClientConfig the client configuration for sentinel connections\n   * @return this builder\n   */\n  public SentinelClientBuilder<C> sentinelClientConfig(JedisClientConfig sentinelClientConfig) {\n    this.sentinelClientConfig = sentinelClientConfig;\n    return this;\n  }\n\n  /**\n   * Sets the delay between re-subscribing to sentinel node after a disconnection.\n   * <p>\n   * In case connection to sentinel nodes is lost, the client will try to reconnect to them. This\n   * method sets the delay between re-subscribing to sentinel nodes after a disconnection.\n   * </p>\n   * @param reconnectDelay the delay between re-subscribing to sentinel nodes after a disconnection\n   * @return this builder\n   */\n  public SentinelClientBuilder<C> sentinelReconnectDelay(Delay reconnectDelay) {\n    JedisAsserts.notNull(reconnectDelay, \"reconnectDelay must not be null\");\n    this.sentinelReconnectDelay = reconnectDelay;\n    return this;\n  }\n\n  @Override\n  protected SentinelClientBuilder<C> self() {\n    return this;\n  }\n\n  @Override\n  protected ConnectionProvider createDefaultConnectionProvider() {\n    return new SentineledConnectionProvider(this.masterName, this.clientConfig, this.cache,\n        this.poolConfig, this.sentinels, this.sentinelClientConfig, sentinelReconnectDelay);\n  }\n\n  @Override\n  protected void validateSpecificConfiguration() {\n    validateCommonConfiguration();\n\n    if (masterName == null || masterName.trim().isEmpty()) {\n      throw new IllegalArgumentException(\"Master name is required for Sentinel mode\");\n    }\n\n    if (sentinels == null || sentinels.isEmpty()) {\n      throw new IllegalArgumentException(\n          \"At least one sentinel must be specified for Sentinel mode\");\n    }\n  }\n\n  @Override\n  public C build() {\n    if (sentinelClientConfig == null) {\n      sentinelClientConfig = DefaultJedisClientConfig.builder().build();\n    }\n\n    return super.build();\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/builders/StandaloneClientBuilder.java",
    "content": "package redis.clients.jedis.builders;\n\nimport java.net.URI;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\nimport redis.clients.jedis.util.JedisAsserts;\nimport redis.clients.jedis.util.JedisURIHelper;\n\n/**\n * Builder for creating RedisClient instances (standalone Redis connections).\n * <p>\n * This builder provides methods specific to standalone Redis deployments, including host/port\n * configuration, URI-based configuration, and client configuration options.\n * </p>\n */\npublic abstract class StandaloneClientBuilder<C>\n    extends AbstractClientBuilder<StandaloneClientBuilder<C>, C> {\n\n  // Standalone-specific configuration fields\n  private HostAndPort hostAndPort = new HostAndPort(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);\n\n  /**\n   * Sets the Redis server host and port.\n   * @param host the Redis server hostname\n   * @param port the Redis server port\n   * @return this builder\n   */\n  public StandaloneClientBuilder<C> hostAndPort(String host, int port) {\n    this.hostAndPort = new HostAndPort(host, port);\n    return this;\n  }\n\n  /**\n   * Sets the Redis server host and port.\n   * @param hostAndPort the Redis server host and port\n   * @return this builder\n   */\n  public StandaloneClientBuilder<C> hostAndPort(HostAndPort hostAndPort) {\n    this.hostAndPort = hostAndPort;\n    return this;\n  }\n\n  @Override\n  protected StandaloneClientBuilder<C> self() {\n    return this;\n  }\n\n  @Override\n  protected ConnectionProvider createDefaultConnectionProvider() {\n    return new PooledConnectionProvider(this.hostAndPort, this.clientConfig, this.cache,\n        this.poolConfig);\n  }\n\n  @Override\n  protected void validateSpecificConfiguration() {\n    validateCommonConfiguration();\n\n    if (hostAndPort == null) {\n      throw new IllegalArgumentException(\"Either URI or host/port must be specified\");\n    }\n  }\n\n  /**\n   * Sets the Redis server URI from a string.\n   * <p>\n   * This method extracts connection parameters from the URI and merges them into the current client\n   * configuration. If a client configuration was previously set via\n   * {@link #clientConfig(JedisClientConfig)}, only the values explicitly provided in the URI will\n   * override the existing configuration. Values not present in the URI will be preserved from the\n   * existing configuration.\n   * <p>\n   * <b>This method sets:</b>\n   * <ul>\n   * <li>Host and port from the URI (always set)</li>\n   * <li>Client configuration with URI-derived values (merged with existing config if present)</li>\n   * </ul>\n   * <p>\n   * <b>URI values override existing config values when:</b>\n   * <ul>\n   * <li>URI contains user/password - overrides existing credentials</li>\n   * <li>URI contains database - overrides existing database</li>\n   * <li>URI contains protocol parameter - overrides existing protocol</li>\n   * <li>URI uses rediss:// scheme - enables SSL</li>\n   * </ul>\n   * <p>\n   * <b>Examples:</b>\n   * \n   * <pre>\n   * // Credentials from config are preserved (URI has no credentials)\n   * builder.clientConfig(configWithCredentials).fromURI(\"redis://localhost:6379\")\n   *\n   * // URI credentials override config credentials\n   * builder.clientConfig(configWithCredentials).fromURI(\"redis://user:pass@localhost:6379\")\n   *\n   * // Config completely overrides URI (last wins)\n   * builder.fromURI(\"redis://user:pass@localhost:6379\").clientConfig(newConfig)\n   * </pre>\n   *\n   * @param uriString the Redis server URI string\n   * @return this builder\n   * @deprecated Use {@link #hostAndPort(String, int)} combined with\n   *             {@link #clientConfig(JedisClientConfig)} for explicit configuration. This method\n   *             will be removed in Jedis 8.0.0.\n   *             <p>\n   *             <b>Migration example:</b>\n   * \n   *             <pre>\n   *             // Old (deprecated):\n   *             builder.fromURI(\"redis://user:pass@localhost:6379/2\")\n   *\n   *             // New (recommended):\n   *             builder.hostAndPort(\"localhost\", 6379)\n   *                    .clientConfig(DefaultJedisClientConfig.builder()\n   *                        .user(\"user\")\n   *                        .password(\"pass\")\n   *                        .database(2)\n   *                        .build())\n   *             </pre>\n   */\n  @Deprecated\n  public StandaloneClientBuilder<C> fromURI(String uriString) {\n    return fromURI(URI.create(uriString));\n  }\n\n  /**\n   * Sets the Redis server URI.\n   * <p>\n   * This method extracts connection parameters from the URI and merges them into the current client\n   * configuration. If a client configuration was previously set via\n   * {@link #clientConfig(JedisClientConfig)}, only the values explicitly provided in the URI will\n   * override the existing configuration. Values not present in the URI will be preserved from the\n   * existing configuration.\n   * <p>\n   * <b>This method sets:</b>\n   * <ul>\n   * <li>Host and port from the URI (always set)</li>\n   * <li>Client configuration with URI-derived values (merged with existing config if present)</li>\n   * </ul>\n   * <p>\n   * <b>URI values override existing config values when:</b>\n   * <ul>\n   * <li>URI contains user/password - overrides existing credentials</li>\n   * <li>URI contains database - overrides existing database</li>\n   * <li>URI contains protocol parameter - overrides existing protocol</li>\n   * <li>URI uses rediss:// scheme - enables SSL</li>\n   * </ul>\n   * <p>\n   * <b>Examples:</b>\n   * \n   * <pre>\n   * // Credentials from config are preserved (URI has no credentials)\n   * builder.clientConfig(configWithCredentials).fromURI(uri)\n   *\n   * // URI credentials override config credentials\n   * builder.clientConfig(configWithCredentials).fromURI(uriWithCredentials)\n   *\n   * // Config completely overrides URI (last wins)\n   * builder.fromURI(uriWithCredentials).clientConfig(newConfig)\n   * </pre>\n   *\n   * @param uri the Redis server URI\n   * @return this builder\n   * @deprecated Use {@link #hostAndPort(HostAndPort)} combined with\n   *             {@link #clientConfig(JedisClientConfig)} for explicit configuration. This method\n   *             will be removed in Jedis 8.0.0. See {@link #fromURI(String)} for migration example.\n   */\n  @Deprecated\n  public StandaloneClientBuilder<C> fromURI(URI uri) {\n    JedisAsserts.notNull(uri, \"Redis URI must not be null\");\n    JedisAsserts.isTrue(JedisURIHelper.isValid(uri), \"Invalid Redis URI\");\n\n    // Start with existing config if present, otherwise create new builder\n    DefaultJedisClientConfig.Builder configBuilder = (this.clientConfig != null)\n        ? DefaultJedisClientConfig.builder().from(this.clientConfig)\n        : DefaultJedisClientConfig.builder();\n\n    // Override with URI values only if URI provides them (non-null)\n    String uriUser = JedisURIHelper.getUser(uri);\n    String uriPassword = JedisURIHelper.getPassword(uri);\n\n    // If URI provides credentials, we need to clear the credentialsProvider\n    // so that the new user/password values are used instead\n    if (uriUser != null || uriPassword != null) {\n      configBuilder.credentials(new DefaultRedisCredentials(uriUser, uriPassword));\n    }\n\n    if (JedisURIHelper.hasDbIndex(uri)) {\n      configBuilder.database(JedisURIHelper.getDBIndex(uri));\n    }\n\n    RedisProtocol uriProtocol = JedisURIHelper.getRedisProtocol(uri);\n    if (uriProtocol != null) {\n      configBuilder.protocol(uriProtocol);\n    }\n\n    if (JedisURIHelper.isRedisSSLScheme(uri)) {\n      configBuilder.ssl(true);\n    } else if (JedisURIHelper.isRedisScheme(uri)) {\n      configBuilder.ssl(false);\n    }\n\n    this.clientConfig = configBuilder.build();\n    return hostAndPort(JedisURIHelper.getHostAndPort(uri));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/AccessControlLogBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.resps.AccessControlUser;\n\n/**\n * This class provides the interfaces necessary to interact with\n * Access Control Lists (ACLs) within redis. These are the interfaces\n * for binary (i.e. non-decoded) interactions.\n *\n * @see <a href=\"https://redis.io/topics/acl\">Redis ACL Guide</a>\n */\npublic interface AccessControlLogBinaryCommands {\n\n  /**\n   * Returns the username used to authenticate the current connection.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-whoami\">ACL WHOAMI</a>\n   * @return The username used for the current connection\n   */\n  byte[] aclWhoAmIBinary();\n\n  /**\n   * Generate a random password\n   *\n   * @see <a href=\"https://redis.io/commands/acl-genpass\">ACL GENPASS</a>\n   * @return A random password\n   */\n  byte[] aclGenPassBinary();\n\n  /**\n   * Generate a random password\n   *\n   * @param bits the number of output bits\n   * @return A random password\n   */\n  byte[] aclGenPassBinary(int bits);\n\n  /**\n   * Returns the currently active ACL rules on the Redis Server\n   *\n   * @see <a href=\"https://redis.io/commands/acl-list\">ACL LIST</a>\n   * @return An array of ACL rules\n   */\n  List<byte[]> aclListBinary();\n\n  /**\n   * Shows a list of all usernames currently configured with access control\n   * lists (ACL).\n   *\n   * @see <a href=\"https://redis.io/commands/acl-users\">ACL USERS</a>\n   * @return list of users\n   */\n  List<byte[]> aclUsersBinary();\n\n  /**\n   * The command returns all the rules defined for an existing ACL user.\n   * @param name username\n   * @return a list of ACL rule definitions for the user.\n   */\n  AccessControlUser aclGetUser(byte[] name);\n\n  /**\n   * Create an ACL for the specified user with the default rules.\n   *\n   * @param name user who receives an acl\n   * @see <a href=\"https://redis.io/commands/acl-setuser\">ACL SETUSER</a>\n   * @return A string containing OK on success\n   */\n  String aclSetUser(byte[] name);\n\n  /**\n   * Create an ACL for the specified user, while specifying the rules.\n   *\n   * @param name user who receives an acl\n   * @param rules the acl rules for the specified user\n   * @see <a href=\"https://redis.io/commands/acl-setuser\">ACL SETUSER</a>\n   * @return A string containing OK on success\n   */\n  String aclSetUser(byte[] name, byte[]... rules);\n\n  /**\n   * Delete the specified user, from the ACL.\n   *\n   * @param names The username to delete\n   * @see <a href=\"https://redis.io/commands/acl-deluser\">ACL DELUSER</a>\n   * @return The number of users delete\n   */\n  long aclDelUser(byte[]... names);\n\n  /**\n   * Show the available ACL categories.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-cat\">ACL CAT</a>\n   * @return the available ACL categories\n   */\n  List<byte[]> aclCatBinary();\n\n  /**\n   * Show the available ACLs for a given category.\n   *\n   * @param category The category for which to list available ACLs\n   * @see <a href=\"https://redis.io/commands/acl-cat\">ACL CAT</a>\n   * @return the available ACL categories\n   */\n  List<byte[]> aclCat(byte[] category);\n\n  /**\n   * Shows the recent ACL security events.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-log\">ACL LOG</a>\n   * @return The list of recent security events\n   */\n  List<byte[]> aclLogBinary();\n\n  /**\n   * Shows the recent limit ACL security events.\n   *\n   * @param limit The number of results to return\n   * @see <a href=\"https://redis.io/commands/acl-log\">ACL LOG</a>\n   * @return The list of recent security events\n   */\n  List<byte[]> aclLogBinary(int limit);\n\n  /**\n   * Reset the script event log\n   *\n   * @see <a href=\"https://redis.io/commands/acl-log\">ACL LOG</a>\n   * @return The OK string\n   */\n  String aclLogReset();\n\n  /**\n   * This function tells Redis to reload its external ACL rules,\n   * when Redis is configured with an external ACL file\n   *\n   * @see <a href=\"https://redis.io/commands/acl-load\">ACL LOAD</a>\n   * @return OK or error text\n   */\n  String aclLoad();\n\n  /**\n   * Save the currently defined in-memory ACL to disk.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-save\">ACL SAVE</a>\n   * @return OK on success\n   */\n  String aclSave();\n\n  byte[] aclDryRunBinary(byte[] username, byte[] command, byte[]... args);\n\n  byte[] aclDryRunBinary(byte[] username, CommandArguments commandArgs);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/AccessControlLogCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.resps.AccessControlLogEntry;\nimport redis.clients.jedis.resps.AccessControlUser;\n\n/**\n * This class provides the interfaces necessary to interact with\n * Access Control Lists (ACLs) within redis. These are the interfaces\n * for string-based (i.e. decoded) interactions.\n *\n *\n * @see <a href=\"https://redis.io/topics/acl\">Redis ACL Guide</a>\n */\n\npublic interface AccessControlLogCommands {\n\n  /**\n   * Returns the username used to authenticate the current connection.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-whoami\">ACL WHOAMI</a>\n   * @return The username used for the current connection\n   */\n  String aclWhoAmI();\n\n  /**\n   * Generate a random password\n   *\n   * @return A random password\n   */\n  String aclGenPass();\n\n  /**\n   * Generate a random password\n   *\n   * @param bits the number of output bits\n   * @return A random password\n   */\n  String aclGenPass(int bits);\n\n  /**\n   * Returns the currently active ACL rules on the Redis Server\n   *\n   * @see <a href=\"https://redis.io/commands/acl-list\">ACL LIST</a>\n   * @return An array of ACL rules\n   */\n  List<String> aclList();\n\n  /**\n   * Shows a list of all usernames currently configured with access control\n   * lists (ACL).\n   *\n   * @see <a href=\"https://redis.io/commands/acl-users\">ACL USERS</a>\n   * @return list of users\n   */\n  List<String> aclUsers();\n\n  /**\n   * The command returns all the rules defined for an existing ACL user.\n   * @param name username\n   * @return a list of ACL rule definitions for the user.\n   */\n  AccessControlUser aclGetUser(String name);\n\n  /**\n   * Create an ACL for the specified user with the default rules.\n   *\n   * @param name user who receives an acl\n   * @see <a href=\"https://redis.io/commands/acl-setuser\">ACL SETUSER</a>\n   * @return A string containing OK on success\n   */\n  String aclSetUser(String name);\n\n  /**\n   * Create an ACL for the specified user, while specifying the rules.\n   *\n   * @param name user who receives an acl\n   * @param rules the acl rules for the specified user\n   * @see <a href=\"https://redis.io/commands/acl-setuser\">ACL SETUSER</a>\n   * @return A string containing OK on success\n   */\n  String aclSetUser(String name, String... rules);\n\n  /**\n   * Delete the specified user, from the ACL.\n   *\n   * @param names The usernames to delete\n   * @see <a href=\"https://redis.io/commands/acl-deluser\">ACL DELUSER</a>\n   * @return The number of users delete\n   */\n  long aclDelUser(String... names);\n\n  /**\n   * Show the available ACL categories.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-cat\">ACL CAT</a>\n   * @return the available ACL categories\n   */\n  List<String> aclCat();\n\n  /**\n   * Show the available ACLs for a given category.\n   *\n   * @param category The category for which to list available ACLs\n   * @see <a href=\"https://redis.io/commands/acl-cat\">ACL CAT</a>\n   * @return the available ACL categories\n   */\n  List<String> aclCat(String category);\n\n  /**\n   * Shows the recent ACL security events.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-log\">ACL LOG</a>\n   * @return The list of recent security events\n   */\n  List<AccessControlLogEntry> aclLog();\n\n  /**\n   * Shows the recent limit ACL security events.\n   *\n   * @param limit The number of results to return\n   * @see <a href=\"https://redis.io/commands/acl-log\">ACL LOG</a>\n   * @return The list of recent security events\n   */\n  List<AccessControlLogEntry> aclLog(int limit);\n\n  /**\n   * Reset the script event log\n   *\n   * @return The OK string\n   */\n  String aclLogReset();\n\n  /**\n   * This function tells Redis to reload its external ACL rules,\n   * when Redis is configured with an external ACL file\n   *\n   * @see <a href=\"https://redis.io/commands/acl-load\">ACL LOAD</a>\n   * @return OK or error text\n   */\n  String aclLoad();\n\n  /**\n   * Save the currently defined in-memory ACL to disk.\n   *\n   * @see <a href=\"https://redis.io/commands/acl-save\">ACL SAVE</a>\n   * @return OK on success\n   */\n  String aclSave();\n\n  String aclDryRun(String username, String command, String... args);\n\n  String aclDryRun(String username, CommandArguments commandArgs);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/BitBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\n\npublic interface BitBinaryCommands {\n\n  boolean setbit(byte[] key, long offset, boolean value);\n\n  boolean getbit(byte[] key, long offset);\n\n  long bitcount(byte[] key);\n\n  long bitcount(byte[] key, long start, long end);\n\n  long bitcount(byte[] key, long start, long end, BitCountOption option);\n\n  long bitpos(byte[] key, boolean value);\n\n  long bitpos(byte[] key, boolean value, BitPosParams params);\n\n  List<Long> bitfield(byte[] key, byte[]... arguments);\n\n  List<Long> bitfieldReadonly(byte[] key, byte[]... arguments);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitop\">Bitop Command</a></b> Perform a bitwise operation\n   * between multiple keys and store the result in the destKey.\n   * @param op can be AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR and ONE\n   * @param destKey\n   * @param srcKeys\n   * @return The size of the string stored in the destKey\n   */\n  long bitop(BitOP op, byte[] destKey, byte[]... srcKeys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/BitCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\n\npublic interface BitCommands {\n\n  /**\n   * <b><a href=\"http://redis.io/commands/setbit\">SetBit Command</a></b>\n   * Sets or clears the bit at offset in the string value stored at key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param offset\n   * @param value\n   * @return The original bit value stored at offset\n   */\n  boolean setbit(String key, long offset, boolean value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/getbit\">GetBit Command</a></b>\n   * Returns the bit value at offset in the string value stored at key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param offset\n   * @return The bit value stored at offset\n   */\n  boolean getbit(String key, long offset);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitcount\">Bitcount Command</a></b>\n   * Count the number of set bits (population counting) in a string.\n   * @param key\n   * @return The number of bits set to 1\n   */\n  long bitcount(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitcount\">Bitcount Command</a></b>\n   * Count the number of set bits (population counting) in a string only in an interval start and end.\n   * <p>\n   * Like for the GETRANGE command start and end can contain negative values in order to index bytes\n   * starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth.\n   * @param key\n   * @param start byte start index\n   * @param end byte end index\n   * @return The number of bits set to 1\n   */\n  long bitcount(String key, long start, long end);\n\n  /**\n   * @see StringCommands#bitcount(String, long, long)\n   * @param key\n   * @param start byte start index\n   * @param end byte end index\n   * @param option indicate BYTE or BIT\n   * @return The number of bits set to 1\n   */\n  long bitcount(String key, long start, long end, BitCountOption option);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitpos\">Bitpos Command</a></b>\n   * Return the position of the first bit set to 1 or 0 in a string.\n   * @param key\n   * @param value the bit value\n   * @return The position of the first bit set to 1 or 0 according to the request\n   */\n  long bitpos(String key, boolean value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitpos\">Bitpos Command</a></b>\n   * Return the position of the first bit set to 1 or 0 in a string.\n   * @param key\n   * @param value the bit value\n   * @param params {@link BitPosParams}\n   * @return The position of the first bit set to 1 or 0 according to the request\n   */\n  long bitpos(String key, boolean value, BitPosParams params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitfield\">Bitfield Command</a></b>\n   * The command treats a Redis string as an array of bits, and is capable of addressing specific integer\n   * fields of varying bit widths and arbitrary non (necessary) aligned offset.\n   * @param key\n   * @param arguments may be used with optional arguments\n   * @return A List of results\n   */\n  List<Long> bitfield(String key, String...arguments);\n\n  /**\n   * The readonly version of {@link StringCommands#bitfield(String, String...) BITFIELD}\n   */\n  List<Long> bitfieldReadonly(String key, String...arguments);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/bitop\">Bitop Command</a></b>\n   * Perform a bitwise operation between multiple keys (containing string values) and store the result in the destKey.\n   * @param op can be AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR and ONE\n   * @param destKey\n   * @param srcKeys\n   * @return The size of the string stored in the destKey\n   */\n  long bitop(BitOP op, String destKey, String... srcKeys);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/BitPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\n\npublic interface BitPipelineBinaryCommands {\n\n  Response<Boolean> setbit(byte[] key, long offset, boolean value);\n\n  Response<Boolean> getbit(byte[] key, long offset);\n\n  Response<Long> bitcount(byte[] key);\n\n  Response<Long> bitcount(byte[] key, long start, long end);\n\n  Response<Long> bitcount(byte[] key, long start, long end, BitCountOption option);\n\n  Response<Long> bitpos(byte[] key, boolean value);\n\n  Response<Long> bitpos(byte[] key, boolean value, BitPosParams params);\n\n  Response<List<Long>> bitfield(byte[] key, byte[]... arguments);\n\n  Response<List<Long>> bitfieldReadonly(byte[] key, byte[]... arguments);\n\n  Response<Long> bitop(BitOP op, byte[] destKey, byte[]... srcKeys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/BitPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\n\nimport java.util.List;\n\npublic interface BitPipelineCommands {\n\n  Response<Boolean> setbit(String key, long offset, boolean value);\n\n  Response<Boolean> getbit(String key, long offset);\n\n  Response<Long> bitcount(String key);\n\n  Response<Long> bitcount(String key, long start, long end);\n\n  Response<Long> bitcount(String key, long start, long end, BitCountOption option);\n\n  Response<Long> bitpos(String key, boolean value);\n\n  Response<Long> bitpos(String key, boolean value, BitPosParams params);\n\n  Response<List<Long>> bitfield(String key, String...arguments);\n\n  Response<List<Long>> bitfieldReadonly(String key, String...arguments);\n\n  Response<Long> bitop(BitOP op, String destKey, String... srcKeys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ClientBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.ClientAttributeOption;\nimport redis.clients.jedis.args.ClientPauseMode;\nimport redis.clients.jedis.args.ClientType;\nimport redis.clients.jedis.args.UnblockType;\nimport redis.clients.jedis.params.ClientKillParams;\n\n/**\n * The interface contain all the commands about client.\n * The params is byte[]\n */\npublic interface ClientBinaryCommands {\n\n  /**\n   * Close a given client connection.\n   *\n   * @param ipPort The ip:port should match a line returned by the CLIENT LIST command (addr field).\n   * @return close success return OK\n   */\n  String clientKill(byte[] ipPort);\n\n  /**\n   * Close a given client connection.\n   *\n   * @param ip   The ip should match a line returned by the CLIENT LIST command (addr field).\n   * @param port The port should match a line returned by the CLIENT LIST command (addr field).\n   * @return Close success return OK\n   */\n  String clientKill(String ip, int port);\n\n  /**\n   * Close a given client connection.\n   *\n   * @param params connect info will be closed\n   * @return Close success return OK\n   */\n  long clientKill(ClientKillParams params);\n\n  /**\n   * Returns the name of the current connection as set by CLIENT SETNAME\n   *\n   * @return Current connect name\n   */\n  byte[] clientGetnameBinary();\n\n  /**\n   * Returns information and statistics about the client connections server\n   * in a mostly human-readable format.\n   *\n   * @return All clients info connected to redis-server\n   */\n  byte[] clientListBinary();\n\n  /**\n   * Returns information and statistics about the client connections server\n   * in a mostly human-readable format filter by client type.\n   *\n   * @return all clients info connected to redis-server\n   */\n  byte[] clientListBinary(ClientType type);\n\n  /**\n   * Returns information and statistics about the client connections server\n   * in a mostly human-readable format filter by client ids.\n   *\n   * @param clientIds Unique 64-bit client IDs\n   * @return All clients info connected to redis-server\n   */\n  byte[] clientListBinary(long... clientIds);\n\n  /**\n   * Returns information and statistics about the current client connection\n   * in a mostly human-readable format.\n   *\n   * @return Information and statistics about the current client connection\n   */\n  byte[] clientInfoBinary();\n\n  /**\n   * client set info command\n   * Since redis 7.2\n   * @param attr the attr option\n   * @param value the value\n   * @return OK or error\n   */\n  String clientSetInfo(ClientAttributeOption attr, byte[] value);\n\n  /**\n   * Assigns a name to the current connection.\n   *\n   * @param name Current connection name\n   * @return OK if the connection name was successfully set.\n   */\n  String clientSetname(byte[] name);\n\n  /**\n   * Returns the ID of the current connection.\n   *\n   * @return The id of the client.\n   */\n  long clientId();\n\n  /**\n   * Unblock from a different connection, a client blocked in a\n   * blocking operation, such as for instance BRPOP or XREAD or WAIT.\n   *\n   * @param clientId The id of the client\n   * @return Specifically:\n   * 1 if the client was unblocked successfully.\n   * 0 if the client wasn't unblocked.\n   */\n  long clientUnblock(long clientId);\n\n  /**\n   * Unblock from a different connection, a client blocked in a\n   * blocking operation, such as for instance BRPOP or XREAD or WAIT.\n   *\n   * @param clientId    The id of the client\n   * @param unblockType TIMEOUT|ERROR\n   * @return Specifically:\n   * 1 if the client was unblocked successfully.\n   * 0 if the client wasn't unblocked.\n   */\n  long clientUnblock(long clientId, UnblockType unblockType);\n\n  /**\n   * A connections control command able to suspend all the\n   * Redis clients for the specified amount of time (in milliseconds)\n   *\n   * @param timeout WRITE|ALL\n   * @return The command returns OK or an error if the timeout is invalid.\n   */\n  String clientPause(long timeout);\n\n  /**\n   * A connections control command able to suspend all the\n   * Redis clients for the specified amount of time (in milliseconds)\n   *\n   * @param timeout Command timeout\n   * @param mode    WRITE|ALL\n   * @return The command returns OK or an error if the timeout is invalid.\n   */\n  String clientPause(long timeout, ClientPauseMode mode);\n\n  /**\n   * CLIENT UNPAUSE is used to resume command processing for all clients that were paused by CLIENT PAUSE.\n   * @return OK\n   */\n  String clientUnpause();\n\n  /**\n   * Turn on the client eviction mode for the current connection.\n   *\n   * @return OK\n   */\n  String clientNoEvictOn();\n\n  /**\n   * Turn off the client eviction mode for the current connection.\n   *\n   * @return OK\n   */\n  String clientNoEvictOff();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ClientCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.ClientAttributeOption;\nimport redis.clients.jedis.args.ClientPauseMode;\nimport redis.clients.jedis.args.ClientType;\nimport redis.clients.jedis.args.UnblockType;\nimport redis.clients.jedis.params.ClientKillParams;\nimport redis.clients.jedis.resps.TrackingInfo;\n\n/**\n * The interface contain all the commands about client.\n * The params is String encoded in uft-8\n */\npublic interface ClientCommands {\n\n  /**\n   * Close a given client connection.\n   *\n   * @param ipPort The ip:port should match a line returned by the CLIENT LIST command (addr field).\n   * @return Close success return OK\n   */\n  String clientKill(String ipPort);\n\n  /**\n   * Close a given client connection.\n   *\n   * @param ip   The ip should match a line returned by the CLIENT LIST command (addr field).\n   * @param port The port should match a line returned by the CLIENT LIST command (addr field).\n   * @return Close success return OK\n   */\n  String clientKill(String ip, int port);\n\n  /**\n   * Close client connections based on certain selection parameters.\n   *\n   * @param params Parameters defining what client connections to close.\n   * @return The number of client connections that were closed.\n   */\n  long clientKill(ClientKillParams params);\n\n  /**\n   * Returns the name of the current connection as set by CLIENT SETNAME\n   *\n   * @return Current connect name\n   */\n  String clientGetname();\n\n  /**\n   * Returns information and statistics about the client connections server\n   * in a mostly human-readable format.\n   *\n   * @return All clients info connected to redis-server\n   */\n  String clientList();\n\n  /**\n   * Returns information and statistics about the client connections server\n   * in a mostly human-readable format filter by client type.\n   *\n   * @return All clients info connected to redis-server\n   */\n  String clientList(ClientType type);\n\n  /**\n   * Returns information and statistics about the client connections server\n   * in a mostly human-readable format filter by client ids.\n   *\n   * @param clientIds Unique 64-bit client IDs\n   * @return All clients info connected to redis-server\n   */\n  String clientList(long... clientIds);\n\n  /**\n   * Returns information and statistics about the current client connection\n   * in a mostly human-readable format.\n   *\n   * @return Information and statistics about the current client connection\n   */\n  String clientInfo();\n\n  /**\n   * client set info command\n   * Since redis 7.2\n   * @param attr the attr option\n   * @param value the value\n   * @return OK or error\n   */\n  String clientSetInfo(ClientAttributeOption attr, String value);\n\n  /**\n   * Assigns a name to the current connection.\n   *\n   * @param name current connection name\n   * @return OK if the connection name was successfully set.\n   */\n  String clientSetname(String name);\n\n  /**\n   * Returns the ID of the current connection.\n   *\n   * @return The id of the client.\n   */\n  long clientId();\n\n  /**\n   * Unblock from a different connection, a client blocked in a\n   * blocking operation, such as for instance BRPOP or XREAD or WAIT.\n   *\n   * @param clientId The id of the client\n   * @return 1 if the client was unblocked successfully, 0 if the client wasn't unblocked.\n   */\n  long clientUnblock(long clientId);\n\n  /**\n   * Unblock from a different connection, a client blocked in a\n   * blocking operation, such as for instance BRPOP or XREAD or WAIT.\n   *\n   * @param clientId    The id of the client\n   * @param unblockType TIMEOUT|ERROR\n   * @return 1 if the client was unblocked successfully, 0 if the client wasn't unblocked.\n   */\n  long clientUnblock(long clientId, UnblockType unblockType);\n\n  /**\n   * A connections control command able to suspend all the\n   * Redis clients for the specified amount of time (in milliseconds)\n   *\n   * @param timeout WRITE|ALL\n   * @return The command returns OK or an error if the timeout is invalid.\n   */\n  String clientPause(long timeout);\n\n  /**\n   * A connections control command able to suspend all the\n   * Redis clients for the specified amount of time (in milliseconds)\n   *\n   * @param timeout Command timeout\n   * @param mode    WRITE|ALL\n   * @return The command returns OK or an error if the timeout is invalid.\n   */\n  String clientPause(long timeout, ClientPauseMode mode);\n\n  /**\n   * CLIENT UNPAUSE is used to resume command processing for all clients that were paused by CLIENT PAUSE.\n   * @return OK\n   */\n  String clientUnpause();\n\n  /**\n   * Turn on the client eviction mode for the current connection.\n   *\n   * @return OK\n   */\n  String clientNoEvictOn();\n\n  /**\n   * Turn off the client eviction mode for the current connection.\n   *\n   * @return OK\n   */\n  String clientNoEvictOff();\n\n  /**\n   * Turn on <a href=\"https://redis.io/commands/client-no-touch/\">CLIENT NO-TOUCH</a>\n   * @return OK\n   */\n  String clientNoTouchOn();\n\n  /**\n   * Turn off <a href=\"https://redis.io/commands/client-no-touch/\">CLIENT NO-TOUCH</a>\n   * @return OK\n   */\n  String clientNoTouchOff();\n\n  TrackingInfo clientTrackingInfo();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ClusterCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.args.ClusterResetType;\nimport redis.clients.jedis.args.ClusterFailoverOption;\nimport redis.clients.jedis.resps.ClusterShardInfo;\n\npublic interface ClusterCommands {\n\n  String asking();\n\n  String readonly();\n\n  String readwrite();\n\n  String clusterNodes();\n\n  String clusterMeet(String ip, int port);\n\n  String clusterAddSlots(int... slots);\n\n  String clusterDelSlots(int... slots);\n\n  String clusterInfo();\n\n  List<String> clusterGetKeysInSlot(int slot, int count);\n\n  List<byte[]> clusterGetKeysInSlotBinary(int slot, int count);\n\n  String clusterSetSlotNode(int slot, String nodeId);\n\n  String clusterSetSlotMigrating(int slot, String nodeId);\n\n  String clusterSetSlotImporting(int slot, String nodeId);\n\n  String clusterSetSlotStable(int slot);\n\n  String clusterForget(String nodeId);\n\n  String clusterFlushSlots();\n\n  long clusterKeySlot(String key);\n\n  long clusterCountFailureReports(String nodeId);\n\n  long clusterCountKeysInSlot(int slot);\n\n  String clusterSaveConfig();\n\n  /**\n   * Set a specific config epoch in a fresh node. It only works when the nodes' table\n   * of the node is empty or when the node current config epoch is zero.\n   * @param configEpoch\n   * @return OK\n   */\n  String clusterSetConfigEpoch(long configEpoch);\n\n  /**\n   * Advance the cluster config epoch.\n   * @return BUMPED if the epoch was incremented, or STILL if the node already has the\n   * greatest config epoch in the cluster.\n   */\n  String clusterBumpEpoch();\n\n  String clusterReplicate(String nodeId);\n\n  /**\n   * {@code CLUSTER SLAVES} command is deprecated since Redis 5.\n   *\n   * @deprecated Use {@link ClusterCommands#clusterReplicas(java.lang.String)}.\n   */\n  @Deprecated\n  List<String> clusterSlaves(String nodeId);\n\n  List<String> clusterReplicas(String nodeId);\n\n  String clusterFailover();\n\n  String clusterFailover(ClusterFailoverOption failoverOption);\n\n  /**\n   * {@code CLUSTER SLOTS} command is deprecated since Redis 7.\n   *\n   * @deprecated Use {@link ClusterCommands#clusterShards()}.\n   */\n  @Deprecated\n  List<Object> clusterSlots();\n\n  /**\n   * {@code CLUSTER SHARDS} returns details about the shards of the cluster.\n   * This command replaces the {@code CLUSTER SLOTS} command from Redis 7,\n   * by providing a more efficient and extensible representation of the cluster.\n   *\n   * @return a list of shards, with each shard containing two objects, 'slots' and 'nodes'.\n   * @see <a href=\"https://redis.io/commands/cluster-shards/\">CLUSTER SHARDS</a>\n   */\n  List<ClusterShardInfo> clusterShards();\n\n  String clusterReset();\n\n  /**\n   * {@code resetType} can be null for default behavior.\n   *\n   * @param resetType\n   * @return OK\n   */\n  String clusterReset(ClusterResetType resetType);\n\n  String clusterMyId();\n\n  String clusterMyShardId();\n\n  /**\n   * return the information of all such peer links as an array, where each array element is a map that contains\n   * attributes and their values for an individual link.\n   *\n   * @return the information of all such peer links as an array\n   * @see <a href=\"https://redis.io/commands/cluster-links\" >CLUSTET LINKS</a>\n   */\n  List<Map<String, Object>> clusterLinks();\n\n  /**\n   * Takes a list of slot ranges (specified by start and end slots) to assign to the node\n   *\n   * @param ranges slots range\n   * @return OK if the command was successful. Otherwise, an error is returned.\n   */\n  String clusterAddSlotsRange(int... ranges);\n\n  /**\n   * Takes a list of slot ranges (specified by start and end slots) to remove to the node.\n   *\n   * @param ranges slots range\n   * @return OK if the command was successful. Otherwise, an error is returned.\n   */\n  String clusterDelSlotsRange(int... ranges);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/CommandCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.params.CommandListFilterByParams;\nimport redis.clients.jedis.resps.CommandDocument;\nimport redis.clients.jedis.resps.CommandInfo;\nimport redis.clients.jedis.util.KeyValue;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic interface CommandCommands {\n\n  /**\n   * The number of total commands in this Redis server\n   * @return The number of total commands\n   */\n  long commandCount();\n\n  /**\n   * Return documentary information about commands.\n   * If not specifying commands, the reply includes all the server's commands.\n   * @param commands specify the names of one or more commands\n   * @return list of {@link CommandDocument}\n   */\n\n  Map<String, CommandDocument> commandDocs(String... commands);\n\n  /**\n   * Return list of keys from a full Redis command\n   * @param command\n   * @return list of keys\n   */\n  List<String> commandGetKeys(String... command);\n\n  /**\n   * Return list of keys from a full Redis command and their usage flags\n   * @param command\n   * @return list of {@link KeyValue}\n   */\n  List<KeyValue<String, List<String>>> commandGetKeysAndFlags(String... command);\n\n  /**\n   * Return details about multiple Redis commands\n   * @param commands\n   * @return list of {@link CommandInfo}\n   */\n  Map<String, CommandInfo> commandInfo(String... commands);\n\n  /**\n   * Return an array with details about every Redis command.\n   * @return list of {@link CommandInfo}\n   */\n  Map<String, CommandInfo> command();\n\n  /**\n   * Return a list of the server's command names\n   * @return commands list\n   */\n  List<String> commandList();\n\n  /**\n   * Return a list of the server's command names filtered by module's name, ACL category or pattern\n   * @param filterByParams {@link CommandListFilterByParams}\n   * @return commands list\n   */\n  List<String> commandListFilterBy(CommandListFilterByParams filterByParams);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ConfigCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.Map;\n\n/**\n * The interface about managing configuration parameters of Redis server.\n */\npublic interface ConfigCommands {\n\n  /**\n   * Used to read the configuration parameters of Redis server.\n   *\n   * @param pattern name of Redis server's configuration\n   * @return config value of Redis server\n   */\n  Map<String, String> configGet(String pattern);\n\n  /**\n   * Used to read the configuration parameters of Redis server.\n   *\n   * @param patterns names of Redis server's configuration\n   * @return values of Redis server's configuration\n   */\n  Map<String, String> configGet(String... patterns);\n\n  /**\n   * Used to read the configuration parameters of Redis server.\n   *\n   * @param pattern name of Redis server's configuration\n   * @return value of Redis server's configuration\n   */\n  Map<byte[], byte[]> configGet(byte[] pattern);\n\n  /**\n   * Used to read the configuration parameters of Redis server.\n   *\n   * @param patterns names of Redis server's configuration\n   * @return values of Redis server's configuration\n   */\n  Map<byte[], byte[]> configGet(byte[]... patterns);\n\n  /**\n   * Used in order to reconfigure the Redis server at run time without\n   * the need to restart.\n   *\n   * @param parameter name of Redis server's configuration\n   * @param value     value of Redis server's configuration\n   * @return OK when the configuration was set properly.\n   * Otherwise, an error is returned.\n   */\n  String configSet(String parameter, String value);\n\n  String configSet(String... parameterValues);\n\n  String configSet(Map<String, String> parameterValues);\n\n  /**\n   * Used in order to reconfigure the Redis server at run time without\n   * the need to restart.\n   *\n   * @param parameter name of Redis server's configuration\n   * @param value     value of Redis server's configuration\n   * @return OK when the configuration was set properly.\n   * Otherwise, an error is returned.\n   */\n  String configSet(byte[] parameter, byte[] value);\n\n  String configSet(byte[]... parameterValues);\n\n  String configSetBinary(Map<byte[], byte[]> parameterValues);\n\n  /**\n   * Resets the statistics reported by Redis using the INFO command.\n   * <p>\n   * These are the counters that are reset:\n   * <p>\n   * 1) Keyspace hits\n   * 2) Keyspace misses\n   * 3) Number of commands processed\n   * 4) Number of connections received\n   * 5) Number of expired keys\n   * 6) Number of rejected connections\n   * 7) Latest fork(2) time\n   * 8) The aof_delayed_fsync counter\n   *\n   * @return always OK.\n   */\n  String configResetStat();\n\n  /**\n   * Rewrites the redis.conf file the server was started with, applying\n   * the minimal changes needed to make it reflect the configuration\n   * currently used by the server, which may be different compared to the\n   * original one because of the use of the CONFIG SET command.\n   *\n   * @return OK when the configuration was rewritten properly.\n   * Otherwise, an error is returned.\n   */\n  String configRewrite();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ControlBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\n/**\n * The interface about Redis management command\n */\npublic interface ControlBinaryCommands extends AccessControlLogBinaryCommands, ClientBinaryCommands {\n\n  /**\n   * Provide information on the role of a Redis instance in the context of replication,\n   * by returning if the instance is currently a master, slave, or sentinel. The command\n   * also returns additional information about the state of the replication\n   * (if the role is master or slave) or the list of monitored master names (if the role is sentinel).\n   *\n   * @return The information on the role of a Redis instance\n   */\n  List<Object> roleBinary();\n\n  /**\n   * Returns the reference count of the stored at {@code key}.\n   *\n   * @param key The key in Redis server\n   * @return The reference count of the stored at {@code key}\n   */\n  Long objectRefcount(byte[] key);\n\n  /**\n   * Returns the internal encoding for the Redis object stored at {@code key}.\n   * <p>\n   * See for details: <a href=\"https://redis.io/commands/object-encoding\">OBJECT ENCODING key</a>\n   *\n   * @param key The key in Redis server\n   * @return The number of references\n   */\n  byte[] objectEncoding(byte[] key);\n\n  /**\n   * Returns the time in seconds since the last access to the value stored at {@code key}.\n   * The command is only available when the maxmemory-policy configuration directive\n   * is not set to one of the LFU policies.\n   *\n   * @param key The key in Redis server\n   * @return The idle time in seconds\n   */\n  Long objectIdletime(byte[] key);\n\n  /**\n   * Returns the object subcommands and usages.\n   *\n   * @return object subcommands and usages\n   */\n  List<byte[]> objectHelpBinary();\n\n  /**\n   * Returns the logarithmic access frequency counter of a Redis object stored at {@code key}.\n   * <p>\n   * The command is only available when the maxmemory-policy configuration directive is\n   * set to one of the LFU policies.\n   *\n   * @param key The key in Redis server\n   * @return The counter's value\n   */\n  Long objectFreq(byte[] key);\n\n  /**\n   * Reports about different memory-related issues that the Redis server experiences,\n   * and advises about possible remedies.\n   */\n  byte[] memoryDoctorBinary();\n\n  /**\n   * Reports the number of bytes that a key and its value require to be stored in RAM.\n   * The reported usage is the total of memory allocations for data and administrative\n   * overheads that a key its value require.\n   * <p>\n   * See for details: <a href=\"https://redis.io/commands/memory-usage\">MEMORY USAGE key</a>\n   *\n   * @param key The key in Redis server\n   * @return The memory usage in bytes, or nil when the key does not exist\n   */\n  Long memoryUsage(byte[] key);\n\n  /**\n   * Reports the number of bytes that a key and its value require to be stored in RAM.\n   * The reported usage is the total of memory allocations for data and administrative\n   * overheads that a key its value require.\n   * <p>\n   * See for details: <a href=\"https://redis.io/commands/memory-usage\">MEMORY USAGE key SAMPLES count</a>\n   *\n   * @param key The key in Redis server\n   * @return The memory usage in bytes, or nil when the key does not exist\n   */\n  Long memoryUsage(byte[] key, int samples);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ControlCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * The interface about Redis management command\n */\npublic interface ControlCommands extends AccessControlLogCommands, ClientCommands {\n\n  /**\n   * Provide information on the role of a Redis instance in the context of replication,\n   * by returning if the instance is currently a master, slave, or sentinel. The command\n   * also returns additional information about the state of the replication\n   * (if the role is master or slave) or the list of monitored master names (if the role is sentinel).\n   *\n   * @return The information on the role of a Redis instance\n   */\n  List<Object> role();\n\n  /**\n   * Returns the reference count of the stored at {@code key}.\n   *\n   * @param key The key in Redis server\n   * @return The reference count of the stored at {@code key}\n   */\n  Long objectRefcount(String key);\n\n  /**\n   * Returns the internal encoding for the Redis object stored at {@code key}.\n   * <p>\n   * See for details: <a href=\"https://redis.io/commands/object-encoding\">OBJECT ENCODING key</a>\n   *\n   * @param key The key in Redis server\n   * @return The number of references\n   */\n  String objectEncoding(String key);\n\n  /**\n   * Returns the time in seconds since the last access to the value stored at {@code key}.\n   * The command is only available when the maxmemory-policy configuration directive\n   * is not set to one of the LFU policies.\n   *\n   * @param key The key in Redis server\n   * @return The idle time in seconds\n   */\n  Long objectIdletime(String key);\n\n  /**\n   * Returns the object subcommands and usages.\n   *\n   * @return object subcommands and usages\n   */\n  List<String> objectHelp();\n\n  /**\n   * Returns the logarithmic access frequency counter of a Redis object stored at {@code key}.\n   * <p>\n   * The command is only available when the maxmemory-policy configuration directive is\n   * set to one of the LFU policies.\n   *\n   * @param key The key in Redis server\n   * @return The counter's value\n   */\n  Long objectFreq(String key);\n\n  /**\n   * Reports about different memory-related issues that the Redis server experiences,\n   * and advises about possible remedies.\n   */\n  String memoryDoctor();\n\n  /**\n   * Reports the number of bytes that a key and its value require to be stored in RAM.\n   * The reported usage is the total of memory allocations for data and administrative\n   * overheads that a key its value require.\n   * <p>\n   * See for details: <a href=\"https://redis.io/commands/memory-usage\">MEMORY USAGE key</a>\n   *\n   * @param key The key in Redis server\n   * @return The memory usage in bytes, or {@code nil} when the key does not exist\n   */\n  Long memoryUsage(String key);\n\n  /**\n   * Reports the number of bytes that a key and its value require to be stored in RAM.\n   * The reported usage is the total of memory allocations for data and administrative\n   * overheads that a key its value require.\n   * <p>\n   * See for details: <a href=\"https://redis.io/commands/memory-usage\">MEMORY USAGE key SAMPLES count</a>\n   *\n   * @param key The key in Redis server\n   * @return The memory usage in bytes, or {@code nil} when the key does not exist\n   */\n  Long memoryUsage(String key, int samples);\n\n  /**\n   * Attempts to purge dirty pages so these can be reclaimed by the allocator.\n   *\n   * @return OK\n   */\n  String memoryPurge();\n\n  /**\n   * Returns an Array reply about the memory usage of the server.\n   *\n   * @return nested list of memory usage metrics and their values\n   */\n  Map<String, Object> memoryStats();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/DatabaseCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.params.MigrateParams;\n\npublic interface DatabaseCommands {\n\n  /**\n   * Select the DB with having the specified zero-based numeric index.\n   * @param index the index\n   * @return OK\n   */\n  String select(int index);\n\n  /**\n   * Return the number of keys in the currently-selected database.\n   * @return The number of keys\n   */\n  long dbSize();\n\n  /**\n   * Delete all the keys of the currently selected DB. This command never fails. The time-complexity\n   * for this operation is O(N), N being the number of keys in the database.\n   * @return OK\n   */\n  String flushDB();\n\n  /**\n   * Delete all the keys of the currently selected DB. This command never fails. The time-complexity\n   * for this operation is O(N), N being the number of keys in the database.\n   * @param flushMode can be SYNC or ASYNC\n   * @return OK\n   */\n  String flushDB(FlushMode flushMode);\n\n  /**\n   * This command swaps two Redis databases, so that immediately all the clients connected to a\n   * given database will see the data of the other database, and the other way around.\n   * @param index1\n   * @param index2\n   * @return OK\n   */\n  String swapDB(int index1, int index2);\n\n  /**\n   * Move the specified key from the currently selected DB to the specified destination DB. Note\n   * that this command returns 1 only if the key was successfully moved, and 0 if the target key was\n   * already there or if the source key was not found at all, so it is possible to use MOVE as a\n   * locking primitive.\n   * @param key  The specified key\n   * @param dbIndex Specified destination database\n   * @return 1 if the key was moved, 0 if the key was not moved because already present on the target\n   * DB or was not found in the current DB\n   */\n  long move(String key, int dbIndex);\n\n  /**\n   * Binary version of {@link DatabaseCommands#move(String, int) MOVE}.\n   * @see DatabaseCommands#move(String, int)\n   */\n  long move(byte[] key, int dbIndex);\n\n  /**\n   * Copy the value stored at the source key to the destination key.\n   * @param srcKey the source key.\n   * @param dstKey the destination key.\n   * @param db allows specifying an alternative logical database index for the destination key.\n   * @param replace removes the destination key before copying the value to it, in order to avoid error.\n   */\n  boolean copy(String srcKey, String dstKey, int db, boolean replace);\n\n  /**\n   * Binary version of {@link DatabaseCommands#copy(String, String, int, boolean) COPY}.\n   * @see DatabaseCommands#copy(String, String, int, boolean)\n   */\n  boolean copy(byte[] srcKey, byte[] dstKey, int db, boolean replace);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/migrate\">Migrate Command</a></b>\n   * Atomically transfer a key from a source Redis instance to a destination Redis instance.\n   * On success the key is deleted from the original instance and is guaranteed to exist in\n   * the target instance.\n   *\n   * @param host          target host\n   * @param port          target port\n   * @param key           migrate key\n   * @param destinationDB target db\n   * @param timeout       the maximum idle time in any moment of the communication with the\n   *                      destination instance in milliseconds.\n   * @return OK on success, or NOKEY if no keys were found in the source instance\n   */\n  String migrate(String host, int port, String key, int destinationDB, int timeout);\n\n  /**\n   * Binary version of {@link DatabaseCommands#migrate(String, int, String, int, int) MIGRATE}.\n   * @see DatabaseCommands#migrate(String, int, String, int, int)\n   */\n  String migrate(String host, int port, byte[] key, int destinationDB, int timeout);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/migrate\">Migrate Command</a></b>\n   * Atomically transfer a key from a source Redis instance to a destination Redis instance.\n   * On success the key is deleted from the original instance and is guaranteed to exist in\n   * the target instance.\n   * @param host          target host\n   * @param port          target port\n   * @param destinationDB target db\n   * @param timeout the maximum idle time in any moment of the communication with the\n   *               destination instance in milliseconds.\n   * @param params {@link MigrateParams}\n   * @param keys to migrate\n   * @return OK on success, or NOKEY if no keys were found in the source instance.\n   */\n  String migrate(String host, int port, int destinationDB, int timeout, MigrateParams params,\n      String... keys);\n\n  /**\n   * Binary version of {@link DatabaseCommands#migrate(String, int, int, int, MigrateParams, String...) MIGRATE}.\n   * @see DatabaseCommands#migrate(String, int, int, int, MigrateParams, String...)\n   */\n  String migrate(String host, int port, int destinationDB, int timeout, MigrateParams params,\n      byte[]... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/DatabasePipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.MigrateParams;\n\npublic interface DatabasePipelineCommands {\n\n  /**\n   * Select the DB with having the specified zero-based numeric index.\n   *\n   * @param index the index of db\n   * @return OK\n   */\n  Response<String> select(int index);\n\n  /**\n   * Return the number of keys in the currently-selected database.\n   *\n   * @return The number of keys\n   */\n  Response<Long> dbSize();\n\n  /**\n   * This command swaps two Redis databases, so that immediately all the clients connected to a\n   * given database will see the data of the other database, and the other way around.\n   *\n   * @param index1\n   * @param index2\n   * @return OK\n   */\n  Response<String> swapDB(int index1, int index2);\n\n  /**\n   * Move the specified key from the currently selected DB to the specified destination DB. Note\n   * that this command returns 1 only if the key was successfully moved, and 0 if the target key was\n   * already there or if the source key was not found at all, so it is possible to use MOVE as a\n   * locking primitive.\n   *\n   * @param key     The specified key\n   * @param dbIndex Specified destination database\n   * @return 1 if the key was moved, 0 if the key was not moved because already present on the target\n   * DB or was not found in the current DB\n   */\n  Response<Long> move(String key, int dbIndex);\n\n  /**\n   * Binary version of {@link DatabaseCommands#move(String, int) MOVE}.\n   *\n   * @see DatabaseCommands#move(String, int)\n   */\n  Response<Long> move(byte[] key, int dbIndex);\n\n  /**\n   * Copy the value stored at the source key to the destination key.\n   *\n   * @param srcKey  The source key.\n   * @param dstKey  The destination key.\n   * @param db      Allows specifying an alternative logical database index for the destination key.\n   * @param replace Removes the destination key before copying the value to it, in order to avoid error.\n   */\n  Response<Boolean> copy(String srcKey, String dstKey, int db, boolean replace);\n\n  /**\n   * Binary version of {@link DatabasePipelineCommands#copy(String, String, int, boolean) COPY}.\n   *\n   * @see DatabasePipelineCommands#copy(String, String, int, boolean)\n   */\n  Response<Boolean> copy(byte[] srcKey, byte[] dstKey, int db, boolean replace);\n\n  /**\n   * Binary version of {@link DatabasePipelineCommands#migrate(String, int, String, int, int) MIGRATE}.\n   *\n   * @see DatabasePipelineCommands#migrate(String, int, String, int, int)\n   */\n  Response<String> migrate(String host, int port, byte[] key, int destinationDB, int timeout);\n\n  /**\n   * Binary version of {@link DatabasePipelineCommands#migrate(String, int, int, int, MigrateParams, String...) MIGRATE}.\n   *\n   * @see DatabasePipelineCommands#migrate(String, int, int, int, MigrateParams, String...)\n   */\n  Response<String> migrate(String host, int port, int destinationDB, int timeout, MigrateParams params, byte[]... keys);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/migrate\">Migrate Command</a></b>\n   * Atomically transfer a key from a source Redis instance to a destination Redis instance.\n   * On success the key is deleted from the original instance and is guaranteed to exist in\n   * the target instance.\n   *\n   * @param host          Target host\n   * @param port          Target port\n   * @param key           Migrate key\n   * @param destinationDB Target db\n   * @param timeout       The maximum idle time in any moment of the communication with the\n   *                      destination instance in milliseconds.\n   * @return OK on success, or NOKEY if no keys were found in the source instance\n   */\n  Response<String> migrate(String host, int port, String key, int destinationDB, int timeout);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/migrate\">Migrate Command</a></b>\n   * Atomically transfer a key from a source Redis instance to a destination Redis instance.\n   * On success the key is deleted from the original instance and is guaranteed to exist in\n   * the target instance.\n   *\n   * @param host          Target host\n   * @param port          Target port\n   * @param destinationDB Target db\n   * @param timeout       The maximum idle time in any moment of the communication with the\n   *                      destination instance in milliseconds.\n   * @param params        {@link MigrateParams}\n   * @param keys          The keys to migrate\n   * @return OK on success, or NOKEY if no keys were found in the source instance.\n   */\n  Response<String> migrate(String host, int port, int destinationDB, int timeout, MigrateParams params, String... keys);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/FunctionBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\n\nimport java.util.List;\n\npublic interface FunctionBinaryCommands {\n\n/**\n   * Invoke a function.\n   * @param name\n   * @param keys\n   * @param args\n   * @return value depends on the function that was executed\n   */\n  Object fcall(byte[] name, List<byte[]> keys, List<byte[]> args);\n\n  Object fcallReadonly(byte[] name, List<byte[]> keys, List<byte[]> args);\n\n  /**\n   * This command deletes the library called library-name and all functions in it.\n   * If the library doesn't exist, the server returns an error.\n   * @param libraryName\n   * @return OK\n   */\n  String functionDelete(byte[] libraryName);\n\n  /**\n   * Return the serialized payload of loaded libraries. You can restore the\n   * serialized payload later with the {@link FunctionBinaryCommands#functionRestore(byte[], FunctionRestorePolicy) FUNCTION RESTORE} command.\n   * @return the serialized payload\n   */\n  byte[] functionDump();\n\n  /**\n   * Deletes all the libraries, unless called with the optional mode argument, the\n   * 'lazyfree-lazy-user-flush' configuration directive sets the effective behavior.\n   * @return OK\n   */\n  String functionFlush();\n\n  /**\n   * Deletes all the libraries, unless called with the optional mode argument, the\n   * 'lazyfree-lazy-user-flush' configuration directive sets the effective behavior.\n   * @param mode ASYNC: Asynchronously flush the libraries, SYNC: Synchronously flush the libraries.\n   * @return OK\n   */\n  String functionFlush(FlushMode mode);\n\n  /**\n   * Kill a function that is currently executing. The command can be used only on functions\n   * that did not modify the dataset during their execution.\n   * @return OK\n   */\n  String functionKill();\n\n  /**\n   * Return information about the functions and libraries.\n   * @return {@link LibraryInfo}\n   */\n  List<Object> functionListBinary();\n\n  /**\n   * Return information about the functions and libraries.\n   * @param libraryNamePattern a pattern for matching library names\n   * @return {@link LibraryInfo}\n   */\n  List<Object> functionList(byte[] libraryNamePattern);\n\n  /**\n   * Similar to {@link FunctionCommands#functionList() FUNCTION LIST} but include the\n   * libraries source implementation in the reply.\n   * @see FunctionCommands#functionList()\n   * @return {@link LibraryInfo}\n   */\n  List<Object> functionListWithCodeBinary();\n\n  /**\n   * Similar to {@link FunctionBinaryCommands#functionList(byte[]) FUNCTION LIST} but include the\n   * libraries source implementation in the reply.\n   * @see FunctionBinaryCommands#functionList(byte[])\n   * @param libraryNamePattern a pattern for matching library names\n   * @return {@link LibraryInfo}\n   */\n  List<Object> functionListWithCode(byte[] libraryNamePattern);\n\n  /**\n   * Load a library to Redis.\n   * <p>\n   * The library payload must start with Shebang statement that provides a metadata about the\n   * library (like the engine to use and the library name). Shebang format:\n   * {@code #!<engine name> name=<library name>}. Currently engine name must be lua.\n   *\n   * @param functionCode the source code.\n   * @return The library name that was loaded\n   */\n  String functionLoad(byte[] functionCode);\n\n  /**\n   * Load a library to Redis. Will replace the current library if it already exists.\n   * @param functionCode the source code\n   * @return The library name that was loaded\n   */\n  String functionLoadReplace(byte[] functionCode);\n\n  /**\n   * Restore libraries from the serialized payload. Default policy is APPEND.\n   * @param serializedValue the serialized payload\n   * @return OK\n   */\n  String functionRestore(byte[] serializedValue);\n\n  /**\n   * Restore libraries from the serialized payload.\n   * @param serializedValue the serialized payload\n   * @param policy can be {@link FunctionRestorePolicy FLUSH, APPEND or REPLACE}\n   * @return OK\n   */\n  String functionRestore(byte[] serializedValue, FunctionRestorePolicy policy);\n\n  /**\n   * Return information about the function that's currently running and information\n   * about the available execution engines.\n   * @return {@link FunctionStats}\n   */\n  Object functionStatsBinary();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/FunctionCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\n\nimport java.util.List;\n\npublic interface FunctionCommands {\n\n  /**\n   * Invoke a function.\n   * @param name\n   * @param keys\n   * @param args\n   */\n  Object fcall(String name, List<String> keys, List<String> args);\n\n  /**\n   * This is a read-only variant of the {@link FunctionCommands#fcall(String, List, List) FCALL}\n   * command that cannot execute commands that modify data.\n   */\n  Object fcallReadonly(String name, List<String> keys, List<String> args);\n\n  /**\n   * This command deletes the library called library-name and all functions in it.\n   * If the library doesn't exist, the server returns an error.\n   * @param libraryName\n   * @return OK\n   */\n  String functionDelete(String libraryName);\n\n  /**\n   * Return the serialized payload of loaded libraries. You can restore the\n   * serialized payload later with the {@link FunctionBinaryCommands#functionRestore(byte[], FunctionRestorePolicy) FUNCTION RESTORE} command.\n   * @return the serialized payload\n   */\n  byte[] functionDump();\n\n  /**\n   * Deletes all the libraries, unless called with the optional mode argument, the\n   * 'lazyfree-lazy-user-flush' configuration directive sets the effective behavior.\n   * @return OK\n   */\n  String functionFlush();\n\n  /**\n   * Deletes all the libraries, unless called with the optional mode argument, the\n   * 'lazyfree-lazy-user-flush' configuration directive sets the effective behavior.\n   * @param mode ASYNC: Asynchronously flush the libraries, SYNC: Synchronously flush the libraries.\n   * @return OK\n   */\n  String functionFlush(FlushMode mode);\n\n  /**\n   * Kill a function that is currently executing. The command can be used only on functions\n   * that did not modify the dataset during their execution.\n   * @return OK\n   */\n  String functionKill();\n\n  /**\n   * Return information about the functions and libraries.\n   * @return {@link LibraryInfo}\n   */\n  List<LibraryInfo> functionList();\n\n  /**\n   * Return information about the functions and libraries.\n   * @param libraryNamePattern a pattern for matching library names\n   * @return {@link LibraryInfo}\n   */\n  List<LibraryInfo> functionList(String libraryNamePattern);\n\n  /**\n   * Similar to {@link FunctionCommands#functionList() FUNCTION LIST} but include the\n   * libraries source implementation in the reply.\n   * @see FunctionCommands#functionList()\n   * @return {@link LibraryInfo}\n   */\n  List<LibraryInfo> functionListWithCode();\n\n  /**\n   * Similar to {@link FunctionCommands#functionList(String) FUNCTION LIST} but include the\n   * libraries source implementation in the reply.\n   * @see FunctionCommands#functionList(String)\n   * @param libraryNamePattern a pattern for matching library names\n   * @return {@link LibraryInfo}\n   */\n  List<LibraryInfo> functionListWithCode(String libraryNamePattern);\n\n  /**\n   * Load a library to Redis.\n   * <p>\n   * The library payload must start with Shebang statement that provides a metadata about the\n   * library (like the engine to use and the library name). Shebang format:\n   * {@code #!<engine name> name=<library name>}. Currently engine name must be lua.\n   *\n   * @param functionCode the source code.\n   * @return The library name that was loaded\n   */\n  String functionLoad(String functionCode);\n\n  /**\n   * Load a library to Redis. Will replace the current library if it already exists.\n   * @param functionCode the source code\n   * @return The library name that was loaded\n   */\n  String functionLoadReplace(String functionCode);\n\n  /**\n   * Restore libraries from the serialized payload. Default policy is APPEND.\n   * @param serializedValue the serialized payload\n   * @return OK\n   */\n  String functionRestore(byte[] serializedValue);\n\n  /**\n   * Restore libraries from the serialized payload.\n   * @param serializedValue the serialized payload\n   * @param policy can be {@link FunctionRestorePolicy FLUSH, APPEND or REPLACE}\n   * @return OK\n   */\n  String functionRestore(byte[] serializedValue, FunctionRestorePolicy policy);\n\n  /**\n   * Return information about the function that's currently running and information\n   * about the available execution engines.\n   * @return {@link FunctionStats}\n   */\n  FunctionStats functionStats();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/FunctionPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\n\nimport java.util.List;\n\npublic interface FunctionPipelineBinaryCommands {\n\n  Response<Object> fcall(byte[] name, List<byte[]> keys, List<byte[]> args);\n\n  Response<Object> fcallReadonly(byte[] name, List<byte[]> keys, List<byte[]> args);\n\n  Response<String> functionDelete(byte[] libraryName);\n\n  Response<byte[]> functionDump();\n\n  Response<String> functionFlush();\n\n  Response<String> functionFlush(FlushMode mode);\n\n  Response<String> functionKill();\n\n  Response<List<Object>> functionListBinary();\n\n  Response<List<Object>> functionList(byte[] libraryNamePattern);\n\n  Response<List<Object>> functionListWithCodeBinary();\n\n  Response<List<Object>> functionListWithCode(byte[] libraryNamePattern);\n\n  Response<String> functionLoad(byte[] functionCode);\n\n  Response<String> functionLoadReplace(byte[] functionCode);\n\n  Response<String> functionRestore(byte[] serializedValue);\n\n  Response<String> functionRestore(byte[] serializedValue, FunctionRestorePolicy policy);\n\n  Response<Object> functionStatsBinary();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/FunctionPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\n\nimport java.util.List;\n\npublic interface FunctionPipelineCommands {\n  \n  Response<Object> fcall(String name, List<String> keys, List<String> args);\n  \n  Response<Object> fcallReadonly(String name, List<String> keys, List<String> args);\n  \n  Response<String> functionDelete(String libraryName);\n\n  Response<byte[]> functionDump();\n\n  Response<String> functionFlush();\n  \n  Response<String> functionFlush(FlushMode mode);\n  \n  Response<String> functionKill();\n\n  Response<List<LibraryInfo>> functionList();\n\n  Response<List<LibraryInfo>> functionList(String libraryNamePattern);\n\n  Response<List<LibraryInfo>> functionListWithCode();\n\n  Response<List<LibraryInfo>> functionListWithCode(String libraryNamePattern);\n\n  Response<String> functionLoad(String functionCode);\n\n  Response<String> functionLoadReplace(String functionCode);\n\n  Response<String> functionRestore(byte[] serializedValue);\n\n  Response<String> functionRestore(byte[] serializedValue, FunctionRestorePolicy policy);\n\n  Response<FunctionStats> functionStats();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/GenericControlCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Module;\nimport redis.clients.jedis.params.FailoverParams;\n\npublic interface GenericControlCommands extends ConfigCommands, ScriptingControlCommands, SlowlogCommands {\n\n  String failover();\n\n  String failover(FailoverParams failoverParams);\n\n  String failoverAbort();\n\n  List<Module> moduleList();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/GeoBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic interface GeoBinaryCommands {\n\n  long geoadd(byte[] key, double longitude, double latitude, byte[] member);\n\n  long geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap);\n\n  long geoadd(byte[] key, GeoAddParams params, Map<byte[], GeoCoordinate> memberCoordinateMap);\n\n  Double geodist(byte[] key, byte[] member1, byte[] member2);\n\n  Double geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit);\n\n  List<byte[]> geohash(byte[] key, byte[]... members);\n\n  List<GeoCoordinate> geopos(byte[] key, byte[]... members);\n\n  List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude, double radius,\n      GeoUnit unit);\n\n  List<GeoRadiusResponse> georadiusReadonly(byte[] key, double longitude, double latitude,\n      double radius, GeoUnit unit);\n\n  List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  List<GeoRadiusResponse> georadiusReadonly(byte[] key, double longitude, double latitude,\n      double radius, GeoUnit unit, GeoRadiusParam param);\n\n  List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit);\n\n  List<GeoRadiusResponse> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit);\n\n  List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit,\n      GeoRadiusParam param);\n\n  List<GeoRadiusResponse> georadiusByMemberReadonly(byte[] key, byte[] member, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  long georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  long georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n  \n  List<GeoRadiusResponse> geosearch(byte[] key, byte[] member, double radius, GeoUnit unit);\n\n  List<GeoRadiusResponse> geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  List<GeoRadiusResponse> geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit);\n\n  List<GeoRadiusResponse> geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  List<GeoRadiusResponse> geosearch(byte[] key, GeoSearchParam params);\n\n  long geosearchStore(byte[] dest, byte[] src, byte[] member, double radius, GeoUnit unit);\n\n  long geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  long geosearchStore(byte[] dest, byte[] src, byte[] member, double width, double height, GeoUnit unit);\n\n  long geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  long geosearchStore(byte[] dest, byte[] src, GeoSearchParam params);\n\n  long geosearchStoreStoreDist(byte[] dest, byte[] src, GeoSearchParam params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/GeoCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic interface GeoCommands {\n\n  /**\n   * Adds the specified geospatial item (longitude, latitude, member) to the specified key.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the sorted set.\n   * @param key\n   * @param longitude\n   * @param latitude\n   * @param member\n   * @return The number of elements added\n   */\n  long geoadd(String key, double longitude, double latitude, String member);\n\n  /**\n   * Adds the specified geospatial items (in memberCoordinateMap) to the specified key.\n   * <p>\n   * Time complexity: O(log(N)) for each item added, where N is the number of elements in\n   * the sorted set.\n   * @param key\n   * @param memberCoordinateMap Members names with their geo coordinates\n   * @return The number of elements added\n   */\n  long geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap);\n\n  /**\n   * Adds the specified geospatial items (in memberCoordinateMap) to the specified key.\n   * Can be used with the following options:\n   * XX- Only update elements that already exist. Never add elements.\n   * NX- Don't update already existing elements. Always add new elements.\n   * CH- Modify the return value from the number of new elements added, to the total number of elements changed\n   * <p>\n   * Time complexity: O(log(N)) for each item added\n   * @param key\n   * @param params Additional options\n   * @param memberCoordinateMap Members names with their geo coordinates\n   * @return The number of elements added\n   */\n  long geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap);\n\n  /**\n   * Return the distance between two members in the geospatial index represented by the sorted set.\n   * <p>\n   * Time complexity: O(log(N))\n   * @param key\n   * @param member1\n   * @param member2\n   * @return The distance as a double\n   */\n  Double geodist(String key, String member1, String member2);\n\n  /**\n   * Return the distance between two members in the geospatial index represented by the sorted set.\n   * <p>\n   * Time complexity: O(log(N))\n   * @param key\n   * @param member1\n   * @param member2\n   * @param unit can be M, KM, MI or FT can  M, KM, MI or FT\n   * @return The distance as a double\n   */\n  Double geodist(String key, String member1, String member2, GeoUnit unit);\n\n  /**\n   * Return valid Geohash strings representing the position of the given members.\n   * <p>\n   * Time complexity: O(log(N)) for each member requested\n   * @param key\n   * @param members\n   * @return A list of Geohash strings corresponding to each member name passed as\n   * argument to the command.\n   */\n  List<String> geohash(String key, String... members);\n\n  /**\n   * Return the positions (longitude,latitude) of all the specified members.\n   * <p>\n   * Time complexity: O(N) where N is the number of members requested.\n   * @param key\n   * @param members\n   * @return A list of GeoCoordinate representing longitude and latitude (x,y)\n   * of each member name passed as argument to the command.\n   */\n  List<GeoCoordinate> geopos(String key, String... members);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified with the center location and the radius.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param longitude of the center point\n   * @param latitude of the center point\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadius(String key, double longitude, double latitude, double radius,\n      GeoUnit unit);\n\n  /**\n   * Readonly version of {@link GeoCommands#georadius(String, double, double, double, GeoUnit) GEORADIUS},\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @see GeoCommands#georadius(String, double, double, double, GeoUnit)\n   * @param key\n   * @param longitude of the center point\n   * @param latitude of the center point\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadiusReadonly(String key, double longitude, double latitude,\n      double radius, GeoUnit unit);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified with the center location and the radius.\n   * Additional information can be reached using {@link GeoRadiusParam}:\n   * WITHDIST: Also return the distance of the returned items from the specified center.\n   * The distance is returned in the same unit as the unit specified as the radius argument of the command.\n   * WITHCOORD: Also return the longitude,latitude coordinates of the matching items.\n   * WITHHASH: Also return the raw geohash-encoded sorted set score of the item, in the form of a 52\n   * bit unsigned integer. This is only useful for low level hacks or debugging and is otherwise of\n   * little interest for the general user.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param longitude of the center point\n   * @param latitude of the center point\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @param param {@link GeoRadiusParam}\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadius(String key, double longitude, double latitude, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  /**\n   * Readonly version of {@link GeoCommands#georadius(String, double, double, double, GeoUnit, GeoRadiusParam) GEORADIUS},\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @see GeoCommands#georadius(String, double, double, double, GeoUnit, GeoRadiusParam)\n   * @param key\n   * @param longitude of the center point\n   * @param latitude of the center point\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @param param {@link GeoRadiusParam}\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadiusReadonly(String key, double longitude, double latitude,\n      double radius, GeoUnit unit, GeoRadiusParam param);\n\n  /**\n   * This command is exactly like {@link GeoCommands#georadius(String, double, double, double, GeoUnit) GEORADIUS}\n   * with the sole difference that instead of taking, as the center of the area to query, a longitude\n   * and latitude value, it takes the name of a member already existing inside the geospatial index\n   * represented by the sorted set.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param member represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadiusByMember(String key, String member, double radius, GeoUnit unit);\n\n  /**\n   * Readonly version of {@link GeoCommands#georadiusByMember(String, String, double, GeoUnit) GEORADIUSBYMEMBER}\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param member represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit);\n\n  /**\n   * This command is exactly like {@link GeoCommands#georadius(String, double, double, double, GeoUnit, GeoRadiusParam) GEORADIUS}\n   * with the sole difference that instead of taking, as the center of the area to query, a longitude\n   * and latitude value, it takes the name of a member already existing inside the geospatial index\n   * represented by the sorted set.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param member represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @param param {@link GeoRadiusParam}\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadiusByMember(String key, String member, double radius, GeoUnit unit,\n      GeoRadiusParam param);\n\n  /**\n   * Readonly version of {@link GeoCommands#georadiusByMember(String, String, double, GeoUnit, GeoRadiusParam) GEORADIUSBYMEMBER}\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param member represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @param param {@link GeoRadiusParam}\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> georadiusByMemberReadonly(String key, String member, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  /**\n   * This command is exactly like {@link GeoCommands#georadius(String, double, double, double, GeoUnit, GeoRadiusParam) GEORADIUS}\n   * but storing the results at the destination key (provided with {@link GeoRadiusStoreParam storeParam}).\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param longitude of the center point\n   * @param latitude of the center point\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @param param {@link GeoRadiusParam}\n   * @param storeParam {@link GeoRadiusStoreParam}\n   * @return The number of results being stored\n   */\n  long georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  /**\n   * This command is exactly like {@link GeoCommands#georadiusByMember(String, String, double, GeoUnit, GeoRadiusParam) GEORADIUSBYMEMBER}\n   * but storing the results at the destination key (provided with {@link GeoRadiusStoreParam storeParam}).\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of\n   * the circular area delimited by center and radius and M is the number of items inside the index.\n   * @param key\n   * @param member represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @param param {@link GeoRadiusParam}\n   * @param storeParam {@link GeoRadiusStoreParam}\n   * @return The number of results being stored\n   */\n  long georadiusByMemberStore(String key, String member, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified by a given shape.\n   * <p>\n   * This command can be used in place of the {@link GeoCommands#georadiusByMember(String, String, double, GeoUnit) GEORADIUSBYMEMBER} command.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param key\n   * @param member represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> geosearch(String key, String member, double radius, GeoUnit unit);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified by a given shape.\n   * <p>\n   * This command can be used in place of the {@link GeoCommands#georadius(String, double, double, double, GeoUnit) GEORADIUS} command.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param key\n   * @param coord represents the center of the area\n   * @param radius of the area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified by a given shape. This command extends\n   * the GEORADIUS command, so in addition to searching within circular areas, it supports\n   * searching within rectangular areas.\n   * <p>\n   * The axis-aligned rectangle, determined by height and width, when the center point is\n   * determined by the position of the given member.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param key\n   * @param member represents the center of the area\n   * @param width of the rectangular area\n   * @param height of the rectangular area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> geosearch(String key, String member, double width, double height, GeoUnit unit);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified by a given shape. This command extends\n   * the GEORADIUS command, so in addition to searching within circular areas, it supports\n   * searching within rectangular areas.\n   * <p>\n   * The axis-aligned rectangle, determined by height and width, when the center point is\n   * determined by the given coordinate.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param key\n   * @param coord represents the center point\n   * @param width of the rectangular area\n   * @param height of the rectangular area\n   * @param unit can be M, KM, MI or FT\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  /**\n   * Return the members of a sorted set populated with geospatial information using GEOADD,\n   * which are within the borders of the area specified by a given shape. This command extends\n   * the GEORADIUS command, so in addition to searching within circular areas, it supports\n   * searching within rectangular areas.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param key\n   * @param params {@link GeoSearchParam}\n   * @return List of GeoRadiusResponse\n   */\n  List<GeoRadiusResponse> geosearch(String key, GeoSearchParam params);\n\n  /**\n   * This command is exactly like {@link GeoCommands#geosearch(String, String, double, GeoUnit) GEOSEARCH}\n   * but storing the results at dest.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param dest\n   * @param src the sorted set (key)\n   * @param member represents the center of the area\n   * @param radius of the circular area\n   * @param unit can be M, KM, MI or FT\n   * @return The number of results being stored\n   */\n  long geosearchStore(String dest, String src, String member, double radius, GeoUnit unit);\n\n  /**\n   * This command is exactly like {@link GeoCommands#geosearch(String, GeoCoordinate, double, GeoUnit) GEOSEARCH}\n   * but storing the results at dest.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param dest\n   * @param src\n   * @param coord represents the center point\n   * @param radius of the circular area\n   * @param unit can be M, KM, MI or FT\n   * @return The number of results being stored\n   */\n  long geosearchStore(String dest, String src, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  /**\n   * This command is exactly like {@link GeoCommands#geosearch(String, String, double, double, GeoUnit) GEOSEARCH}\n   * but storing the results at dest.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param dest\n   * @param src\n   * @param member represents the center of the area\n   * @param width of the rectangular area\n   * @param height of the rectangular area\n   * @param unit can be M, KM, MI or FT\n   * @return The number of results being stored\n   */\n  long geosearchStore(String dest, String src, String member, double width, double height, GeoUnit unit);\n\n  /**\n   * This command is exactly like {@link GeoCommands#geosearch(String, GeoCoordinate, double, double, GeoUnit) GEOSEARCH}\n   * but storing the results at dest.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param dest\n   * @param src\n   * @param coord represents the center point\n   * @param width of the rectangular area\n   * @param height of the rectangular area\n   * @param unit can be M, KM, MI or FT\n   * @return The number of results being stored\n   */\n  long geosearchStore(String dest, String src, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  /**\n   * This command is exactly like {@link GeoCommands#geosearch(String, GeoSearchParam) GEOSEARCH}\n   * but storing the results at dest.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param dest\n   * @param src\n   * @param params {@link GeoSearchParam}\n   * @return The number of results being stored\n   */\n  long geosearchStore(String dest, String src, GeoSearchParam params);\n\n  /**\n   * This command is exactly like {@link GeoCommands#geosearchStore(String, String, GeoSearchParam) GEOSEARCHSTORE}\n   * but storing the results with their destinations from the center point.\n   * <p>\n   * Time complexity: O(N+log(M)) where N is the number of elements in the grid-aligned\n   * bounding box area around the shape provided as the filter and M is the number of items\n   * inside the shape\n   * @param dest\n   * @param src\n   * @param params {@link GeoSearchParam}\n   * @return The number of results being stored\n   */\n  long geosearchStoreStoreDist(String dest, String src, GeoSearchParam params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/GeoPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic interface GeoPipelineBinaryCommands {\n\n  Response<Long> geoadd(byte[] key, double longitude, double latitude, byte[] member);\n\n  Response<Long> geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap);\n\n  Response<Long> geoadd(byte[] key, GeoAddParams params, Map<byte[], GeoCoordinate> memberCoordinateMap);\n\n  Response<Double> geodist(byte[] key, byte[] member1, byte[] member2);\n\n  Response<Double> geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit);\n\n  Response<List<byte[]>> geohash(byte[] key, byte[]... members);\n\n  Response<List<GeoCoordinate>> geopos(byte[] key, byte[]... members);\n\n  Response<List<GeoRadiusResponse>> georadius(byte[] key, double longitude, double latitude, double radius,\n      GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadiusReadonly(byte[] key, double longitude, double latitude,\n      double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadius(byte[] key, double longitude, double latitude, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  Response<List<GeoRadiusResponse>> georadiusReadonly(byte[] key, double longitude, double latitude,\n      double radius, GeoUnit unit, GeoRadiusParam param);\n\n  Response<List<GeoRadiusResponse>> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit,\n      GeoRadiusParam param);\n\n  Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(byte[] key, byte[] member, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  Response<Long> georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  Response<Long> georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  Response<List<GeoRadiusResponse>> geosearch(byte[] key, byte[] member, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(byte[] key, GeoSearchParam params);\n\n  Response<Long> geosearchStore(byte[] dest, byte[] src, byte[] member, double radius, GeoUnit unit);\n\n  Response<Long> geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  Response<Long> geosearchStore(byte[] dest, byte[] src, byte[] member, double width, double height, GeoUnit unit);\n\n  Response<Long> geosearchStore(byte[] dest, byte[] src, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  Response<Long> geosearchStore(byte[] dest, byte[] src, GeoSearchParam params);\n\n  Response<Long> geosearchStoreStoreDist(byte[] dest, byte[] src, GeoSearchParam params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/GeoPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic interface GeoPipelineCommands {\n\n  Response<Long> geoadd(String key, double longitude, double latitude, String member);\n\n  Response<Long> geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap);\n\n  Response<Long> geoadd(String key, GeoAddParams params, Map<String, GeoCoordinate> memberCoordinateMap);\n\n  Response<Double> geodist(String key, String member1, String member2);\n\n  Response<Double> geodist(String key, String member1, String member2, GeoUnit unit);\n\n  Response<List<String>> geohash(String key, String... members);\n\n  Response<List<GeoCoordinate>> geopos(String key, String... members);\n\n  Response<List<GeoRadiusResponse>> georadius(String key, double longitude, double latitude, double radius,\n      GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadiusReadonly(String key, double longitude, double latitude,\n      double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadius(String key, double longitude, double latitude, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  Response<List<GeoRadiusResponse>> georadiusReadonly(String key, double longitude, double latitude,\n      double radius, GeoUnit unit, GeoRadiusParam param);\n\n  Response<List<GeoRadiusResponse>> georadiusByMember(String key, String member, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> georadiusByMember(String key, String member, double radius, GeoUnit unit,\n      GeoRadiusParam param);\n\n  Response<List<GeoRadiusResponse>> georadiusByMemberReadonly(String key, String member, double radius,\n      GeoUnit unit, GeoRadiusParam param);\n\n  Response<Long> georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  Response<Long> georadiusByMemberStore(String key, String member, double radius, GeoUnit unit,\n      GeoRadiusParam param, GeoRadiusStoreParam storeParam);\n\n  Response<List<GeoRadiusResponse>> geosearch(String key, String member, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(String key, String member, double width, double height, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  Response<List<GeoRadiusResponse>> geosearch(String key, GeoSearchParam params);\n\n  Response<Long> geosearchStore(String dest, String src, String member, double radius, GeoUnit unit);\n\n  Response<Long> geosearchStore(String dest, String src, GeoCoordinate coord, double radius, GeoUnit unit);\n\n  Response<Long> geosearchStore(String dest, String src, String member, double width, double height, GeoUnit unit);\n\n  Response<Long> geosearchStore(String dest, String src, GeoCoordinate coord, double width, double height, GeoUnit unit);\n\n  Response<Long> geosearchStore(String dest, String src, GeoSearchParam params);\n\n  Response<Long> geosearchStoreStoreDist(String dest, String src, GeoSearchParam params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HashBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface HashBinaryCommands {\n\n  long hset(byte[] key, byte[] field, byte[] value);\n\n  long hset(byte[] key, Map<byte[], byte[]> hash);\n\n  /**\n   * Sets the specified fields in the hash stored at key to the specified values with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   * \n   * @param key the key of the hash\n   * @param params the parameters for the HSETEX command\n   * @param field the field in the hash\n   * @param value the value to set\n   * @return 0 if no fields were set, 1 if all the fields were set \n   * \n   * @see HSetExParams\n   */\n  long hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value);\n\n  /**\n   * Sets the specified fields in the hash stored at key to the specified values with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   * \n   * @param key the key of the hash\n   * @param params the parameters for the HSETEX command\n   * @param hash the map containing field-value pairs to set in the hash\n   * @return 0 if no fields were set, 1 if all the fields were set \n   * \n   * @see HSetExParams\n   */\n  long hsetex(byte[] key, HSetExParams params, Map<byte[], byte[]> hash);\n\n  byte[] hget(byte[] key, byte[] field);\n\n  /**\n   * Retrieves the values associated with the specified fields in a hash stored at the given key \n   * and optionally sets their expiration. Use `HGetExParams` object to specify expiration parameters.\n   *\n   * @param key the key of the hash\n   * @param params additional parameters for the HGETEX command\n   * @param fields the fields whose values are to be retrieved\n   * @return a list of the value associated with each field or nil if the field doesn’t exist.\n   * \n   * @see HGetExParams\n   */\n  List<byte[]> hgetex(byte[] key, HGetExParams params, byte[]... fields);\n\n  /**\n   * Retrieves the values associated with the specified fields in the hash stored at the given key\n   * and then deletes those fields from the hash.\n   *\n   * @param key the key of the hash\n   * @param fields the fields whose values are to be retrieved and then deleted\n   * @return a list of values associated with the specified fields before they were deleted\n   */\n  List<byte[]> hgetdel(byte[] key, byte[]... fields);\n\n  long hsetnx(byte[] key, byte[] field, byte[] value);\n\n  /**\n   * @deprecated Use {@link HashBinaryCommands#hset(byte[], Map)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  String hmset(byte[] key, Map<byte[], byte[]> hash);\n\n  List<byte[]> hmget(byte[] key, byte[]... fields);\n\n  long hincrBy(byte[] key, byte[] field, long value);\n\n  double hincrByFloat(byte[] key, byte[] field, double value);\n\n  boolean hexists(byte[] key, byte[] field);\n\n  long hdel(byte[] key, byte[]... field);\n\n  long hlen(byte[] key);\n\n  Set<byte[]> hkeys(byte[] key);\n\n  List<byte[]> hvals(byte[] key);\n\n  Map<byte[], byte[]> hgetAll(byte[] key);\n\n  byte[] hrandfield(byte[] key);\n\n  List<byte[]> hrandfield(byte[] key, long count);\n\n  List<Map.Entry<byte[], byte[]>> hrandfieldWithValues(byte[] key, long count);\n\n  default ScanResult<Map.Entry<byte[], byte[]>> hscan(byte[] key, byte[] cursor) {\n    return hscan(key, cursor, new ScanParams());\n  }\n\n  ScanResult<Map.Entry<byte[], byte[]>> hscan(byte[] key, byte[] cursor, ScanParams params);\n\n  default ScanResult<byte[]> hscanNoValues(byte[] key, byte[] cursor) {\n    return hscanNoValues(key, cursor, new ScanParams());\n  }\n\n  ScanResult<byte[]> hscanNoValues(byte[] key, byte[] cursor, ScanParams params);\n\n  long hstrlen(byte[] key, byte[] field);\n\n  /**\n   * Set expiry for hash field using relative time to expire (seconds).\n   *\n   * @param key hash\n   * @param seconds time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpire(byte[] key, long seconds, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using relative time to expire (seconds).\n   *\n   * @param key hash\n   * @param seconds time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using relative time to expire (milliseconds).\n   *\n   * @param key hash\n   * @param milliseconds time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpire(byte[] key, long milliseconds, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using relative time to expire (milliseconds).\n   *\n   * @param key hash\n   * @param milliseconds time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (seconds).\n   *\n   * @param key hash\n   * @param unixTimeSeconds time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (seconds).\n   *\n   * @param key hash\n   * @param unixTimeSeconds time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (milliseconds).\n   *\n   * @param key hash\n   * @param unixTimeMillis time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (milliseconds).\n   *\n   * @param key hash\n   * @param unixTimeMillis time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields);\n\n  /**\n   * Returns the expiration time of a hash field as a Unix timestamp, in seconds.\n   *\n   * @param key hash\n   * @param fields\n   * @return Expiration Unix timestamp in seconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hexpireTime(byte[] key, byte[]... fields);\n\n  /**\n   * Returns the expiration time of a hash field as a Unix timestamp, in milliseconds.\n   *\n   * @param key hash\n   * @param fields\n   * @return Expiration Unix timestamp in milliseconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hpexpireTime(byte[] key, byte[]... fields);\n\n  /**\n   * Returns the TTL in seconds of a hash field.\n   *\n   * @param key hash\n   * @param fields\n   * @return TTL in seconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> httl(byte[] key, byte[]... fields);\n\n  /**\n   * Returns the TTL in milliseconds of a hash field.\n   *\n   * @param key hash\n   * @param fields\n   * @return TTL in milliseconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hpttl(byte[] key, byte[]... fields);\n\n  /**\n   * Removes the expiration time for each specified field.\n   *\n   * @param key hash\n   * @param fields\n   * @return integer-reply: 1 if the expiration time was removed,\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hpersist(byte[] key, byte[]... fields);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HashCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface HashCommands {\n\n  long hset(String key, String field, String value);\n\n  long hset(String key, Map<String, String> hash);\n\n  /**\n   * Sets the specified fields in the hash stored at key to the specified values with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   * \n   * @param key the key of the hash\n   * @param params the parameters for the HSETEX command\n   * @param field the field in the hash\n   * @param value the value to set\n   * @return 0 if no fields were set, 1 if all the fields were set \n   * \n   * @see HSetExParams\n   */  \n  long hsetex(String key, HSetExParams params, String field, String value);\n\n  /**\n   * Sets the specified fields in the hash stored at key to the specified values with additional parameters,\n   * and optionally set their expiration. Use `HSetExParams` object to specify expiration parameters.\n   * This command can overwrite any existing fields in the hash.\n   * If key does not exist, a new key holding a hash is created.\n   * \n   * @param key the key of the hash\n   * @param params the parameters for the HSETEX command\n   * @param hash the map containing field-value pairs to set in the hash\n   * @return 0 if no fields were set, 1 if all the fields were set \n   * \n   * @see HSetExParams\n   */\n  long hsetex(String key, HSetExParams params, Map<String, String> hash);\n\n  String hget(String key, String field);\n    \n  /**\n   * Retrieves the values associated with the specified fields in a hash stored at the given key \n   * and optionally sets their expiration. Use `HGetExParams` object to specify expiration parameters.\n   *\n   * @param key the key of the hash\n   * @param params additional parameters for the HGETEX command\n   * @param fields the fields whose values are to be retrieved\n   * @return a list of the value associated with each field or nil if the field doesn’t exist.\n   * \n   * @see HGetExParams\n   */\n  List<String> hgetex(String key, HGetExParams params, String... fields);\n\n  /**\n   * Retrieves the values associated with the specified fields in the hash stored at the given key\n   * and then deletes those fields from the hash.\n   *\n   * @param key the key of the hash\n   * @param fields the fields whose values are to be retrieved and then deleted\n   * @return a list of values associated with the specified fields before they were deleted\n   */\n  List<String> hgetdel(String key, String... fields);\n  \n  long hsetnx(String key, String field, String value);\n\n  /**\n   * @deprecated Use {@link HashCommands#hset(String, Map)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 4.0.0.\n   */\n  @Deprecated\n  String hmset(String key, Map<String, String> hash);\n\n  List<String> hmget(String key, String... fields);\n\n  long hincrBy(String key, String field, long value);\n\n  double hincrByFloat(String key, String field, double value);\n\n  boolean hexists(String key, String field);\n\n  long hdel(String key, String... field);\n\n  long hlen(String key);\n\n  Set<String> hkeys(String key);\n\n  List<String> hvals(String key);\n\n  Map<String, String> hgetAll(String key);\n\n  String hrandfield(String key);\n\n  List<String> hrandfield(String key, long count);\n\n  List<Map.Entry<String, String>> hrandfieldWithValues(String key, long count);\n\n  default ScanResult<Map.Entry<String, String>> hscan(String key, String cursor) {\n    return hscan(key, cursor, new ScanParams());\n  }\n\n  ScanResult<Map.Entry<String, String>> hscan(String key, String cursor, ScanParams params);\n\n  default ScanResult<String> hscanNoValues(String key, String cursor) {\n    return hscanNoValues(key, cursor, new ScanParams());\n  }\n\n  ScanResult<String> hscanNoValues(String key, String cursor, ScanParams params);\n\n  long hstrlen(String key, String field);\n\n  /**\n   * Set expiry for hash field using relative time to expire (seconds).\n   *\n   * @param key hash\n   * @param seconds time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpire(String key, long seconds, String... fields);\n\n  /**\n   * Set expiry for hash field using relative time to expire (seconds).\n   *\n   * @param key hash\n   * @param seconds time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpire(String key, long seconds, ExpiryOption condition, String... fields);\n\n  /**\n   * Set expiry for hash field using relative time to expire (milliseconds).\n   *\n   * @param key hash\n   * @param milliseconds time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpire(String key, long milliseconds, String... fields);\n\n  /**\n   * Set expiry for hash field using relative time to expire (milliseconds).\n   *\n   * @param key hash\n   * @param milliseconds time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (seconds).\n   *\n   * @param key hash\n   * @param unixTimeSeconds time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpireAt(String key, long unixTimeSeconds, String... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (seconds).\n   *\n   * @param key hash\n   * @param unixTimeSeconds time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (milliseconds).\n   *\n   * @param key hash\n   * @param unixTimeMillis time to expire\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpireAt(String key, long unixTimeMillis, String... fields);\n\n  /**\n   * Set expiry for hash field using an absolute Unix timestamp (milliseconds).\n   *\n   * @param key hash\n   * @param unixTimeMillis time to expire\n   * @param condition can be NX, XX, GT or LT\n   * @param fields\n   * @return integer-reply: 1 if the timeout was set, 0 otherwise\n   */\n  List<Long> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields);\n\n  /**\n   * Returns the expiration time of a hash field as a Unix timestamp, in seconds.\n   *\n   * @param key hash\n   * @param fields\n   * @return Expiration Unix timestamp in seconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hexpireTime(String key, String... fields);\n\n  /**\n   * Returns the expiration time of a hash field as a Unix timestamp, in milliseconds.\n   *\n   * @param key hash\n   * @param fields\n   * @return Expiration Unix timestamp in milliseconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hpexpireTime(String key, String... fields);\n\n  /**\n   * Returns the TTL in seconds of a hash field.\n   *\n   * @param key hash\n   * @param fields\n   * @return TTL in seconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> httl(String key, String... fields);\n\n  /**\n   * Returns the TTL in milliseconds of a hash field.\n   *\n   * @param key hash\n   * @param fields\n   * @return TTL in milliseconds;\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hpttl(String key, String... fields);\n\n  /**\n   * Removes the expiration time for each specified field.\n   *\n   * @param key hash\n   * @param fields\n   * @return integer-reply: 1 if the expiration time was removed,\n   *         or -1 if the field exists but has no associated expire or -2 if the field does not exist.\n   */\n  List<Long> hpersist(String key, String... fields);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HashPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface HashPipelineBinaryCommands {\n\n  Response<Long> hset(byte[] key, byte[] field, byte[] value);\n\n  Response<Long> hset(byte[] key, Map<byte[], byte[]> hash);\n\n  Response<Long> hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value);\n\n  Response<Long> hsetex(byte[] key, HSetExParams params, Map<byte[], byte[]> hash);\n  \n  Response<byte[]> hget(byte[] key, byte[] field);\n\n  Response<List<byte[]>> hgetex(byte[] key, HGetExParams params, byte[]... fields);\n  \n  Response<List<byte[]>> hgetdel(byte[] key, byte[]... fields);\n  \n  Response<Long> hsetnx(byte[] key, byte[] field, byte[] value);\n\n  Response<String> hmset(byte[] key, Map<byte[], byte[]> hash);\n\n  Response<List<byte[]>> hmget(byte[] key, byte[]... fields);\n\n  Response<Long> hincrBy(byte[] key, byte[] field, long value);\n\n  Response<Double> hincrByFloat(byte[] key, byte[] field, double value);\n\n  Response<Boolean> hexists(byte[] key, byte[] field);\n\n  Response<Long> hdel(byte[] key, byte[]... field);\n\n  Response<Long> hlen(byte[] key);\n\n  Response<Set<byte[]>> hkeys(byte[] key);\n\n  Response<List<byte[]>> hvals(byte[] key);\n\n  Response<Map<byte[], byte[]>> hgetAll(byte[] key);\n\n  Response<byte[]> hrandfield(byte[] key);\n\n  Response<List<byte[]>> hrandfield(byte[] key, long count);\n\n  Response<List<Map.Entry<byte[], byte[]>>> hrandfieldWithValues(byte[] key, long count);\n\n  default Response<ScanResult<Map.Entry<byte[], byte[]>>> hscan(byte[] key, byte[] cursor) {\n    return hscan(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<Map.Entry<byte[], byte[]>>> hscan(byte[] key, byte[] cursor, ScanParams params);\n\n  default Response<ScanResult<byte[]>> hscanNoValues(byte[] key, byte[] cursor) {\n    return hscanNoValues(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<byte[]>> hscanNoValues(byte[] key, byte[] cursor, ScanParams params);\n\n  Response<Long> hstrlen(byte[] key, byte[] field);\n\n  Response<List<Long>> hexpire(byte[] key, long seconds, byte[]... fields);\n\n  Response<List<Long>> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields);\n\n  Response<List<Long>> hpexpire(byte[] key, long milliseconds, byte[]... fields);\n\n  Response<List<Long>> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields);\n\n  Response<List<Long>> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields);\n\n  Response<List<Long>> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields);\n\n  Response<List<Long>> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields);\n\n  Response<List<Long>> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields);\n\n  Response<List<Long>> hexpireTime(byte[] key, byte[]... fields);\n\n  Response<List<Long>> hpexpireTime(byte[] key, byte[]... fields);\n\n  Response<List<Long>> httl(byte[] key, byte[]... fields);\n\n  Response<List<Long>> hpttl(byte[] key, byte[]... fields);\n\n  Response<List<Long>> hpersist(byte[] key, byte[]... fields);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HashPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface HashPipelineCommands {\n\n  Response<Long> hset(String key, String field, String value);\n\n  Response<Long> hset(String key, Map<String, String> hash);\n\n  Response<Long> hsetex(String key, HSetExParams params, String field, String value);\n\n  Response<Long> hsetex(String key, HSetExParams params, Map<String, String> hash);\n\n  Response<String> hget(String key, String field);\n  \n  Response<List<String>> hgetex(String key, HGetExParams params, String... fields);\n\n  Response<List<String>> hgetdel(String key, String... fields);\n  \n  Response<Long> hsetnx(String key, String field, String value);\n\n  Response<String> hmset(String key, Map<String, String> hash);\n\n  Response<List<String>> hmget(String key, String... fields);\n\n  Response<Long> hincrBy(String key, String field, long value);\n\n  Response<Double> hincrByFloat(String key, String field, double value);\n\n  Response<Boolean> hexists(String key, String field);\n\n  Response<Long> hdel(String key, String... field);\n\n  Response<Long> hlen(String key);\n\n  Response<Set<String>> hkeys(String key);\n\n  Response<List<String>> hvals(String key);\n\n  Response<Map<String, String>> hgetAll(String key);\n\n  Response<String> hrandfield(String key);\n\n  Response<List<String>> hrandfield(String key, long count);\n\n  Response<List<Map.Entry<String, String>>> hrandfieldWithValues(String key, long count);\n\n  default Response<ScanResult<Map.Entry<String, String>>> hscan(String key, String cursor) {\n    return hscan(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<Map.Entry<String, String>>> hscan(String key, String cursor, ScanParams params);\n\n  default Response<ScanResult<String>> hscanNoValues(String key, String cursor) {\n    return hscanNoValues(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<String>> hscanNoValues(String key, String cursor, ScanParams params);\n\n  Response<Long> hstrlen(String key, String field);\n\n  Response<List<Long>> hexpire(String key, long seconds, String... fields);\n\n  Response<List<Long>> hexpire(String key, long seconds, ExpiryOption condition, String... fields);\n\n  Response<List<Long>> hpexpire(String key, long milliseconds, String... fields);\n\n  Response<List<Long>> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields);\n\n  Response<List<Long>> hexpireAt(String key, long unixTimeSeconds, String... fields);\n\n  Response<List<Long>> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields);\n\n  Response<List<Long>> hpexpireAt(String key, long unixTimeMillis, String... fields);\n\n  Response<List<Long>> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields);\n\n  Response<List<Long>> hexpireTime(String key, String... fields);\n\n  Response<List<Long>> hpexpireTime(String key, String... fields);\n\n  Response<List<Long>> httl(String key, String... fields);\n\n  Response<List<Long>> hpttl(String key, String... fields);\n\n  Response<List<Long>> hpersist(String key, String... fields);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HyperLogLogBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\npublic interface HyperLogLogBinaryCommands {\n\n  long pfadd(byte[] key, byte[]... elements);\n\n  String pfmerge(byte[] destkey, byte[]... sourcekeys);\n\n  long pfcount(byte[] key);\n\n  long pfcount(byte[]... keys);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HyperLogLogCommands.java",
    "content": "package redis.clients.jedis.commands;\n\npublic interface HyperLogLogCommands {\n\n  long pfadd(String key, String... elements);\n\n  String pfmerge(String destkey, String... sourcekeys);\n\n  long pfcount(String key);\n\n  long pfcount(String... keys);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HyperLogLogPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\n\npublic interface HyperLogLogPipelineBinaryCommands {\n\n  Response<Long> pfadd(byte[] key, byte[]... elements);\n\n  Response<String> pfmerge(byte[] destkey, byte[]... sourcekeys);\n\n  Response<Long> pfcount(byte[] key);\n\n  Response<Long> pfcount(byte[]... keys);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/HyperLogLogPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\n\npublic interface HyperLogLogPipelineCommands {\n\n  Response<Long> pfadd(String key, String... elements);\n\n  Response<String> pfmerge(String destkey, String... sourcekeys);\n\n  Response<Long> pfcount(String key);\n\n  Response<Long> pfcount(String... keys);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/JedisBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\npublic interface JedisBinaryCommands extends KeyBinaryCommands, StringBinaryCommands,\n    ListBinaryCommands, HashBinaryCommands, SetBinaryCommands, SortedSetBinaryCommands,\n    GeoBinaryCommands, HyperLogLogBinaryCommands, StreamBinaryCommands, ScriptingKeyBinaryCommands,\n    FunctionBinaryCommands, VectorSetBinaryCommands {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/JedisCommands.java",
    "content": "package redis.clients.jedis.commands;\n\npublic interface JedisCommands extends KeyCommands, StringCommands, ListCommands, HashCommands,\n    SetCommands, SortedSetCommands, GeoCommands, HyperLogLogCommands, StreamCommands,\n    ScriptingKeyCommands, FunctionCommands, VectorSetCommands {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/KeyBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface KeyBinaryCommands {\n\n  boolean exists(byte[] key);\n\n  long exists(byte[]... keys);\n\n  long persist(byte[] key);\n\n  String type(byte[] key);\n\n  byte[] dump(byte[] key);\n\n  String restore(byte[] key, long ttl, byte[] serializedValue);\n\n  String restore(byte[] key, long ttl, byte[] serializedValue, RestoreParams params);\n\n  long expire(byte[] key, long seconds);\n\n  long expire(byte[] key, long seconds, ExpiryOption expiryOption);\n\n  long pexpire(byte[] key, long milliseconds);\n\n  long pexpire(byte[] key, long milliseconds, ExpiryOption expiryOption);\n\n  long expireTime(byte[] key);\n\n  long pexpireTime(byte[] key);\n\n  long expireAt(byte[] key, long unixTime);\n\n  long expireAt(byte[] key, long unixTime, ExpiryOption expiryOption);\n\n  long pexpireAt(byte[] key, long millisecondsTimestamp);\n\n  long pexpireAt(byte[] key, long millisecondsTimestamp, ExpiryOption expiryOption);\n\n  long ttl(byte[] key);\n\n  long pttl(byte[] key);\n\n  long touch(byte[] key);\n\n  long touch(byte[]... keys);\n\n  List<byte[]> sort(byte[] key);\n\n  List<byte[]> sort(byte[] key, SortingParams sortingParams);\n\n  long del(byte[] key);\n\n  /**\n   * Experimental: Compare-and-delete guarded by value/digest condition.\n   */\n  @Experimental\n  long delex(byte[] key, CompareCondition condition);\n\n  /** Returns the 64-bit XXH3 digest hex (ASCII bytes) of the string value stored at key, or null if missing. */\n  @Experimental\n  byte[] digestKey(byte[] key);\n\n  long del(byte[]... keys);\n\n  long unlink(byte[] key);\n\n  long unlink(byte[]... keys);\n\n  boolean copy(byte[] srcKey, byte[] dstKey, boolean replace);\n\n  String rename(byte[] oldkey, byte[] newkey);\n\n  long renamenx(byte[] oldkey, byte[] newkey);\n\n  long sort(byte[] key, SortingParams sortingParams, byte[] dstkey);\n\n  long sort(byte[] key, byte[] dstkey);\n\n  List<byte[]> sortReadonly(byte[] key, SortingParams sortingParams);\n\n  Long memoryUsage(byte[] key);\n\n  Long memoryUsage(byte[] key, int samples);\n\n  Long objectRefcount(byte[] key);\n\n  byte[] objectEncoding(byte[] key);\n\n  Long objectIdletime(byte[] key);\n\n  Long objectFreq(byte[] key);\n\n  String migrate(String host, int port, byte[] key, int timeout);\n\n  String migrate(String host, int port, int timeout, MigrateParams params, byte[]... keys);\n\n  Set<byte[]> keys(byte[] pattern);\n\n  ScanResult<byte[]> scan(byte[] cursor);\n\n  ScanResult<byte[]> scan(byte[] cursor, ScanParams params);\n\n  ScanResult<byte[]> scan(byte[] cursor, ScanParams params, byte[] type);\n\n  byte[] randomBinaryKey();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/KeyCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface KeyCommands {\n\n  /**\n   * <b><a href=\"http://redis.io/commands/exists\">Exists Command</a></b>\n   * Test if the specified key exist.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return {@code true} if the key exists, {@code false} otherwise\n   */\n  boolean exists(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/exists\">Exists Command</a></b>\n   * Test if the specified keys exist.\n   * <p>\n   * Time complexity: O(N)\n   * @param keys\n   * @return The number of keys that exist from those specified as {@code keys}.\n   */\n  long exists(String... keys);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/persist\">Persist Command</a></b>\n   * Undo a {@link KeyCommands#expire(String, long) expire} at turning the expire key into a normal key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return 1 if the key is now persist. 0 otherwise (only happens when key not set)\n   */\n  long persist(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/type\">Type Command</a></b>\n   * Return the type of the value stored at key in form of a string. The type can be one of \"none\",\n   * \"string\", \"list\", \"set\". \"none\" is returned if the key does not exist.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return \"none\" if the key does not exist, \"string\" if the key contains a String value, \"list\"\n   * if the key contains a List value, \"set\" if the key contains a Set value, \"zset\" if the key\n   * contains a Sorted Set value, \"hash\" if the key contains a Hash value\n   */\n  String type(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/dump\">Dump Command</a></b>\n   * Serialize the value stored at key in a Redis-specific format and return it to the user.\n   * <p>\n   * Time complexity: O(1) to access the key and additional O(N*M) to serialize it where N is\n   * the number of Redis objects composing the value and M their average size.\n   * @param key\n   * @return The serialized value\n   */\n  byte[] dump(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/restore\">Restore Command</a></b>\n   * Create a key associated with a value that is obtained by deserializing the provided serialized\n   * value (obtained via {@link KeyCommands#dump(String) DUMP}).\n   * <p>\n   * Time complexity: O(1) to access the key and additional O(N*M) to serialize it where N is\n   * the number of Redis objects composing the value and M their average size.\n   * @param key\n   * @param ttl If ttl is 0 the key is created without any expire, otherwise the specified expire\n   *           time (in milliseconds) is set.\n   * @param serializedValue\n   * @return OK\n   */\n  String restore(String key, long ttl, byte[] serializedValue);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/restore\">Restore Command</a></b>\n   * Create a key associated with a value that is obtained by deserializing the provided serialized\n   * value (obtained via {@link KeyCommands#dump(String) DUMP}).\n   * <p>\n   * Time complexity: O(1) to access the key and additional O(N*M) to serialize it where N is\n   * the number of Redis objects composing the value and M their average size.\n   * @param key\n   * @param ttl If ttl is 0 the key is created without any expire, otherwise the specified expire\n   *           time (in milliseconds) is set.\n   * @param serializedValue\n   * @param params {@link RestoreParams}\n   * @return OK\n   */\n  String restore(String key, long ttl, byte[] serializedValue, RestoreParams params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/expire\">Expire Command</a></b>\n   * Set a timeout on the specified key. After the timeout the key will be automatically deleted by\n   * the server. A key with an associated timeout is said to be volatile in Redis terminology.\n   * <p>\n   * Volatile keys are stored on disk like the other keys, the timeout is persistent too like all\n   * the other aspects of the dataset. Saving a dataset containing expires and stopping the server\n   * does not stop the flow of time as Redis stores on disk the time when the key will no longer be\n   * available as Unix time, and not the remaining seconds.\n   * <p>\n   * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire\n   * set. It is also possible to undo the expire at all turning the key into a normal key using the\n   * {@link KeyCommands#persist(String) PERSIST} command.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param seconds time to expire\n   * @return 1 if the timeout was set, 0 otherwise. Since the key already has an associated timeout\n   * (this may happen only in Redis versions &lt; 2.1.3, Redis &gt;= 2.1.3 will happily update the timeout),\n   * or the key does not exist.\n   */\n  long expire(String key, long seconds);\n\n  /**\n   * Similar to {@link KeyCommands#expire(String, long) EXPIRE} but with optional expiry setting.\n   * @see KeyCommands#expire(String, long)\n   * @param key\n   * @param seconds time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise. Since the key already has an associated timeout\n   * (this may happen only in Redis versions &lt; 2.1.3, Redis &gt;= 2.1.3 will happily update the timeout),\n   * or the key does not exist.\n   */\n  long expire(String key, long seconds, ExpiryOption expiryOption);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/pexpire\">PExpire Command</a></b>\n   * This command works exactly like {@link KeyCommands#expire(String, long) EXPIRE} but the time\n   * to live of the key is specified in milliseconds instead of seconds.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param milliseconds time to expire\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  long pexpire(String key, long milliseconds);\n\n  /**\n   * Similar to {@link KeyCommands#pexpire(String, long) EXPIRE} but with optional expiry setting.\n   * @see KeyCommands#pexpire(String, long)\n   * @param key\n   * @param milliseconds time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  long pexpire(String key, long milliseconds, ExpiryOption expiryOption);\n\n\n  /**\n   * <b><a href=\"http://redis.io/commands/expireTime\">ExpireTime Command</a></b>\n   * Returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire.\n   * <p>\n   * The command returns -1 if the key exists but has no associated expiration time, and -2 if the key does not exist.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return Expiration Unix timestamp in seconds, or a negative value in order to signal an error:\n   * -1 if the key exists but has no associated expiration time, and -2 if the key does not exist.\n   */\n  long expireTime(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/pexpireTime\">PExpireTime Command</a></b>\n   * Similar to {@link KeyCommands#expireTime(String) EXPIRETIME} but returns the absolute Unix expiration\n   * timestamp in milliseconds instead of seconds.\n   * <p>\n   * Time complexity: O(1)\n   * @see KeyCommands#expireTime(String)\n   * @param key\n   * @return Expiration Unix timestamp in milliseconds, or a negative value in order to signal an error:\n   * -1 if the key exists but has no associated expiration time, and -2 if the key does not exist.\n   */\n  long pexpireTime(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/expireat\">ExpireAt Command</a></b>\n   * EXPIREAT works exactly like {@link KeyCommands#expire(String, long) EXPIRE} but instead to get the\n   * number of seconds representing the Time To Live of the key as a second argument (that is a\n   * relative way of specifying the TTL), it takes an absolute one in the form of a UNIX timestamp\n   * (Number of seconds elapsed since 1 Gen 1970).\n   * <p>\n   * EXPIREAT was introduced in order to implement the Append Only File persistence mode so that\n   * EXPIRE commands are automatically translated into EXPIREAT commands for the append only file.\n   * Of course EXPIREAT can also used by programmers that need a way to simply specify that a given\n   * key should expire at a given time in the future.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param unixTime time to expire\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  long expireAt(String key, long unixTime);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/expireat\">ExpireAt Command</a></b>\n   * Similar to {@link KeyCommands#expireAt(String, long) EXPIREAT} but with {@code ExpiryOption}.\n   * @see KeyCommands#expireAt(String, long)\n   * @param key\n   * @param unixTime time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  long expireAt(String key, long unixTime, ExpiryOption expiryOption);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/pexpireat\">PExpireAt Command</a></b>\n   * This command works exactly like {@link KeyCommands#expireAt(String, long) EXPIREAT} but\n   * Unix time at which the key will expire is specified in milliseconds instead of seconds.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param millisecondsTimestamp time to expire\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  long pexpireAt(String key, long millisecondsTimestamp);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/expireat\">ExpireAt Command</a></b>\n   * Similar to {@link KeyCommands#pexpireAt(String, long) PEXPIREAT} but with {@code ExpiryOption}.\n   * @see KeyCommands#pexpireAt(String, long)\n   * @param key\n   * @param millisecondsTimestamp time to expire\n   * @param expiryOption can be NX, XX, GT or LT\n   * @return 1 if the timeout was set, 0 otherwise.\n   * e.g. key doesn't exist, or operation skipped due to the provided arguments.\n   */\n  long pexpireAt(String key, long millisecondsTimestamp, ExpiryOption expiryOption);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/ttl\">TTL Command</a></b>\n   * The TTL command returns the remaining time to live in seconds of a key that has an\n   * {@link KeyCommands#expire(String, long) EXPIRE} set. This introspection capability allows a Redis\n   * connection to check how many seconds a given key will continue to be part of the dataset.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return TTL in seconds, or a negative value in order to signal an error\n   */\n  long ttl(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/pttl\">PTTL Command</a></b>\n   * The PTTL command returns the remaining time to live in milliseconds of a key that has an\n   * {@link KeyCommands#expire(String, long) EXPIRE} set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return TTL in milliseconds, or a negative value in order to signal an error\n   */\n  long pttl(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/touch\">Touch Command</a></b>\n   * Alters the last access time of a key. A key is ignored if it does not exist.\n   * <p>\n   * Time complexity: O(N) where N is the number of keys that will be touched.\n   * @param key\n   * @return The number of keys that were touched\n   */\n  long touch(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/touch\">Touch Command</a></b>\n   * Alters the last access time of a key(s). A key is ignored if it does not exist.\n   * <p>\n   * Time complexity: O(N) where N is the number of keys that will be touched.\n   * @param keys\n   * @return The number of keys that were touched\n   */\n  long touch(String... keys);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/sort\">Sort Command</a></b>\n   * Sort a Set or a List.\n   * <p>\n   * Sort the elements contained in the List, Set, or Sorted Set values at key. By default, sorting is\n   * numeric with elements being compared as double precision floating point numbers. This is the\n   * simplest form of SORT.\n   * @see KeyCommands#sort(String, SortingParams)\n   * @param key\n   * @return Assuming the Set/List at key contains a list of numbers, the return value will be the\n   *         list of numbers ordered from the smallest to the biggest number.\n   */\n  List<String> sort(String key);\n\n  /**\n   * Similar to {@link KeyCommands#sort(String) SORT} but store the result in {@code dstkey}.\n   * @see KeyCommands#sort(String)\n   * @param key\n   * @param dstkey\n   * @return The number of elements stored at dstkey.\n   */\n  long sort(String key, String dstkey);\n\n  /**\n   * Sort a Set or a List accordingly to the specified parameters.\n   * <p>\n   * <b>examples:</b>\n   * <p>\n   * Given are the following sets and key/values:\n   *\n   * <pre>\n   * x = [1, 2, 3]\n   * y = [a, b, c]\n   *\n   * k1 = z\n   * k2 = y\n   * k3 = x\n   *\n   * w1 = 9\n   * w2 = 8\n   * w3 = 7\n   * </pre>\n   *\n   * Sort Order:\n   *\n   * <pre>\n   * sort(x) or sort(x, sp.asc())\n   * -&gt; [1, 2, 3]\n   *\n   * sort(x, sp.desc())\n   * -&gt; [3, 2, 1]\n   *\n   * sort(y)\n   * -&gt; [c, a, b]\n   *\n   * sort(y, sp.alpha())\n   * -&gt; [a, b, c]\n   *\n   * sort(y, sp.alpha().desc())\n   * -&gt; [c, a, b]\n   * </pre>\n   *\n   * Limit (e.g. for Pagination):\n   *\n   * <pre>\n   * sort(x, sp.limit(0, 2))\n   * -&gt; [1, 2]\n   *\n   * sort(y, sp.alpha().desc().limit(1, 2))\n   * -&gt; [b, a]\n   * </pre>\n   *\n   * Sorting by external keys:\n   *\n   * <pre>\n   * sort(x, sb.by(w*))\n   * -&gt; [3, 2, 1]\n   *\n   * sort(x, sb.by(w*).desc())\n   * -&gt; [1, 2, 3]\n   * </pre>\n   *\n   * Getting external keys:\n   *\n   * <pre>\n   * sort(x, sp.by(w*).get(k*))\n   * -&gt; [x, y, z]\n   *\n   * sort(x, sp.by(w*).get(#).get(k*))\n   * -&gt; [3, x, 2, y, 1, z]\n   * </pre>\n   * @param key\n   * @param sortingParameters {@link SortingParams}\n   * @return A list of sorted elements\n   */\n  List<String> sort(String key, SortingParams sortingParameters);\n\n  /**\n   * Similar to {@link KeyCommands#sort(String, SortingParams) SORT} but store the result in {@code dstkey}.\n   * @see KeyCommands#sort(String, SortingParams)\n   * @param key\n   * @param sortingParameters {@link SortingParams}\n   * @param dstkey\n   * @return The number of elements stored at dstkey\n   */\n  long sort(String key, SortingParams sortingParameters, String dstkey);\n\n  /**\n   * Read-only variant of the {@link KeyCommands#sort(String, SortingParams) SORT} command.\n   * It is exactly like the original SORT but refuses the STORE option and can safely be used in read-only replicas.\n   * @param key the key to sort\n   * @param sortingParams {@link SortingParams}\n   * @return list of sorted elements.\n   */\n  List<String> sortReadonly(String key, SortingParams sortingParams);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/del\">Del Command</a></b>\n   * Remove the specified key. If a given key does not exist, no operation is performed.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return 1 if the key was removed, 0 if the key does not exist\n   */\n  long del(String key);\n\n  /**\n   * Remove the specified keys. If a given key does not exist, no operation is performed.\n   * <p>\n   * Time complexity: O(N)\n   * @param keys\n   * @return An integer greater than 0 if one or more keys were removed, 0 if none of the specified keys existed\n   */\n  long del(String... keys);\n\n  /**\n   * Experimental: Compare-and-delete guarded by value/digest condition.\n   */\n  @Experimental\n  long delex(String key, CompareCondition condition);\n\n  /**\n   * Compute and return the 64-bit XXH3 digest hex of the string value stored at key. Returns null\n   * if key does not exist.\n   */\n  @Experimental\n  String digestKey(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/unlink\">Unlink Command</a></b>\n   * This command is very similar to {@link KeyCommands#del(String) DEL}: it removes the specified key.\n   * Just like DEL a key is ignored if it does not exist. However, the command performs the actual\n   * memory reclaiming in a different thread, so it is not blocking, while DEL is. This is where the\n   * command name comes from: the command just unlinks the keys from the keyspace. The actual removal\n   * will happen later asynchronously.\n   * <p>\n   * Time complexity: O(1) for each key removed regardless of its size. Then the command does O(N)\n   * work in a different thread in order to reclaim memory, where N is the number of allocations the\n   * deleted objects where composed of.\n   * @param key\n   * @return The number of keys that were unlinked\n   */\n  long unlink(String key);\n\n  /**\n   * Similar to {@link KeyCommands#unlink(String) SORT} but can be used with multiple keys.\n   * @see KeyCommands#unlink(String)\n   * @param keys\n   * @return The number of keys that were unlinked\n   */\n  long unlink(String... keys);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/copy\">Copy Command</a></b>\n   * Copy the value stored at the source key to the destination key.\n   * @param srcKey the source key.\n   * @param dstKey the destination key.\n   * @param replace removes the destination key before copying the value to it, in order to avoid error.\n   * @return {@code true} if source was copied, {@code false} otherwise\n   */\n  boolean copy(String srcKey, String dstKey, boolean replace);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/rename\">Rename Command</a></b>\n   * Atomically renames the key {@code oldkey} to {@code newkey}. If the source and destination name are the same an\n   * error is returned. If {@code newkey} already exists it is overwritten.\n   * <p>\n   * Time complexity: O(1)\n   * @param oldkey\n   * @param newkey\n   * @return OK\n   */\n  String rename(String oldkey, String newkey);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/renamenx\">RenameNX Command</a></b>\n   * Rename oldkey into newkey but fails if the destination key newkey already exists.\n   * <p>\n   * Time complexity: O(1)\n   * @param oldkey\n   * @param newkey\n   * @return 1 if the key was renamed, 0 if the target key already exist\n   */\n  long renamenx(String oldkey, String newkey);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/memory-usage\">Memory Usage Command</a></b>\n   * Report the number of bytes that a key and its value require to be stored in RAM.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The memory usage in bytes\n   */\n  Long memoryUsage(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/memory-usage\">Memory Usage Command</a></b>\n   * Report the number of bytes that a key and its value require to be stored in RAM.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param samples the number of sampled nested values. By default, this option is set to 5.\n   *               To sample the all the nested values, use 0.\n   * @return The memory usage in bytes\n   */\n  Long memoryUsage(String key, int samples);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/object-refcount\">Object Refcount Command</a></b>\n   * Return the reference count of the stored at key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The number of references\n   */\n  Long objectRefcount(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/object-encoding\">Object Encoding Command</a></b>\n   * Return the internal encoding for the Redis object stored at key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The encoding of the object\n   */\n  String objectEncoding(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/object-idletime\">Object IdleTime Command</a></b>\n   * Return the time in seconds since the last access to the value stored at key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The idle time in seconds\n   */\n  Long objectIdletime(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/object-freq\">Object Freq Command</a></b>\n   * Return the logarithmic access frequency counter of a Redis object stored at key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The counter's value\n   */\n  Long objectFreq(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/migrate\">Migrate Command</a></b>\n   * Atomically transfer a key from a source Redis instance to a destination Redis instance.\n   * On success the key is deleted from the original instance and is guaranteed to exist in\n   * the target instance.\n   * @param host\n   * @param port\n   * @param key\n   * @param timeout the maximum idle time in any moment of the communication with the\n   *               destination instance in milliseconds.\n   * @return OK on success, or NOKEY if no keys were found in the source instance.\n   */\n  String migrate(String host, int port, String key, int timeout);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/migrate\">Migrate Command</a></b>\n   * Atomically transfer a key from a source Redis instance to a destination Redis instance.\n   * On success the key is deleted from the original instance and is guaranteed to exist in\n   * the target instance.\n   * @param host\n   * @param port\n   * @param timeout the maximum idle time in any moment of the communication with the\n   *               destination instance in milliseconds.\n   * @param params {@link MigrateParams}\n   * @param keys\n   * @return OK on success, or NOKEY if no keys were found in the source instance.\n   */\n  String migrate(String host, int port, int timeout, MigrateParams params, String... keys);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/keys\">Keys Command</a></b>\n   * Returns all the keys matching the glob-style pattern as space separated strings. For example if\n   * you have in the database the keys \"foo\" and \"foobar\" the command \"KEYS foo*\" will return\n   * \"foo foobar\".\n   * <p>\n   * Note that while the time complexity for this operation is O(n) the constant times are pretty\n   * low. For example Redis running on an entry level laptop can scan a 1 million keys database in\n   * 40 milliseconds. <b>Still it's better to consider this one of the slow commands that may ruin\n   * the DB performance if not used with care.</b>\n   * <p>\n   * In other words this command is intended only for debugging and special operations like creating\n   * a script to change the DB schema. Don't use it in your normal code. Use Redis Sets in order to\n   * group together a subset of objects.\n   * <p>\n   * Glob style patterns examples:\n   * <ul>\n   * <li>h?llo will match hello hallo hhllo\n   * <li>h*llo will match hllo heeeello\n   * <li>h[ae]llo will match hello and hallo, but not hillo\n   * </ul>\n   * <p>\n   * Use \\ to escape special chars if you want to match them verbatim.\n   * <p>\n   * Time complexity: O(n) (with n being the number of keys in the DB, and assuming keys and pattern\n   * of limited length)\n   * @param pattern\n   * @return List of keys matching the pattern.\n   */\n  Set<String> keys(String pattern);\n\n  ScanResult<String> scan(String cursor);\n\n  ScanResult<String> scan(String cursor, ScanParams params);\n\n  ScanResult<String> scan(String cursor, ScanParams params, String type);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/randomkey\">RandomKey Command</a></b>\n   * Return a randomly selected key from the currently selected DB.\n   * <p>\n   * Time complexity: O(1)\n   * @return The random key, or {@code nil} when the database is empty\n   */\n  String randomKey();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/KeyPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.CompareCondition;\n\npublic interface KeyPipelineBinaryCommands {\n\n  Response<Boolean> exists(byte[] key);\n\n  Response<Long> exists(byte[]... keys);\n\n  Response<Long> persist(byte[] key);\n\n  Response<String> type(byte[] key);\n\n  Response<byte[]> dump(byte[] key);\n\n  Response<String> restore(byte[] key, long ttl, byte[] serializedValue);\n\n  Response<String> restore(byte[] key, long ttl, byte[] serializedValue, RestoreParams params);\n\n  Response<Long> expire(byte[] key, long seconds);\n\n  Response<Long> expire(byte[] key, long seconds, ExpiryOption expiryOption);\n\n  Response<Long> pexpire(byte[] key, long milliseconds);\n\n  Response<Long> pexpire(byte[] key, long milliseconds, ExpiryOption expiryOption);\n\n  Response<Long> expireTime(byte[] key);\n\n  Response<Long> pexpireTime(byte[] key);\n\n  Response<Long> expireAt(byte[] key, long unixTime);\n\n  Response<Long> expireAt(byte[] key, long unixTime, ExpiryOption expiryOption);\n\n  Response<Long> pexpireAt(byte[] key, long millisecondsTimestamp);\n\n  Response<Long> pexpireAt(byte[] key, long millisecondsTimestamp, ExpiryOption expiryOption);\n\n  Response<Long> ttl(byte[] key);\n\n  Response<Long> pttl(byte[] key);\n\n  Response<Long> touch(byte[] key);\n\n  Response<Long> touch(byte[]... keys);\n\n  Response<List<byte[]>> sort(byte[] key);\n\n  Response<List<byte[]>> sort(byte[] key, SortingParams sortingParams);\n\n  Response<List<byte[]>> sortReadonly(byte[] key, SortingParams sortingParams);\n\n  Response<Long> del(byte[] key);\n\n  Response<Long> del(byte[]... keys);\n\n  Response<Long> delex(byte[] key, CompareCondition condition);\n\n  Response<byte[]> digestKey(byte[] key);\n\n  Response<Long> unlink(byte[] key);\n\n  Response<Long> unlink(byte[]... keys);\n\n  Response<Boolean> copy(byte[] srcKey, byte[] dstKey, boolean replace);\n\n  Response<String> rename(byte[] oldkey, byte[] newkey);\n\n  Response<Long> renamenx(byte[] oldkey, byte[] newkey);\n\n  Response<Long> sort(byte[] key, SortingParams sortingParams, byte[] dstkey);\n\n  Response<Long> sort(byte[] key, byte[] dstkey);\n\n  Response<Long> memoryUsage(byte[] key);\n\n  Response<Long> memoryUsage(byte[] key, int samples);\n\n  Response<Long> objectRefcount(byte[] key);\n\n  Response<byte[]> objectEncoding(byte[] key);\n\n  Response<Long> objectIdletime(byte[] key);\n\n  Response<Long> objectFreq(byte[] key);\n\n  Response<String> migrate(String host, int port, byte[] key, int timeout);\n\n  Response<String> migrate(String host, int port, int timeout, MigrateParams params, byte[]... keys);\n\n  Response<Set<byte[]>> keys(byte[] pattern);\n\n  Response<ScanResult<byte[]>> scan(byte[] cursor);\n\n  Response<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params);\n\n  Response<ScanResult<byte[]>> scan(byte[] cursor, ScanParams params, byte[] type);\n\n  Response<byte[]> randomBinaryKey();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/KeyPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.util.CompareCondition;\n\npublic interface KeyPipelineCommands {\n\n  Response<Boolean> exists(String key);\n\n  Response<Long> exists(String... keys);\n\n  Response<Long> persist(String key);\n\n  Response<String> type(String key);\n\n  Response<byte[]> dump(String key);\n\n  Response<String> restore(String key, long ttl, byte[] serializedValue);\n\n  Response<String> restore(String key, long ttl, byte[] serializedValue, RestoreParams params);\n\n  Response<Long> expire(String key, long seconds);\n\n  Response<Long> expire(String key, long seconds, ExpiryOption expiryOption);\n\n  Response<Long> pexpire(String key, long milliseconds);\n\n  Response<Long> pexpire(String key, long milliseconds, ExpiryOption expiryOption);\n\n  Response<Long> expireTime(String key);\n\n  Response<Long> pexpireTime(String key);\n\n  Response<Long> expireAt(String key, long unixTime);\n\n  Response<Long> expireAt(String key, long unixTime, ExpiryOption expiryOption);\n\n  Response<Long> pexpireAt(String key, long millisecondsTimestamp);\n\n  Response<Long> pexpireAt(String key, long millisecondsTimestamp, ExpiryOption expiryOption);\n\n  Response<Long> ttl(String key);\n\n  Response<Long> pttl(String key);\n\n  Response<Long> touch(String key);\n\n  Response<Long> touch(String... keys);\n\n  Response<List<String>> sort(String key);\n\n  Response<Long> sort(String key, String dstkey);\n\n  Response<List<String>> sort(String key, SortingParams sortingParams);\n\n  Response<Long> sort(String key, SortingParams sortingParams, String dstkey);\n\n  Response<List<String>> sortReadonly(String key, SortingParams sortingParams);\n\n  Response<Long> del(String key);\n\n  Response<Long> delex(String key, CompareCondition condition);\n\n  Response<String> digestKey(String key);\n\n  Response<Long> del(String... keys);\n\n  Response<Long> unlink(String key);\n\n  Response<Long> unlink(String... keys);\n\n  Response<Boolean> copy(String srcKey, String dstKey, boolean replace);\n\n  Response<String> rename(String oldkey, String newkey);\n\n  Response<Long> renamenx(String oldkey, String newkey);\n\n  Response<Long> memoryUsage(String key);\n\n  Response<Long> memoryUsage(String key, int samples);\n\n  Response<Long> objectRefcount(String key);\n\n  Response<String> objectEncoding(String key);\n\n  Response<Long> objectIdletime(String key);\n\n  Response<Long> objectFreq(String key);\n\n  Response<String> migrate(String host, int port, String key, int timeout);\n\n  Response<String> migrate(String host, int port, int timeout, MigrateParams params, String... keys);\n\n  Response<Set<String>> keys(String pattern);\n\n  Response<ScanResult<String>> scan(String cursor);\n\n  Response<ScanResult<String>> scan(String cursor, ScanParams params);\n\n  Response<ScanResult<String>> scan(String cursor, ScanParams params, String type);\n\n  Response<String> randomKey();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ListBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface ListBinaryCommands {\n\n  long rpush(byte[] key, byte[]... args);\n\n  long lpush(byte[] key, byte[]... args);\n\n  long llen(byte[] key);\n\n  List<byte[]> lrange(byte[] key, long start, long stop);\n\n  String ltrim(byte[] key, long start, long stop);\n\n  byte[] lindex(byte[] key, long index);\n\n  String lset(byte[] key, long index, byte[] value);\n\n  long lrem(byte[] key, long count, byte[] value);\n\n  byte[] lpop(byte[] key);\n\n  List<byte[]> lpop(byte[] key, int count);\n\n  Long lpos(byte[] key, byte[] element);\n\n  Long lpos(byte[] key, byte[] element, LPosParams params);\n\n  List<Long> lpos(byte[] key, byte[] element, LPosParams params, long count);\n\n  byte[] rpop(byte[] key);\n\n  List<byte[]> rpop(byte[] key, int count);\n\n  long linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value);\n\n  long lpushx(byte[] key, byte[]... args);\n\n  long rpushx(byte[] key, byte[]... args);\n\n  List<byte[]> blpop(int timeout, byte[]... keys);\n\n  KeyValue<byte[], byte[]> blpop(double timeout, byte[]... keys);\n\n  List<byte[]> brpop(int timeout, byte[]... keys);\n\n  KeyValue<byte[], byte[]> brpop(double timeout, byte[]... keys);\n\n  /**\n   * @deprecated Use {@link ListBinaryCommands#lmove(byte[], byte[], ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  byte[] rpoplpush(byte[] srckey, byte[] dstkey);\n\n  /**\n   * @deprecated Use {@link ListBinaryCommands#blmove(byte[], byte[], ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  byte[] brpoplpush(byte[] source, byte[] destination, int timeout);\n\n  byte[] lmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to);\n\n  byte[] blmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to, double timeout);\n\n  KeyValue<byte[], List<byte[]>> lmpop(ListDirection direction, byte[]... keys);\n\n  KeyValue<byte[], List<byte[]>> lmpop(ListDirection direction, int count, byte[]... keys);\n\n  KeyValue<byte[], List<byte[]>> blmpop(double timeout, ListDirection direction, byte[]... keys);\n\n  KeyValue<byte[], List<byte[]>> blmpop(double timeout, ListDirection direction, int count, byte[]... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ListCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface ListCommands {\n\n  /**\n   * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key\n   * does not exist an empty list is created just before the append operation. If the key exists but\n   * is not a List an error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param strings data to push\n   * @return The number of elements inside the list after the push operation\n   */\n  long rpush(String key, String... strings);\n\n  /**\n   * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key\n   * does not exist an empty list is created just before the append operation. If the key exists but\n   * is not a List an error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param strings data to push\n   * @return The number of elements inside the list after the push operation\n   */\n  long lpush(String key, String... strings);\n\n  /**\n   * Return the length of the list stored at the specified key. If the key does not exist zero is\n   * returned (the same behaviour as for empty lists). If the value stored at key is not a list an\n   * error is returned.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The length of the list\n   */\n  long llen(String key);\n\n  /**\n   * Return the specified elements of the list stored at the specified key. Start and end are\n   * zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and\n   * so on.\n   * <p>\n   * For example LRANGE foobar 0 2 will return the first three elements of the list.\n   * <p>\n   * start and end can also be negative numbers indicating offsets from the end of the list. For\n   * example -1 is the last element of the list, -2 the penultimate element and so on.\n   * <p>\n   * <b>Consistency with range functions in various programming languages</b>\n   * <p>\n   * Note that if you have a list of numbers from 0 to 100, LRANGE 0 10 will return 11 elements,\n   * that is, rightmost item is included. This may or may not be consistent with behavior of\n   * range-related functions in your programming language of choice (think Ruby's Range.new,\n   * Array#slice or Python's range() function).\n   * <p>\n   * LRANGE behavior is consistent with one of Tcl.\n   * <p>\n   * <b>Out-of-range indexes</b>\n   * <p>\n   * Indexes out of range will not produce an error: if start is over the end of the list, or start\n   * &gt; end, an empty list is returned. If end is over the end of the list Redis will threat it\n   * just like the last element of the list.\n   * <p>\n   * Time complexity: O(start+n) (with n being the length of the range and start being the start\n   * offset)\n   * @param key\n   * @param start\n   * @param stop\n   * @return A list of elements in the specified range\n   */\n  List<String> lrange(String key, long start, long stop);\n\n  /**\n   * Trim an existing list so that it will contain only the specified range of elements specified.\n   * Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the\n   * next element and so on.\n   * <p>\n   * For example LTRIM foobar 0 2 will modify the list stored at foobar key so that only the first\n   * three elements of the list will remain.\n   * <p>\n   * start and end can also be negative numbers indicating offsets from the end of the list. For\n   * example -1 is the last element of the list, -2 the penultimate element and so on.\n   * <p>\n   * Indexes out of range will not produce an error: if start is over the end of the list, or start\n   * &gt; end, an empty list is left as value. If end over the end of the list Redis will threat it\n   * just like the last element of the list.\n   * <p>\n   * Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:\n   * <p>\n   * {@code lpush(\"mylist\", \"someelement\"); ltrim(\"mylist\", 0, 99); * }\n   * <p>\n   * The above two commands will push elements in the list taking care that the list will not grow\n   * without limits. This is very useful when using Redis to store logs for example. It is important\n   * to note that when used in this way LTRIM is an O(1) operation because in the average case just\n   * one element is removed from the tail of the list.\n   * <p>\n   * Time complexity: O(n) (with n being len of list - len of range)\n   * @param key\n   * @param start\n   * @param stop\n   * @return OK\n   */\n  String ltrim(String key, long start, long stop);\n\n  /**\n   * Returns the element at index in the list stored at key.  0 is the first element, 1 the second\n   * and so on. Negative indexes are supported, for example -1 is the last element, -2 the penultimate\n   * and so on.\n   * <p>\n   * If the value stored at key is not of list type an error is returned. If the index is out of\n   * range a 'nil' reply is returned.\n   * <p>\n   * Note that even if the average time complexity is O(n) asking for the first or the last element\n   * of the list is O(1).\n   * <p>\n   * Time complexity: O(n) (with n being the length of the list)\n   * @param key\n   * @param index\n   * @return The requested element\n   */\n  String lindex(String key, long index);\n\n  /**\n   * Set a new value as the element at index position of the List at key.\n   * <p>\n   * Out of range indexes will generate an error.\n   * <p>\n   * Similarly to other list commands accepting indexes, the index can be negative to access\n   * elements starting from the end of the list. So -1 is the last element, -2 is the penultimate,\n   * and so forth.\n   * <p>\n   * Time Complexity O(N) when N being the length of the list. For the first or last elements of\n   * the list is O(1)\n   * @param key\n   * @param index\n   * @param value\n   * @return OK\n   */\n  String lset(String key, long index, String value);\n\n  /**\n   * Remove the first count occurrences of the value element from the list. If count is zero all the\n   * elements are removed. If count is negative elements are removed from tail to head, instead to\n   * go from head to tail that is the normal behaviour. So for example LREM with count -2 and hello\n   * as value to remove against the list (a,b,c,hello,x,hello,hello) will leave the list\n   * (a,b,c,hello,x). The number of removed elements is returned as an integer, see below for more\n   * information about the returned value. Note that non existing keys are considered like empty\n   * lists by LREM, so LREM against non existing keys will always return 0.\n   * <p>\n   * Time complexity: O(N) (with N being the length of the list)\n   * @param key\n   * @param count\n   * @param value\n   * @return The number of removed elements if the operation succeeded\n   */\n  long lrem(String key, long count, String value);\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned.\n   * @param key\n   * @return The popped element\n   */\n  String lpop(String key);\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * @param key\n   * @param count\n   * @return A list of popped elements, or 'nil' when key does not exist\n   */\n  List<String> lpop(String key, int count);\n\n  /**\n   * Returns the index of the first matching element inside a redis list. If the element is found,\n   * its index (the zero-based position in the list) is returned. Otherwise, if no match is found,\n   * 'nil' is returned.\n   * <p>\n   * Time complexity: O(N) where N is the number of elements in the list\n   * @param key\n   * @param element\n   * @return The index of first matching element in the list. Value will be 'nil' when the element\n   * is not present in the list\n   */\n  Long lpos(String key, String element);\n\n  /**\n   * In case there are multiple matches Rank option specifies the \"rank\" of the element to return.\n   * A rank of 1 returns the first match, 2 to return the second match, and so forth.\n   * If list `foo` has elements (\"a\",\"b\",\"c\",\"1\",\"2\",\"3\",\"c\",\"c\"), The function call to get the\n   * index of second occurrence of \"c\" will be as follows lpos(\"foo\",\"c\", LPosParams.lPosParams().rank(2)).\n   * <p>\n   * Maxlen option compares the element provided only with a given maximum number of list items.\n   * A value of 1000 will make sure that the command performs only 1000 comparisons. The\n   * comparison is made for the first part or the last part depending on the fact we use a positive or\n   * negative rank.\n   * Following is how we could use the Maxlen option lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(1).maxlen(2)).\n   * @param key\n   * @param element\n   * @param params {@link LPosParams}\n   * @return The integer representing the matching element, or 'nil' if there is no match\n   */\n  Long lpos(String key, String element, LPosParams params);\n\n  /**\n   * Returns the index of matching elements inside a Redis list. If the element is found, its index\n   * (the zero-based position in the list) is returned. Otherwise, if no match is found, nil is returned.\n   * <p>\n   * Time complexity: O(N) where N is the number of elements in the list\n   * @param key\n   * @param element\n   * @param params {@link LPosParams}\n   * @param count\n   * @return A list containing position of the matching elements inside the list\n   */\n  List<Long> lpos(String key, String element, LPosParams params, long count);\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * @param key\n   * @return The popped element\n   */\n  String rpop(String key);\n\n  /**\n   * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example\n   * if the list contains the elements \"a\",\"b\",\"c\" LPOP will return \"a\" and the list will become\n   * \"b\",\"c\".\n   * @param key\n   * @param count return up to count elements\n   * @return A list of count popped elements, or 'nil' when key does not exist.\n   */\n  List<String> rpop(String key, int count);\n\n  /**\n   * Inserts element in the list stored at key either before or after the reference value pivot.\n   * <p>\n   * When key does not exist, it is considered an empty list and no operation is performed.\n   * @param key\n   * @param where can be  BEFORE or AFTER\n   * @param pivot reference value\n   * @param value the value\n   * @return The length of the list after the insert operation, or -1 when the value pivot was not found\n   */\n  long linsert(String key, ListPosition where, String pivot, String value);\n\n  /**\n   * Inserts specified values at the head of the list stored at key. In contrary to\n   * {@link ListCommands#lpush(String, String...) LPUSH}, no operation will be performed when key\n   * does not yet exist.\n   * @param key\n   * @param strings the strings to push\n   * @return The length of the list after the push operation\n   */\n  long lpushx(String key, String... strings);\n\n  /**\n   * Inserts specified values at the tail of the list stored at key. In contrary to\n   * {@link ListCommands#rpush(String, String...) RPUSH}, no operation will be performed when key\n   * does not yet exist.\n   * @param key\n   * @param strings the strings to push\n   * @return The length of the list after the push operation\n   */\n  long rpushx(String key, String... strings);\n\n  /**\n   * The blocking version of {@link ListCommands#lpop(String)} LPOP} because it blocks the connection\n   * when there are no elements to pop from any of the given lists. An element is popped from the head of\n   * the first list that is non-empty, with the given keys being checked in the order that they are given.\n   * @param timeout the timeout argument is interpreted as a double value specifying the maximum number of\n   *               seconds to block. A timeout of zero can be used to block indefinitely.\n   * @param keys\n   */\n  List<String> blpop(int timeout, String... keys);\n\n  /**\n   * @see ListCommands#blpop(int, String...)\n   */\n  List<String> blpop(int timeout, String key);\n\n  /**\n   * The blocking version of {@link ListCommands#lpop(String)} LPOP} because it blocks the connection\n   * when there are no elements to pop from any of the given lists. An element is popped from the head of\n   * the first list that is non-empty, with the given keys being checked in the order that they are given.\n   * @param timeout the timeout argument is interpreted as a double value specifying the maximum number of\n   *               seconds to block. A timeout of zero can be used to block indefinitely.\n   * @param keys\n   */\n  KeyValue<String, String> blpop(double timeout, String... keys);\n\n\n  /**\n   * @see ListCommands#blpop(double, String...)\n   */\n  KeyValue<String, String> blpop(double timeout, String key);\n\n  /**\n   * The blocking version of {@link ListCommands#rpop(String)} RPOP} because it blocks the connection\n   * when there are no elements to pop from any of the given lists. An element is popped from the tail of\n   * the first list that is non-empty, with the given keys being checked in the order that they are given.\n   * @param timeout the timeout argument is interpreted as a double value specifying the maximum number of\n   *               seconds to block. A timeout of zero can be used to block indefinitely.\n   * @param keys\n   */\n  List<String> brpop(int timeout, String... keys);\n\n  /**\n   * @see ListCommands#brpop(int, String...)\n   */\n  List<String> brpop(int timeout, String key);\n\n  /**\n   * The blocking version of {@link ListCommands#rpop(String)} RPOP} because it blocks the connection\n   * when there are no elements to pop from any of the given lists. An element is popped from the tail of\n   * the first list that is non-empty, with the given keys being checked in the order that they are given.\n   * @param timeout the timeout argument is interpreted as a double value specifying the maximum number of\n   *               seconds to block. A timeout of zero can be used to block indefinitely.\n   * @param keys\n   */\n  KeyValue<String, String> brpop(double timeout, String... keys);\n\n  /**\n   * @see ListCommands#brpop(double, String...)\n   */\n  KeyValue<String, String> brpop(double timeout, String key);\n\n  /**\n   * Atomically return and remove the last (tail) element of the srckey list, and push the element\n   * as the first (head) element of the dstkey list. For example if the source list contains the\n   * elements \"a\",\"b\",\"c\" and the destination list contains the elements \"foo\",\"bar\" after an\n   * RPOPLPUSH command the content of the two lists will be \"a\",\"b\" and \"c\",\"foo\",\"bar\".\n   * <p>\n   * If the key does not exist or the list is already empty the special value 'nil' is returned. If\n   * the srckey and dstkey are the same the operation is equivalent to removing the last element\n   * from the list and pushing it as first element of the list, so it's a \"list rotation\" command.\n   * <p>\n   * Time complexity: O(1)\n   * @param srckey\n   * @param dstkey\n   * @return Bulk reply\n   * @deprecated Use {@link ListCommands#lmove(String, String, ListDirection, ListDirection)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  String rpoplpush(String srckey, String dstkey);\n\n  /**\n   * The blocking variant of {@link ListCommands#rpoplpush(String, String)}. When source is\n   * empty, Redis will block the connection until another client pushes to it or until timeout is\n   * reached. A timeout of zero can be used to block indefinitely.\n   * <p>\n   * Time complexity: O(1)\n   * @param source\n   * @param destination\n   * @param timeout the timeout argument is interpreted as a double value specifying the maximum number of\n   *               seconds to block. A timeout of zero can be used to block indefinitely.\n   * @return The element being popped from source and pushed to destination\n   * @deprecated Use {@link ListCommands#blmove(String, String, ListDirection, ListDirection, double)} with\n   * {@link ListDirection#RIGHT} and {@link ListDirection#LEFT}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  String brpoplpush(String source, String destination, int timeout);\n\n  /**\n   * Pop an element from a list, push it to another list and return it\n   * @param srcKey\n   * @param dstKey\n   * @param from can be LEFT or RIGHT\n   * @param to can be LEFT or RIGHT\n   * @return The element being popped and pushed\n   */\n  String lmove(String srcKey, String dstKey, ListDirection from, ListDirection to);\n\n  /**\n   * Pop an element from a list, push it to another list and return it; or block until one is available\n   * @param srcKey\n   * @param dstKey\n   * @param from can be LEFT or RIGHT\n   * @param to can be LEFT or RIGHT\n   * @param timeout the timeout argument is interpreted as a double value specifying the maximum number of\n   *               seconds to block. A timeout of zero can be used to block indefinitely.\n   * @return The element being popped and pushed\n   */\n  String blmove(String srcKey, String dstKey, ListDirection from, ListDirection to, double timeout);\n\n  KeyValue<String, List<String>> lmpop(ListDirection direction, String... keys);\n\n  KeyValue<String, List<String>> lmpop(ListDirection direction, int count, String... keys);\n\n  KeyValue<String, List<String>> blmpop(double timeout, ListDirection direction, String... keys);\n\n  KeyValue<String, List<String>> blmpop(double timeout, ListDirection direction, int count, String... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ListPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface ListPipelineBinaryCommands {\n\n  Response<Long> rpush(byte[] key, byte[]... args);\n\n  Response<Long> lpush(byte[] key, byte[]... args);\n\n  Response<Long> llen(byte[] key);\n\n  Response<List<byte[]>> lrange(byte[] key, long start, long stop);\n\n  Response<String> ltrim(byte[] key, long start, long stop);\n\n  Response<byte[]> lindex(byte[] key, long index);\n\n  Response<String> lset(byte[] key, long index, byte[] value);\n\n  Response<Long> lrem(byte[] key, long count, byte[] value);\n\n  Response<byte[]> lpop(byte[] key);\n\n  Response<List<byte[]>> lpop(byte[] key, int count);\n\n  Response<Long> lpos(byte[] key, byte[] element);\n\n  Response<Long> lpos(byte[] key, byte[] element, LPosParams params);\n\n  Response<List<Long>> lpos(byte[] key, byte[] element, LPosParams params, long count);\n\n  Response<byte[]> rpop(byte[] key);\n\n  Response<List<byte[]>> rpop(byte[] key, int count);\n\n  Response<Long> linsert(byte[] key, ListPosition where, byte[] pivot, byte[] value);\n\n  Response<Long> lpushx(byte[] key, byte[]... args);\n\n  Response<Long> rpushx(byte[] key, byte[]... args);\n\n  Response<List<byte[]>> blpop(int timeout, byte[]... keys);\n\n  Response<KeyValue<byte[], byte[]>> blpop(double timeout, byte[]... keys);\n\n  Response<List<byte[]>> brpop(int timeout, byte[]... keys);\n\n  Response<KeyValue<byte[], byte[]>> brpop(double timeout, byte[]... keys);\n\n  Response<byte[]> rpoplpush(byte[] srckey, byte[] dstkey);\n\n  Response<byte[]> brpoplpush(byte[] source, byte[] destination, int timeout);\n\n  Response<byte[]> lmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to);\n\n  Response<byte[]> blmove(byte[] srcKey, byte[] dstKey, ListDirection from, ListDirection to, double timeout);\n\n  Response<KeyValue<byte[], List<byte[]>>> lmpop(ListDirection direction, byte[]... keys);\n\n  Response<KeyValue<byte[], List<byte[]>>> lmpop(ListDirection direction, int count, byte[]... keys);\n\n  Response<KeyValue<byte[], List<byte[]>>> blmpop(double timeout, ListDirection direction, byte[]... keys);\n\n  Response<KeyValue<byte[], List<byte[]>>> blmpop(double timeout, ListDirection direction, int count, byte[]... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ListPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface ListPipelineCommands {\n\n  Response<Long> rpush(String key, String... string);\n\n  Response<Long> lpush(String key, String... string);\n\n  Response<Long> llen(String key);\n\n  Response<List<String>> lrange(String key, long start, long stop);\n\n  Response<String> ltrim(String key, long start, long stop);\n\n  Response<String> lindex(String key, long index);\n\n  Response<String> lset(String key, long index, String value);\n\n  Response<Long> lrem(String key, long count, String value);\n\n  Response<String> lpop(String key);\n\n  Response<List<String>> lpop(String key, int count);\n\n  Response<Long> lpos(String key, String element);\n\n  Response<Long> lpos(String key, String element, LPosParams params);\n\n  Response<List<Long>> lpos(String key, String element, LPosParams params, long count);\n\n  Response<String> rpop(String key);\n\n  Response<List<String>> rpop(String key, int count);\n\n  Response<Long> linsert(String key, ListPosition where, String pivot, String value);\n\n  Response<Long> lpushx(String key, String... strings);\n\n  Response<Long> rpushx(String key, String... strings);\n\n  Response<List<String>> blpop(int timeout, String key);\n\n  Response<KeyValue<String, String>> blpop(double timeout, String key);\n\n  Response<List<String>> brpop(int timeout, String key);\n\n  Response<KeyValue<String, String>> brpop(double timeout, String key);\n\n  Response<List<String>> blpop(int timeout, String... keys);\n\n  Response<KeyValue<String, String>> blpop(double timeout, String... keys);\n\n  Response<List<String>> brpop(int timeout, String... keys);\n\n  Response<KeyValue<String, String>> brpop(double timeout, String... keys);\n\n  Response<String> rpoplpush(String srckey, String dstkey);\n\n  Response<String> brpoplpush(String source, String destination, int timeout);\n\n  Response<String> lmove(String srcKey, String dstKey, ListDirection from, ListDirection to);\n\n  Response<String> blmove(String srcKey, String dstKey, ListDirection from, ListDirection to, double timeout);\n\n  Response<KeyValue<String, List<String>>> lmpop(ListDirection direction, String... keys);\n\n  Response<KeyValue<String, List<String>>> lmpop(ListDirection direction, int count, String... keys);\n\n  Response<KeyValue<String, List<String>>> blmpop(double timeout, ListDirection direction, String... keys);\n\n  Response<KeyValue<String, List<String>>> blmpop(double timeout, ListDirection direction, int count, String... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ModuleCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Module;\nimport redis.clients.jedis.params.ModuleLoadExParams;\n\npublic interface ModuleCommands {\n\n  /**\n   * Load and initialize the Redis module from the dynamic library specified by the path argument.\n   *\n   * @param path should be the absolute path of the library, including the full filename\n   * @return OK\n   */\n  String moduleLoad(String path);\n\n  /**\n   * Load and initialize the Redis module from the dynamic library specified by the path argument.\n   *\n   * @param path should be the absolute path of the library, including the full filename\n   * @param args additional arguments are passed unmodified to the module\n   * @return OK\n   */\n  String moduleLoad(String path, String... args);\n\n  /**\n   * Loads a module from a dynamic library at runtime with configuration directives.\n   * <p>\n   * This is an extended version of the MODULE LOAD command.\n   * <p>\n   * It loads and initializes the Redis module from the dynamic library specified by the path\n   * argument. The path should be the absolute path of the library, including the full filename.\n   * <p>\n   * You can use the optional CONFIG argument to provide the module with configuration directives.\n   * Any additional arguments that follow the ARGS keyword are passed unmodified to the module.\n   *\n   * @param path should be the absolute path of the library, including the full filename\n   * @param params as in description\n   * @return OK\n   */\n  String moduleLoadEx(String path, ModuleLoadExParams params);\n\n  /**\n   * Unload the module specified by name. Note that the module's name is reported by the\n   * {@link ModuleCommands#moduleList() MODULE LIST} command, and may differ from the dynamic\n   * library's filename.\n   *\n   * @param name\n   * @return OK\n   */\n  String moduleUnload(String name);\n\n  /**\n   * Return information about the modules loaded to the server.\n   *\n   * @return list of {@link Module}\n   */\n  List<Module> moduleList();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/PipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\npublic interface PipelineBinaryCommands extends KeyPipelineBinaryCommands,\n    StringPipelineBinaryCommands, ListPipelineBinaryCommands, HashPipelineBinaryCommands,\n    SetPipelineBinaryCommands, SortedSetPipelineBinaryCommands, GeoPipelineBinaryCommands,\n    HyperLogLogPipelineBinaryCommands, StreamPipelineBinaryCommands,\n    ScriptingKeyPipelineBinaryCommands, SampleBinaryKeyedPipelineCommands, FunctionPipelineBinaryCommands,\n    VectorSetPipelineBinaryCommands {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/PipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\npublic interface PipelineCommands extends KeyPipelineCommands, StringPipelineCommands,\n    ListPipelineCommands, HashPipelineCommands, SetPipelineCommands, SortedSetPipelineCommands,\n    GeoPipelineCommands, HyperLogLogPipelineCommands, StreamPipelineCommands,\n    ScriptingKeyPipelineCommands, SampleKeyedPipelineCommands, FunctionPipelineCommands,\n    VectorSetPipelineCommands {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ProtocolCommand.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.Rawable;\n\npublic interface ProtocolCommand extends Rawable {\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/RedisModuleCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.bloom.commands.RedisBloomCommands;\nimport redis.clients.jedis.json.commands.RedisJsonCommands;\nimport redis.clients.jedis.search.RediSearchCommands;\nimport redis.clients.jedis.timeseries.RedisTimeSeriesCommands;\n\npublic interface RedisModuleCommands extends\n    RediSearchCommands,\n    RedisJsonCommands,\n    RedisTimeSeriesCommands,\n    RedisBloomCommands {\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/RedisModulePipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.bloom.commands.RedisBloomPipelineCommands;\nimport redis.clients.jedis.json.commands.RedisJsonPipelineCommands;\nimport redis.clients.jedis.search.RediSearchPipelineCommands;\nimport redis.clients.jedis.timeseries.RedisTimeSeriesPipelineCommands;\n\npublic interface RedisModulePipelineCommands extends\n    RediSearchPipelineCommands,\n    RedisJsonPipelineCommands,\n    RedisTimeSeriesPipelineCommands,\n    RedisBloomPipelineCommands {\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SampleBinaryKeyedCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SampleBinaryKeyedCommands {\n\n  long waitReplicas(byte[] sampleKey, int replicas, long timeout);\n\n  KeyValue<Long, Long> waitAOF(byte[] sampleKey, long numLocal, long numReplicas, long timeout);\n\n  Object eval(byte[] script, byte[] sampleKey);\n\n  Object evalsha(byte[] sha1, byte[] sampleKey);\n\n  Boolean scriptExists(byte[] sha1, byte[] sampleKey);\n\n  List<Boolean> scriptExists(byte[] sampleKey, byte[]... sha1s);\n\n  byte[] scriptLoad(byte[] script, byte[] sampleKey);\n\n  String scriptFlush(byte[] sampleKey);\n\n  String scriptFlush(byte[] sampleKey, FlushMode flushMode);\n\n  String scriptKill(byte[] sampleKey);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SampleBinaryKeyedPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SampleBinaryKeyedPipelineCommands {\n\n  Response<Long> waitReplicas(byte[] sampleKey, int replicas, long timeout);\n\n  Response<KeyValue<Long, Long>> waitAOF(byte[] sampleKey, long numLocal, long numReplicas, long timeout);\n\n  Response<Object> eval(byte[] script, byte[] sampleKey);\n\n  Response<Object> evalsha(byte[] sha1, byte[] sampleKey);\n//\n//  Response<Boolean> scriptExists(byte[] sha1, byte[] sampleKey);\n\n  Response<List<Boolean>> scriptExists(byte[] sampleKey, byte[]... sha1s);\n\n  Response<byte[]> scriptLoad(byte[] script, byte[] sampleKey);\n\n  Response<String> scriptFlush(byte[] sampleKey);\n\n  Response<String> scriptFlush(byte[] sampleKey, FlushMode flushMode);\n\n  Response<String> scriptKill(byte[] sampleKey);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SampleKeyedCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SampleKeyedCommands {\n\n  long waitReplicas(String sampleKey, int replicas, long timeout);\n\n  KeyValue<Long, Long> waitAOF(String sampleKey, long numLocal, long numReplicas, long timeout);\n\n  Object eval(String script, String sampleKey);\n\n  Object evalsha(String sha1, String sampleKey);\n\n  Boolean scriptExists(String sha1, String sampleKey);\n\n  List<Boolean> scriptExists(String sampleKey, String... sha1s);\n\n  String scriptLoad(String script, String sampleKey);\n\n  String scriptFlush(String sampleKey);\n\n  String scriptFlush(String sampleKey, FlushMode flushMode);\n\n  String scriptKill(String sampleKey);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SampleKeyedPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SampleKeyedPipelineCommands {\n\n  Response<Long> waitReplicas(String sampleKey, int replicas, long timeout);\n\n  Response<KeyValue<Long, Long>> waitAOF(String sampleKey, long numLocal, long numReplicas, long timeout);\n\n  Response<Object> eval(String script, String sampleKey);\n\n  Response<Object> evalsha(String sha1, String sampleKey);\n//\n//  Response<Boolean> scriptExists(String sha1, String sampleKey);\n\n  Response<List<Boolean>> scriptExists(String sampleKey, String... sha1);\n\n  Response<String> scriptLoad(String script, String sampleKey);\n\n  Response<String> scriptFlush(String sampleKey);\n\n  Response<String> scriptFlush(String sampleKey, FlushMode flushMode);\n\n  Response<String> scriptKill(String sampleKey);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ScriptingControlCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.args.FlushMode;\n\npublic interface ScriptingControlCommands {\n\n  Boolean scriptExists(String sha1);\n\n  List<Boolean> scriptExists(String... sha1);\n\n  Boolean scriptExists(byte[] sha1);\n\n  List<Boolean> scriptExists(byte[]... sha1);\n\n  String scriptLoad(String script);\n\n  byte[] scriptLoad(byte[] script);\n\n  String scriptFlush();\n\n  String scriptFlush(FlushMode flushMode);\n\n  String scriptKill();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ScriptingKeyBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\npublic interface ScriptingKeyBinaryCommands {\n\n  Object eval(byte[] script);\n\n  Object eval(byte[] script, int keyCount, byte[]... params);\n\n  Object eval(byte[] script, List<byte[]> keys, List<byte[]> args);\n\n  Object evalReadonly(byte[] script, List<byte[]> keys, List<byte[]> args);\n\n  Object evalsha(byte[] sha1);\n\n  Object evalsha(byte[] sha1, int keyCount, byte[]... params);\n\n  Object evalsha(byte[] sha1, List<byte[]> keys, List<byte[]> args);\n\n  Object evalshaReadonly(byte[] sha1, List<byte[]> keys, List<byte[]> args);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ScriptingKeyCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\npublic interface ScriptingKeyCommands {\n\n  /**\n   * <b><a href=\"http://redis.io/commands/eval\">Eval Command</a></b>\n   * Use to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0.\n   * @param script Lua 5.1 script. The script does not need to define a Lua function (and should not).\n   *              It is just a Lua program that will run in the context of the Redis server.\n   * @return The result of the evaluated script\n   */\n  Object eval(String script);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/eval\">Eval Command</a></b>\n   * Use to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0.\n   * @param script Lua 5.1 script. The script does not need to define a Lua function (and should not).\n   *              It is just a Lua program that will run in the context of the Redis server.\n   * @param keyCount the count of the provided keys\n   * @param params arguments that can be accessed from the script\n   * @return The result of the evaluated script\n   */\n  Object eval(String script, int keyCount, String... params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/eval\">Eval Command</a></b>\n   * Use to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0.\n   * @param script Lua 5.1 script. The script does not need to define a Lua function (and should not).\n   *              It is just a Lua program that will run in the context of the Redis server.\n   * @param keys arguments that can be accessed by the script\n   * @param args additional arguments should not represent key names and can be accessed by the script\n   * @return The result of the evaluated script\n   */\n  Object eval(String script, List<String> keys, List<String> args);\n\n  /**\n   * Readonly version of {@link ScriptingKeyCommands#eval(String, List, List) EVAL}\n   * @see ScriptingKeyCommands#eval(String, List, List)\n   * @param script Lua 5.1 script. The script does not need to define a Lua function (and should not).\n   *              It is just a Lua program that will run in the context of the Redis server.\n   * @param keys arguments that can be accessed by the script\n   * @param args additional arguments should not represent key names and can be accessed by the script\n   * @return The result of the evaluated script\n   */\n  Object evalReadonly(String script, List<String> keys, List<String> args);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/evalsha\">EvalSha Command</a></b>\n   * Similar to {@link ScriptingKeyCommands#eval(String) EVAL}, but the script cached on the server\n   * side by its SHA1 digest. Scripts are cached on the server side using the SCRIPT LOAD command.\n   * @see ScriptingKeyCommands#eval(String)\n   * @param sha1 the script\n   * @return The result of the evaluated script\n   */\n  Object evalsha(String sha1);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/evalsha\">EvalSha Command</a></b>\n   * Similar to {@link ScriptingKeyCommands#eval(String, int, String...)}  EVAL}, but the script cached on the server\n   * side by its SHA1 digest. Scripts are cached on the server side using the SCRIPT LOAD command.\n   * @see ScriptingKeyCommands#eval(String, int, String...)\n   * @param sha1 the script\n   * @return The result of the evaluated script\n   */\n  Object evalsha(String sha1, int keyCount, String... params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/evalsha\">EvalSha Command</a></b>\n   * Similar to {@link ScriptingKeyCommands#eval(String, List, List)}  EVAL}, but the script cached on the server\n   * side by its SHA1 digest. Scripts are cached on the server side using the SCRIPT LOAD command.\n   * @see ScriptingKeyCommands#eval(String, List, List)\n   * @param sha1 the script\n   * @return The result of the evaluated script\n   */\n  Object evalsha(String sha1, List<String> keys, List<String> args);\n\n  /**\n   * Readonly version of {@link ScriptingKeyCommands#evalsha(String, List, List) EVAL}\n   * @see ScriptingKeyCommands#evalsha(String, List, List)\n   * @param sha1 the script\n   * @return The result of the evaluated script\n   */\n  Object evalshaReadonly(String sha1, List<String> keys, List<String> args);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ScriptingKeyPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Response;\n\npublic interface ScriptingKeyPipelineBinaryCommands {\n\n  Response<Object> eval(byte[] script);\n\n  Response<Object> eval(byte[] script, int keyCount, byte[]... params);\n\n  Response<Object> eval(byte[] script, List<byte[]> keys, List<byte[]> args);\n\n  Response<Object> evalReadonly(byte[] script, List<byte[]> keys, List<byte[]> args);\n\n  Response<Object> evalsha(byte[] sha1);\n\n  Response<Object> evalsha(byte[] sha1, int keyCount, byte[]... params);\n\n  Response<Object> evalsha(byte[] sha1, List<byte[]> keys, List<byte[]> args);\n\n  Response<Object> evalshaReadonly(byte[] sha1, List<byte[]> keys, List<byte[]> args);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ScriptingKeyPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Response;\n\npublic interface ScriptingKeyPipelineCommands {\n\n  Response<Object> eval(String script);\n\n  Response<Object> eval(String script, int keyCount, String... params);\n\n  Response<Object> eval(String script, List<String> keys, List<String> args);\n\n  Response<Object> evalReadonly(String script, List<String> keys, List<String> args);\n\n  Response<Object> evalsha(String sha1);\n\n  Response<Object> evalsha(String sha1, int keyCount, String... params);\n\n  Response<Object> evalsha(String sha1, List<String> keys, List<String> args);\n\n  Response<Object> evalshaReadonly(String sha1, List<String> keys, List<String> args);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SentinelCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\n//Legacy\npublic interface SentinelCommands {\n\n  String sentinelMyId();\n\n  List<Map<String, String>> sentinelMasters();\n\n  Map<String, String> sentinelMaster(String masterName);\n\n  List<Map<String, String>> sentinelSentinels(String masterName);\n\n  List<String> sentinelGetMasterAddrByName(String masterName);\n\n  Long sentinelReset(String pattern);\n\n  /**\n   * @deprecated Use {@link SentinelCommands#sentinelReplicas(java.lang.String)}.\n   */\n  @Deprecated\n  List<Map<String, String>> sentinelSlaves(String masterName);\n\n  List<Map<String, String>> sentinelReplicas(String masterName);\n\n  String sentinelFailover(String masterName);\n\n  String sentinelMonitor(String masterName, String ip, int port, int quorum);\n\n  String sentinelRemove(String masterName);\n\n  String sentinelSet(String masterName, Map<String, String> parameterMap);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/ServerCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.LatencyEvent;\nimport redis.clients.jedis.args.SaveMode;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.params.HotkeysParams;\nimport redis.clients.jedis.params.LolwutParams;\nimport redis.clients.jedis.params.ShutdownParams;\nimport redis.clients.jedis.resps.HotkeysInfo;\nimport redis.clients.jedis.resps.LatencyHistoryInfo;\nimport redis.clients.jedis.resps.LatencyLatestInfo;\nimport redis.clients.jedis.util.KeyValue;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic interface ServerCommands {\n\n  /**\n   * This command is often used to test if a connection is still alive, or to measure latency.\n   * @return PONG\n   */\n  String ping();\n\n  String ping(String message);\n\n  String echo(String string);\n\n  byte[] echo(byte[] arg);\n\n  /**\n   * Delete all the keys of the currently selected DB. This command never fails. The time-complexity\n   * for this operation is O(N), N being the number of keys in the database.\n   * @return OK\n   */\n  String flushDB();\n\n  /**\n   * Delete all the keys of the currently selected DB. This command never fails. The time-complexity\n   * for this operation is O(N), N being the number of keys in the database.\n   * @param flushMode can be SYNC or ASYNC\n   * @return OK\n   */\n  String flushDB(FlushMode flushMode);\n\n  /**\n   * Delete all the keys of all the existing databases, not just the currently selected one.\n   * @return a simple string reply (OK)\n   */\n  String flushAll();\n\n  /**\n   * Delete all the keys of all the existing databases, not just the currently selected one.\n   * @param flushMode SYNC or ASYNC\n   * @return a simple string reply (OK)\n   */\n  String flushAll(FlushMode flushMode);\n\n  /**\n   * Request for authentication in a password-protected Redis server. Redis can be instructed to\n   * require a password before allowing clients to execute commands. This is done using the\n   * requirepass directive in the configuration file. If password matches the password in the\n   * configuration file, the server replies with the OK status code and starts accepting commands.\n   * Otherwise, an error is returned and the clients needs to try a new password.\n   * @return the result of the auth\n   */\n  String auth(String password);\n\n  /**\n   * Request for authentication with username and password, based on the ACL feature introduced in\n   * Redis 6.0 see https://redis.io/topics/acl\n   * @return OK\n   */\n  String auth(String user, String password);\n\n  /**\n   * The SAVE commands performs a synchronous save of the dataset producing a point in time snapshot\n   * of all the data inside the Redis instance, in the form of an RDB file. You almost never want to\n   * call SAVE in production environments where it will block all the other clients. Instead usually\n   * BGSAVE is used. However, in case of issues preventing Redis to create the background saving\n   * child (for instance errors in the fork(2) system call), the SAVE command can be a good last\n   * resort to perform the dump of the latest dataset.\n   * @return result of the save\n   */\n  String save();\n\n  /**\n   * Save the DB in background. The OK code is immediately returned. Redis forks, the parent\n   * continues to serve the clients, the child saves the DB on disk then exits. A client may be able\n   * to check if the operation succeeded using the LASTSAVE command.\n   * @return ok\n   */\n  String bgsave();\n\n  String bgsaveSchedule();\n\n  /**\n   * Instruct Redis to start an Append Only File rewrite process. The rewrite will create a small\n   * optimized version of the current Append Only File If BGREWRITEAOF fails, no data gets lost as\n   * the old AOF will be untouched. The rewrite will be only triggered by Redis if there is not\n   * already a background process doing persistence. Specifically: If a Redis child is creating a\n   * snapshot on disk, the AOF rewrite is scheduled but not started until the saving child producing\n   * the RDB file terminates. In this case the BGREWRITEAOF will still return an OK code, but with\n   * an appropriate message. You can check if an AOF rewrite is scheduled looking at the INFO\n   * command as of Redis 2.6. If an AOF rewrite is already in progress the command returns an error\n   * and no AOF rewrite will be scheduled for a later time. Since Redis 2.4 the AOF rewrite is\n   * automatically triggered by Redis, however the BGREWRITEAOF command can be used to trigger a\n   * rewrite at any time.\n   * @return the response of the command\n   */\n  String bgrewriteaof();\n\n  /**\n   * Return the UNIX TIME of the last DB save executed with success.\n   * @return the unix latest save\n   */\n  long lastsave();\n\n  /**\n   * Stop all the client. Perform a SAVE (if one save point is configured). Flush the append only\n   * file if AOF is enabled quit the server\n   * @throws JedisException only in case of error.\n   */\n  void shutdown() throws JedisException;\n\n  default void shutdown(SaveMode saveMode) throws JedisException {\n    shutdown(ShutdownParams.shutdownParams().saveMode(saveMode));\n  }\n\n  /**\n   * @see SaveMode\n   * @param shutdownParams set commands parameters\n   * @throws JedisException\n   */\n  void shutdown(ShutdownParams shutdownParams) throws JedisException;\n\n  String shutdownAbort();\n\n  /**\n   * The INFO command returns information and statistics about the server in a format that is simple\n   * to parse by computers and easy to read by humans.\n   * @return information on the server\n   */\n  String info();\n\n  /**\n   * The INFO command returns information and statistics about the server in a format that is simple\n   * to parse by computers and easy to read by humans.\n   * @param section (all: Return all sections, default: Return only the default set of sections,\n   *          server: General information about the Redis server, clients: Client connections\n   *          section, memory: Memory consumption related information, persistence: RDB and AOF\n   *          related information, stats: General statistics, replication: Master/slave replication\n   *          information, cpu: CPU consumption statistics, commandstats: Redis command statistics,\n   *          cluster: Redis Cluster section, keyspace: Database related statistics)\n   * @return info\n   */\n  String info(String section);\n\n  /**\n   * The SLAVEOF command can change the replication settings of a slave on the fly. In the proper\n   * form SLAVEOF hostname port will make the server a slave of another server listening at the\n   * specified hostname and port. If a server is already a slave of some master, SLAVEOF hostname\n   * port will stop the replication against the old server and start the synchronization against the\n   * new one, discarding the old dataset.\n   * @param host listening at the specified hostname\n   * @param port server listening at the specified port\n   * @return result of the command.\n   * @deprecated Use {@link ServerCommands#replicaof(java.lang.String, int)}.\n   */\n  @Deprecated\n  String slaveof(String host, int port);\n\n  /**\n   * SLAVEOF NO ONE will stop replication, turning the server into a MASTER, but will not discard\n   * the replication. So, if the old master stops working, it is possible to turn the slave into a\n   * master and set the application to use this new master in read/write. Later when the other Redis\n   * server is fixed, it can be reconfigured to work as a slave.\n   * @return result of the command\n   * @deprecated Use {@link ServerCommands#replicaofNoOne()}.\n   */\n  @Deprecated\n  String slaveofNoOne();\n\n  /**\n   * The REPLICAOF command can change the replication settings of a replica on the fly. In the\n   * proper form REPLICAOF hostname port will make the server a replica of another server\n   * listening at the specified hostname and port. If a server is already a replica of some master,\n   * REPLICAOF hostname port will stop the replication against the old server and start the\n   * synchronization against the new one, discarding the old dataset.\n   * @param host listening at the specified hostname\n   * @param port server listening at the specified port\n   * @return result of the command.\n   */\n  String replicaof(String host, int port);\n\n  /**\n   * REPLICAOF NO ONE will stop replication, turning the server into a MASTER, but will not discard\n   * the replication. So, if the old master stops working, it is possible to turn the replica into\n   * a master and set the application to use this new master in read/write. Later when the other\n   * Redis server is fixed, it can be reconfigured to work as a replica.\n   * @return result of the command\n   */\n  String replicaofNoOne();\n\n  /**\n   * Synchronous replication of Redis as described here: http://antirez.com/news/66.\n   * <p>\n   * Blocks until all the previous write commands are successfully transferred and acknowledged by\n   * at least the specified number of replicas. If the timeout, specified in milliseconds, is\n   * reached, the command returns even if the specified number of replicas were not yet reached.\n   * <p>\n   * Since Java Object class has implemented {@code wait} method, we cannot use it.\n   * @param replicas successfully transferred and acknowledged by at least the specified number of\n   *          replicas\n   * @param timeout the time to block in milliseconds, a timeout of 0 means to block forever\n   * @return the number of replicas reached by all the writes performed in the context of the\n   *         current connection\n   */\n  long waitReplicas(int replicas, long timeout);\n\n  /**\n   * Blocks the current client until all the previous write commands are acknowledged as having been\n   * fsynced to the AOF of the local Redis and/or at least the specified number of replicas.\n   * <a href=\"https://redis.io/commands/waitaof/\">Redis Documentation</a>\n   * @param numLocal Number of local instances that are required to acknowledge the sync (0 or 1),\n   *                 cannot be non-zero if the local Redis does not have AOF enabled\n   * @param numReplicas Number of replicas that are required to acknowledge the sync\n   * @param timeout Timeout in millis of the operation - if 0 timeout is unlimited. If the timeout is reached,\n   *                the command returns even if the specified number of acknowledgments has not been met.\n   * @return KeyValue where Key is number of local Redises (0 or 1) that have fsynced to AOF all writes\n   * performed in the context of the current connection, and the value is the number of replicas that have acknowledged doing the same.\n   */\n  KeyValue<Long, Long> waitAOF(long numLocal, long numReplicas, long timeout);\n\n  String lolwut();\n\n  String lolwut(LolwutParams lolwutParams);\n\n  String reset();\n\n  /**\n   * The LATENCY DOCTOR command reports about different latency-related issues and advises about\n   * possible remedies.\n   * <p>\n   * This command is the most powerful analysis tool in the latency monitoring framework, and is\n   * able to provide additional statistical data like the average period between latency spikes, the\n   * median deviation, and a human-readable analysis of the event. For certain events, like fork,\n   * additional information is provided, like the rate at which the system forks processes.\n   * <p>\n   * This is the output you should post in the Redis mailing list if you are looking for help about\n   * Latency related issues.\n   *\n   * @return the report\n   */\n  String latencyDoctor();\n\n  Map<String, LatencyLatestInfo> latencyLatest();\n\n  List<LatencyHistoryInfo> latencyHistory(LatencyEvent events);\n\n  long latencyReset(LatencyEvent... events);\n\n  /**\n   * Start hotkey tracking with the specified parameters.\n   * <p>\n   * The HOTKEYS command is used to identify hot keys in your Redis instance by tracking\n   * keys by CPU time consumption and network bytes transferred.\n   * <p>\n   * <b>Note: This command is not supported in Redis Cluster mode.</b> HOTKEYS is a node-local\n   * operation and there is no built-in mechanism to aggregate hotkeys data across cluster nodes.\n   * Calling this method on a cluster client will throw {@link UnsupportedOperationException}.\n   * To use HOTKEYS in a cluster, connect directly to individual nodes.\n   *\n   * @param params the parameters for hotkey tracking (metrics, count, duration, sample)\n   * @return OK\n   * @throws UnsupportedOperationException if called on a cluster client\n   */\n  String hotkeysStart(HotkeysParams params);\n\n  /**\n   * Stop hotkey tracking. Data is preserved until RESET or next START.\n   * <p>\n   * <b>Note: This command is not supported in Redis Cluster mode.</b>\n   * See {@link #hotkeysStart(HotkeysParams)} for details.\n   *\n   * @return OK\n   * @throws UnsupportedOperationException if called on a cluster client\n   */\n  String hotkeysStop();\n\n  /**\n   * Clear all collected hotkey data and return to empty state.\n   * <p>\n   * <b>Note: This command is not supported in Redis Cluster mode.</b>\n   * See {@link #hotkeysStart(HotkeysParams)} for details.\n   *\n   * @return OK\n   * @throws UnsupportedOperationException if called on a cluster client\n   */\n  String hotkeysReset();\n\n  /**\n   * Retrieve collected hotkey statistics.\n   * <p>\n   * Returns null if hotkey tracking has never been started.\n   * <p>\n   * <b>Note: This command is not supported in Redis Cluster mode.</b>\n   * See {@link #hotkeysStart(HotkeysParams)} for details.\n   *\n   * @return HotkeysInfo containing tracking statistics, or null if never started\n   * @throws UnsupportedOperationException if called on a cluster client\n   */\n  HotkeysInfo hotkeysGet();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SetBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface SetBinaryCommands {\n\n  long sadd(byte[] key, byte[]... members);\n\n  Set<byte[]> smembers(byte[] key);\n\n  long srem(byte[] key, byte[]... members);\n\n  byte[] spop(byte[] key);\n\n  Set<byte[]> spop(byte[] key, long count);\n\n  long scard(byte[] key);\n\n  boolean sismember(byte[] key, byte[] member);\n\n  List<Boolean> smismember(byte[] key, byte[]... members);\n\n  byte[] srandmember(byte[] key);\n\n  List<byte[]> srandmember(byte[] key, int count);\n\n  default ScanResult<byte[]> sscan(byte[] key, byte[] cursor) {\n    return sscan(key, cursor, new ScanParams());\n  }\n\n  ScanResult<byte[]> sscan(byte[] key, byte[] cursor, ScanParams params);\n\n  Set<byte[]> sdiff(byte[]... keys);\n\n  long sdiffstore(byte[] dstkey, byte[]... keys);\n\n  Set<byte[]> sinter(byte[]... keys);\n\n  long sinterstore(byte[] dstkey, byte[]... keys);\n\n  /**\n   * This command works exactly like {@link SetBinaryCommands#sinter(byte[][]) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result. LIMIT defaults to 0 and means unlimited\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param keys\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  long sintercard(byte[]... keys);\n\n  /**\n   * This command works exactly like {@link SetBinaryCommands#sinter(byte[][]) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param limit If the intersection cardinality reaches limit partway through the computation,\n   *              the algorithm will exit and yield limit as the cardinality.\n   * @param keys\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  long sintercard(int limit, byte[]... keys);\n\n  Set<byte[]> sunion(byte[]... keys);\n\n  long sunionstore(byte[] dstkey, byte[]... keys);\n\n  long smove(byte[] srckey, byte[] dstkey, byte[] member);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SetCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface SetCommands {\n\n  /**\n   * Add the specified member to the set value stored at key. If member is already a member of the\n   * set no operation is performed. If key does not exist a new set with the specified member as\n   * sole member is created. If the key exists but does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param members\n   * @return The number of elements that were added to the set, not including all the elements\n   * already present in the set\n   */\n  long sadd(String key, String... members);\n\n  /**\n   * Return all the members (elements) of the set value stored at key. This is just syntax glue for\n   * {@link SetCommands#sinter(String...) SINTER}.\n   * <p>\n   * Time complexity O(N)\n   * @param key\n   * @return All elements of the set\n   */\n  Set<String> smembers(String key);\n\n  /**\n   * Remove the specified member from the set value stored at key. If member was not a member of the\n   * set no operation is performed. If key does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param members\n   * @return The number of members that were removed from the set, not including non-existing members\n   */\n  long srem(String key, String... members);\n\n  /**\n   * Remove a random element from a Set returning it as return value. If the Set is empty or the key\n   * does not exist, a nil object is returned.\n   * <p>\n   * The {@link SetCommands#srandmember(String)} command does a similar work but the returned element is\n   * not removed from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The removed member, or nil when key does not exist\n   */\n  String spop(String key);\n\n  /**\n   * By default, the command {@link SetCommands#spop(String)} pops a single member from the set.\n   * In this command, the reply will consist of up to count members, depending on the set's cardinality.\n   * <p>\n   * The {@link SetCommands#srandmember(String)} command does a similar work but the returned element is\n   * not removed from the Set.\n   * <p>\n   * Time complexity O(N), where N is the value of the passed count\n   * @param key\n   * @param count\n   * @return The removed members\n   */\n  Set<String> spop(String key, long count);\n\n  /**\n   * Return the set cardinality (number of elements). If the key does not exist 0 is returned, like\n   * for empty sets.\n   * @param key\n   * @return The cardinality (number of elements) of the set\n   */\n  long scard(String key);\n\n  /**\n   * Return true if member is a member of the set stored at key, otherwise false is returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param member\n   * @return {@code true} if the element is a member of the set, {@code false} otherwise\n   */\n  boolean sismember(String key, String member);\n\n  /**\n   * Returns whether each member is a member of the set stored at key.\n   * <p>\n   * Time complexity O(N) where N is the number of elements being checked for membership\n   * @param key\n   * @param members\n   * @return List representing the membership of the given elements, in the same order as they are requested\n   */\n  List<Boolean> smismember(String key, String... members);\n\n  /**\n   * Return a random element from a Set, without removing the element. If the Set is empty or the\n   * key does not exist, a nil object is returned.\n   * <p>\n   * The {@link SetCommands#spop(String) SPOP} command does a similar work but the returned element\n   * is popped (removed) from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The randomly selected element\n   */\n  String srandmember(String key);\n\n  /**\n   * Return a random elements from a Set, without removing the elements. If the Set is empty or the\n   * key does not exist, an empty list is returned.\n   * <p>\n   * The {@link SetCommands#spop(String) SPOP} command does a similar work but the returned element\n   * is popped (removed) from the Set.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param count if positive, return an array of distinct elements.\n   *        If negative the behavior changes and the command is allowed to\n   *        return the same element multiple times\n   * @return A list of randomly selected elements\n   */\n  List<String> srandmember(String key, int count);\n\n  default ScanResult<String> sscan(String key, String cursor) {\n    return sscan(key, cursor, new ScanParams());\n  }\n\n  ScanResult<String> sscan(String key, String cursor, ScanParams params);\n\n  /**\n   * Return the difference between the Sets stored at {@code keys}\n   * <p>\n   * <b>Example:</b>\n   *\n   * <pre>\n   * key1 = [x, a, b, c]\n   * key2 = [c]\n   * key3 = [a, d]\n   * SDIFF key1,key2,key3 =&gt; [x, b]\n   * </pre>\n   *\n   * Non existing keys are considered like empty sets.\n   * <p>\n   * Time complexity O(N) with N being the total number of elements of all the sets\n   * @param keys group of sets\n   * @return The members of a set resulting from the difference between the sets\n   */\n  Set<String> sdiff(String... keys);\n\n  /**\n   * This command works exactly like {@link SetCommands#sdiff(String...) SDIFF} but instead of being\n   * returned the resulting set is stored in dstkey.\n   * @param dstkey\n   * @param keys group of sets\n   * @return The number of elements in the resulting set\n   */\n  long sdiffstore(String dstkey, String... keys);\n\n  /**\n   * Return the members of a set resulting from the intersection of all the sets hold at the\n   * specified keys. Like in {@link ListCommands#lrange(String, long, long) LRANGE} the result is sent to\n   * the connection as a multi-bulk reply (see the protocol specification for more information). If\n   * just a single key is specified, then this command produces the same result as\n   * {@link SetCommands#smembers(String) SMEMBERS}. Actually SMEMBERS is just syntax sugar for SINTER.\n   * <p>\n   * Non existing keys are considered like empty sets, so if one of the keys is missing an empty set\n   * is returned (since the intersection with an empty set always is an empty set).\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the\n   * number of sets\n   * @param keys group of sets\n   * @return A set with members of the resulting set\n   */\n  Set<String> sinter(String... keys);\n\n  /**\n   * This command works exactly like {@link SetCommands#sinter(String...) SINTER} but instead of being\n   * returned the resulting set is stored as dstkey.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the\n   * number of sets\n   * @param dstkey\n   * @param keys group of sets\n   * @return The number of elements in the resulting set\n   */\n  long sinterstore(String dstkey, String... keys);\n\n  /**\n   * This command works exactly like {@link SetCommands#sinter(String[]) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result. LIMIT defaults to 0 and means unlimited\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param keys group of sets\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  long sintercard(String... keys);\n\n  /**\n   * This command works exactly like {@link SetCommands#sinter(String[]) SINTER} but instead of returning\n   * the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*M) worst case where N is the cardinality of the smallest\n   * @param limit If the intersection cardinality reaches limit partway through the computation,\n   *              the algorithm will exit and yield limit as the cardinality.\n   * @param keys group of sets\n   * @return The cardinality of the set which would result from the intersection of all the given sets\n   */\n  long sintercard(int limit, String... keys);\n\n  /**\n   * Return the members of a set resulting from the union of all the sets hold at the specified\n   * keys. Like in {@link ListCommands#lrange(String, long, long) LRANGE} the result is sent to the\n   * connection as a multi-bulk reply (see the protocol specification for more information). If just\n   * a single key is specified, then this command produces the same result as\n   * {@link SetCommands#smembers(String) SMEMBERS}.\n   * <p>\n   * Non existing keys are considered like empty sets.\n   * <p>\n   * Time complexity O(N) where N is the total number of elements in all the provided sets\n   * @param keys group of sets\n   * @return A set with members of the resulting set\n   */\n  Set<String> sunion(String... keys);\n\n  /**\n   * This command works exactly like {@link SetCommands#sunion(String...) SUNION} but instead of being\n   * returned the resulting set is stored as dstkey. Any existing value in dstkey will be\n   * over-written.\n   * <p>\n   * Time complexity O(N) where N is the total number of elements in all the provided sets\n   * @param dstkey\n   * @param keys group of sets\n   * @return The number of elements in the resulting set\n   */\n  long sunionstore(String dstkey, String... keys);\n\n  /**\n   * Move the specified member from the set at srckey to the set at dstkey. This operation is\n   * atomic, in every given moment the element will appear to be in the source or destination set\n   * for accessing clients.\n   * <p>\n   * If the source set does not exist or does not contain the specified element no operation is\n   * performed and zero is returned, otherwise the element is removed from the source set and added\n   * to the destination set. On success one is returned, even if the element was already present in\n   * the destination set.\n   * <p>\n   * An error is raised if the source or destination keys contain a non Set value.\n   * <p>\n   * Time complexity O(1)\n   * @param srckey\n   * @param dstkey\n   * @param member\n   * @return 1 if the element was moved, 0 if no operation was performed\n   */\n  long smove(String srckey, String dstkey, String member);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SetPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface SetPipelineBinaryCommands {\n\n  Response<Long> sadd(byte[] key, byte[]... members);\n\n  Response<Set<byte[]>> smembers(byte[] key);\n\n  Response<Long> srem(byte[] key, byte[]... members);\n\n  Response<byte[]> spop(byte[] key);\n\n  Response<Set<byte[]>> spop(byte[] key, long count);\n\n  Response<Long> scard(byte[] key);\n\n  Response<Boolean> sismember(byte[] key, byte[] member);\n\n  Response<List<Boolean>> smismember(byte[] key, byte[]... members);\n\n  Response<byte[]> srandmember(byte[] key);\n\n  Response<List<byte[]>> srandmember(byte[] key, int count);\n\n  default Response<ScanResult<byte[]>> sscan(byte[] key, byte[] cursor) {\n    return sscan(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<byte[]>> sscan(byte[] key, byte[] cursor, ScanParams params);\n\n  Response<Set<byte[]>> sdiff(byte[]... keys);\n\n  Response<Long> sdiffstore(byte[] dstkey, byte[]... keys);\n\n  Response<Set<byte[]>> sinter(byte[]... keys);\n\n  Response<Long> sinterstore(byte[] dstkey, byte[]... keys);\n\n  Response<Long> sintercard(byte[]... keys);\n\n  Response<Long> sintercard(int limit, byte[]... keys);\n\n  Response<Set<byte[]>> sunion(byte[]... keys);\n\n  Response<Long> sunionstore(byte[] dstkey, byte[]... keys);\n\n  Response<Long> smove(byte[] srckey, byte[] dstkey, byte[] member);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SetPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic interface SetPipelineCommands {\n\n  Response<Long> sadd(String key, String... members);\n\n  Response<Set<String>> smembers(String key);\n\n  Response<Long> srem(String key, String... members);\n\n  Response<String> spop(String key);\n\n  Response<Set<String>> spop(String key, long count);\n\n  Response<Long> scard(String key);\n\n  Response<Boolean> sismember(String key, String member);\n\n  Response<List<Boolean>> smismember(String key, String... members);\n\n  Response<String> srandmember(String key);\n\n  Response<List<String>> srandmember(String key, int count);\n\n  default Response<ScanResult<String>> sscan(String key, String cursor) {\n    return sscan(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<String>> sscan(String key, String cursor, ScanParams params);\n\n  Response<Set<String>> sdiff(String... keys);\n\n  Response<Long> sdiffstore(String dstKey, String... keys);\n\n  /**\n   * @deprecated Use {@link SetPipelineCommands#sdiffstore(java.lang.String, java.lang.String...)}.\n   */\n  @Deprecated\n  default Response<Long> sdiffStore(String dstKey, String... keys) {\n    return sdiffstore(dstKey, keys);\n  }\n\n  Response<Set<String>> sinter(String... keys);\n\n  Response<Long> sinterstore(String dstKey, String... keys);\n\n  Response<Long> sintercard(String... keys);\n\n  Response<Long> sintercard(int limit, String... keys);\n\n  Response<Set<String>> sunion(String... keys);\n\n  Response<Long> sunionstore(String dstKey, String... keys);\n\n  Response<Long> smove(String srckey, String dstKey, String member);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SlowlogCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.resps.Slowlog;\n\npublic interface SlowlogCommands {\n\n  String slowlogReset();\n\n  long slowlogLen();\n\n  List<Slowlog> slowlogGet();\n\n  List<Object> slowlogGetBinary();\n\n  List<Slowlog> slowlogGet(long entries);\n\n  List<Object> slowlogGetBinary(long entries);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SortedSetBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SortedSetBinaryCommands {\n\n  long zadd(byte[] key, double score, byte[] member);\n\n  long zadd(byte[] key, double score, byte[] member, ZAddParams params);\n\n  long zadd(byte[] key, Map<byte[], Double> scoreMembers);\n\n  long zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params);\n\n  Double zaddIncr(byte[] key, double score, byte[] member, ZAddParams params);\n\n  long zrem(byte[] key, byte[]... members);\n\n  double zincrby(byte[] key, double increment, byte[] member);\n\n  Double zincrby(byte[] key, double increment, byte[] member, ZIncrByParams params);\n\n  Long zrank(byte[] key, byte[] member);\n\n  Long zrevrank(byte[] key, byte[] member);\n\n  KeyValue<Long, Double> zrankWithScore(byte[] key, byte[] member);\n\n  KeyValue<Long, Double> zrevrankWithScore(byte[] key, byte[] member);\n\n  List<byte[]> zrange(byte[] key, long start, long stop);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrange(byte[] key, long start, long stop);\n\n  List<Tuple> zrangeWithScores(byte[] key, long start, long stop);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeWithScores(byte[] key, long start, long stop);\n\n  List<byte[]> zrange(byte[] key, ZRangeParams zRangeParams);\n\n  List<Tuple> zrangeWithScores(byte[] key, ZRangeParams zRangeParams);\n\n  long zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams);\n\n  byte[] zrandmember(byte[] key);\n\n  List<byte[]> zrandmember(byte[] key, long count);\n\n  List<Tuple> zrandmemberWithScores(byte[] key, long count);\n\n  long zcard(byte[] key);\n\n  Double zscore(byte[] key, byte[] member);\n\n  List<Double> zmscore(byte[] key, byte[]... members);\n\n  Tuple zpopmax(byte[] key);\n\n  List<Tuple> zpopmax(byte[] key, int count);\n\n  Tuple zpopmin(byte[] key);\n\n  List<Tuple> zpopmin(byte[] key, int count);\n\n  long zcount(byte[] key, double min, double max);\n\n  long zcount(byte[] key, byte[] min, byte[] max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrangeByScore(byte[] key, double min, double max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrangeByScore(byte[] key, byte[] min, byte[] max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrangeByScore(byte[] key, double max, double min);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrangeByScore(byte[] key, double min, double max, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrangeByScore(byte[] key, byte[] max, byte[] min);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrangeByScore(byte[] key, double max, double min, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(byte[] key, double min, double max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(byte[] key, double max, double min);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrangeWithScores(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count);\n\n  long zremrangeByRank(byte[] key, long start, long stop);\n\n  long zremrangeByScore(byte[] key, double min, double max);\n\n  long zremrangeByScore(byte[] key, byte[] min, byte[] max);\n\n  long zlexcount(byte[] key, byte[] min, byte[] max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(byte[], byte[])}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrangeByLex(byte[] key, byte[] min, byte[] max);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(byte[], byte[])}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(byte[], byte[])} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrangeByLex(byte[] key, byte[] max, byte[] min);\n\n  /**\n   * @deprecated Use {@link SortedSetBinaryCommands#zrange(byte[], ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(byte[], byte[])} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<byte[]> zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count);\n\n  long zremrangeByLex(byte[] key, byte[] min, byte[] max);\n\n  default ScanResult<Tuple> zscan(byte[] key, byte[] cursor) {\n    return zscan(key, cursor, new ScanParams());\n  }\n\n  ScanResult<Tuple> zscan(byte[] key, byte[] cursor, ScanParams params);\n\n  KeyValue<byte[], Tuple> bzpopmax(double timeout, byte[]... keys);\n\n  KeyValue<byte[], Tuple> bzpopmin(double timeout, byte[]... keys);\n\n  List<byte[]> zdiff(byte[]... keys);\n\n  List<Tuple> zdiffWithScores(byte[]... keys);\n\n  /**\n   * @deprecated Use {@link #zdiffstore(byte[], byte[][])}.\n   */\n  @Deprecated\n  long zdiffStore(byte[] dstkey, byte[]... keys);\n\n  long zdiffstore(byte[] dstkey, byte[]... keys);\n\n  List<byte[]> zinter(ZParams params, byte[]... keys);\n\n  List<Tuple> zinterWithScores(ZParams params, byte[]... keys);\n\n  long zinterstore(byte[] dstkey, byte[]... sets);\n\n  long zinterstore(byte[] dstkey, ZParams params, byte[]... sets);\n\n  /**\n   * Similar to {@link #zinter(ZParams, byte[][]) ZINTER}, but instead of returning the result set,\n   * it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*K) worst case with N being the smallest input sorted set, K\n   * being the number of input sorted sets\n   * @see #zinter(ZParams, byte[][])\n   * @param keys group of sets\n   * @return The number of elements in the resulting intersection\n   */\n  long zintercard(byte[]... keys);\n\n  /**\n   * Similar to {@link #zinter(ZParams, byte[][]) ZINTER}, but instead of returning the result set,\n   * it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*K) worst case with N being the smallest input sorted set, K\n   * being the number of input sorted sets\n   * @see #zinter(ZParams, byte[][])\n   * @param limit If the intersection cardinality reaches limit partway through the computation,\n   *              the algorithm will exit and yield limit as the cardinality\n   * @param keys group of sets\n   * @return The number of elements in the resulting intersection\n   */\n  long zintercard(long limit, byte[]... keys);\n\n  List<byte[]> zunion(ZParams params, byte[]... keys);\n\n  List<Tuple> zunionWithScores(ZParams params, byte[]... keys);\n\n  long zunionstore(byte[] dstkey, byte[]... sets);\n\n  long zunionstore(byte[] dstkey, ZParams params, byte[]... sets);\n\n  KeyValue<byte[], List<Tuple>> zmpop(SortedSetOption option, byte[]... keys);\n\n  KeyValue<byte[], List<Tuple>> zmpop(SortedSetOption option, int count, byte[]... keys);\n\n  KeyValue<byte[], List<Tuple>> bzmpop(double timeout, SortedSetOption option, byte[]... keys);\n\n  KeyValue<byte[], List<Tuple>> bzmpop(double timeout, SortedSetOption option, int count, byte[]... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SortedSetCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SortedSetCommands {\n\n  /**\n   * Add the specified member having the specified score to the sorted set stored at key. If member\n   * is already a member of the sorted set the score is updated, and the element reinserted in the\n   * right position to ensure sorting. If key does not exist a new sorted set with the specified\n   * member as sole member is created. If the key exists but does not hold a sorted set value an\n   * error is returned.\n   * <p>\n   * The score value can be the string representation of a double precision floating point number.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param score\n   * @param member\n   * @return 1 if the new element was added, 0 if the element was already a member of the sorted\n   * set and the score was updated\n   */\n  long zadd(String key, double score, String member);\n\n  /**\n   * Similar to {@link SortedSetCommands#zadd(String, double, String) ZADD} but can be used with optional params.\n   * @see SortedSetCommands#zadd(String, double, String)\n   * @param key\n   * @param score\n   * @param member\n   * @param params {@link ZAddParams}\n   * @return 1 if the new element was added, 0 if the element was already a member of the sorted\n   * set and the score was updated\n   */\n  long zadd(String key, double score, String member, ZAddParams params);\n\n  /**\n   * Similar to {@link SortedSetCommands#zadd(String, double, String) ZADD} but for multiple members.\n   * @see SortedSetCommands#zadd(String, double, String)\n   * @param key\n   * @param scoreMembers\n   * @return The number of elements added to the sorted set (excluding score updates).\n   */\n  long zadd(String key, Map<String, Double> scoreMembers);\n\n  /**\n   * Similar to {@link SortedSetCommands#zadd(String, double, String) ZADD} but can be used with optional params,\n   * and fits for multiple members.\n   * @see SortedSetCommands#zadd(String, double, String)\n   * @param key\n   * @param scoreMembers\n   * @param params {@link ZAddParams}\n   * @return The number of elements added to the sorted set (excluding score updates).\n   */\n  long zadd(String key, Map<String, Double> scoreMembers, ZAddParams params);\n\n  /**\n   * Increments the score of member in the sorted set stored at key by increment. If member does not\n   * exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0).\n   * If key does not exist, a new sorted set with the specified member as its sole member is created.\n   * <p>\n   * The score value should be the string representation of a numeric value, and accepts double precision\n   * floating point numbers. It is possible to provide a negative value to decrement the score.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param score\n   * @param member\n   * @param params {@link ZAddParams}\n   * @return 1 if the new element was added, 0 if the element was already a member of the sorted\n   * set and the score was updated\n   */\n  Double zaddIncr(String key, double score, String member, ZAddParams params);\n\n  /**\n   * Remove the specified member from the sorted set value stored at key. If member was not a member\n   * of the set no operation is performed. If key does not hold a set value an error is returned.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param members\n   * @return 1 if the new element was removed, 0 if the new element was not a member of the set\n   */\n  long zrem(String key, String... members);\n\n  /**\n   * If member already exists in the sorted set adds the increment to its score and updates the\n   * position of the element in the sorted set accordingly. If member does not already exist in the\n   * sorted set it is added with increment as score (that is, like if the previous score was\n   * virtually zero). If key does not exist a new sorted set with the specified member as sole\n   * member is created. If the key exists but does not hold a sorted set value an error is returned.\n   * <p>\n   * The score value can be the string representation of a double precision floating point number.\n   * It's possible to provide a negative value to perform a decrement.\n   * <p>\n   * For an introduction to sorted sets check the Introduction to Redis data types page.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @param increment\n   * @param member\n   * @return The new score\n   */\n  double zincrby(String key, double increment, String member);\n\n  /**\n   * Similar to {@link SortedSetCommands#zincrby(String, double, String) ZINCRBY} but can be used with optionals params.\n   * @see SortedSetCommands#zincrby(String, double, String)\n   * @param key\n   * @param increment\n   * @param member\n   * @param params {@link ZIncrByParams}\n   * @return The new score for key\n   */\n  Double zincrby(String key, double increment, String member, ZIncrByParams params);\n\n  /**\n   * Return the rank (or index) of member in the sorted set at key, with scores being ordered from\n   * low to high.\n   * <p>\n   * When the given member does not exist in the sorted set, the special value 'nil' is returned.\n   * The returned rank (or index) of the member is 0-based for both commands.\n   * <p>\n   * Time complexity O(log(N))\n   * @param key\n   * @param member\n   * @return The rank of the element as an integer reply if the element exists. A nil bulk reply\n   * if there is no such element\n   */\n  Long zrank(String key, String member);\n\n  /**\n   * Return the rank (or index) of member in the sorted set at key, with scores being ordered from\n   * high to low.\n   * <p>\n   * When the given member does not exist in the sorted set, the special value 'nil' is returned.\n   * The returned rank (or index) of the member is 0-based for both commands.\n   * <p>\n   * Time complexity O(log(N))\n   * @param key\n   * @param member\n   * @return The rank of the element as an integer reply if the element exists. A nil bulk reply\n   * if there is no such element\n   */\n  Long zrevrank(String key, String member);\n\n  /**\n   * Returns the rank and the score of member in the sorted set stored at key, with the scores\n   * ordered from low to high.\n   * @param key the key\n   * @param member the member\n   * @return the KeyValue contains rank and score.\n   */\n  KeyValue<Long, Double> zrankWithScore(String key, String member);\n\n  /**\n   * Returns the rank and the score of member in the sorted set stored at key, with the scores\n   * ordered from high to low.\n   * @param key the key\n   * @param member the member\n   * @return the KeyValue contains rank and score.\n   */\n  KeyValue<Long, Double> zrevrankWithScore(String key, String member);\n\n  /**\n   * Returns the specified range of elements in the sorted set stored at key.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set and M the\n   * number of elements returned.\n   * @param key the key to query\n   * @param start the minimum index\n   * @param stop the maximum index\n   * @return A List of Strings in the specified range\n   */\n  List<String> zrange(String key, long start, long stop);\n\n  /**\n   * Returns the specified range of elements in the sorted set stored at key. The elements are\n   * considered to be ordered from the highest to the lowest score. Descending lexicographical\n   * order is used for elements with equal score.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set and M the\n   * number of elements returned.\n   * @param key the key to query\n   * @param start the minimum index\n   * @param stop the maximum index\n   * @return A List of Strings in the specified range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrange(String key, long start, long stop);\n\n  /**\n   * Returns the specified range of elements in the sorted set stored at key with the scores.\n   * @param key the key to query\n   * @param start the minimum index\n   * @param stop the maximum index\n   * @return A List of Tuple in the specified range (elements names and their scores)\n   */\n  List<Tuple> zrangeWithScores(String key, long start, long stop);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrange(String, long, long) ZREVRANGE} but the reply will\n   * include the scores of the returned elements.\n   * @see SortedSetCommands#zrevrange(String, long, long)\n   * @param key the key to query\n   * @param start the minimum index\n   * @param stop the maximum index\n   * @return A List of Tuple in the specified range (elements names and their scores)\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeWithScores(String key, long start, long stop);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrange(String, long, long) ZRANGE} but can be used with additional params.\n   * @see SortedSetCommands#zrange(String, long, long)\n   * @param key the key to query\n   * @param zRangeParams {@link ZRangeParams}\n   * @return A List of Strings in the specified range\n   */\n  List<String> zrange(String key, ZRangeParams zRangeParams);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeWithScores(String, long, long) ZRANGE} but can be used with additional params.\n   * @see SortedSetCommands#zrangeWithScores(String, long, long)\n   * @param key the key to query\n   * @param zRangeParams {@link ZRangeParams}\n   * @return A List of Tuple in the specified range (elements names and their scores)\n   */\n  List<Tuple> zrangeWithScores(String key, ZRangeParams zRangeParams);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrange(String, ZRangeParams) ZRANGE} but stores the result in {@code dest}.\n   * @see SortedSetCommands#zrange(String, ZRangeParams)\n   * @param dest the storing key\n   * @param src the key to query\n   * @param zRangeParams {@link ZRangeParams}\n   * @return The number of elements in the resulting sorted set\n   */\n  long zrangestore(String dest, String src, ZRangeParams zRangeParams);\n\n  /**\n   * Return a random element from the sorted set value stored at key.\n   * <p>\n   * Time complexity O(N) where N is the number of elements returned\n   * @param key\n   * @return Random String from the set\n   */\n  String zrandmember(String key);\n\n  /**\n   * Return an array of distinct elements. The array's length is either count or the sorted set's\n   * cardinality ({@link SortedSetCommands#zcard(String) ZCARD}), whichever is lower.\n   * <p>\n   * Time complexity O(N) where N is the number of elements returned\n   * @param key\n   * @param count choose up to count elements\n   * @return A list of distinct Strings from the set\n   */\n  List<String> zrandmember(String key, long count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrandmember(String, long) ZRANDMEMBER} but the replay will\n   * include the scores with the result.\n   * @see SortedSetCommands#zrandmember(String, long)\n   * @param key\n   * @param count choose up to count elements\n   * @return A List of distinct Strings with their scores\n   */\n  List<Tuple> zrandmemberWithScores(String key, long count);\n\n  /**\n   * Return the sorted set cardinality (number of elements). If the key does not exist 0 is\n   * returned, like for empty sorted sets.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @return The cardinality (number of elements) of the set as an integer\n   */\n  long zcard(String key);\n\n  /**\n   * Return the score of the specified element of the sorted set at key. If the specified element\n   * does not exist in the sorted set, or the key does not exist at all, a special 'nil' value is\n   * returned.\n   * <p>\n   * Time complexity O(1)\n   * @param key\n   * @param member\n   * @return The score\n   */\n  Double zscore(String key, String member);\n\n  /**\n   * Return the scores associated with the specified members in the sorted set stored at key.\n   * For every member that does not exist in the sorted set, a nil value is returned.\n   * <p>\n   * Time complexity O(N) where N is the number of members being requested\n   * @param key\n   * @param members\n   * @return The scores\n   */\n  List<Double> zmscore(String key, String... members);\n\n  /**\n   * Remove and return the member with the highest score in the sorted set stored at key.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @return The popped element and the score\n   */\n  Tuple zpopmax(String key);\n\n  /**\n   * Remove and return up to count members with the highest scores in the sorted set stored at key.\n   * <p>\n   * Time complexity O(log(N)*M) with N being the number of elements in the sorted set, and M being\n   * the number of elements popped.\n   * @param key\n   * @param count the number of elements to pop\n   * @return A List of popped elements and scores\n   */\n  List<Tuple> zpopmax(String key, int count);\n\n  /**\n   * Remove and return the member with the lowest score in the sorted set stored at key.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set\n   * @param key\n   * @return The popped element and the score\n   */\n  Tuple zpopmin(String key);\n\n  /**\n   * Remove and return up to count members with the lowest scores in the sorted set stored at key.\n   * <p>\n   * Time complexity O(log(N)*M) with N being the number of elements in the sorted set, and M being\n   * the number of elements popped.\n   * @param key\n   * @param count the number of elements to pop\n   * @return A List of popped elements and scores\n   */\n  List<Tuple> zpopmin(String key, int count);\n\n  /**\n   * Return the number of elements in the sorted set at key with a score between min and max.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set.\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @return The number of elements in the specified score range.\n   */\n  long zcount(String key, double min, double max);\n\n  /**\n   * Similar to {@link SortedSetCommands#zcount(String, double, double) ZCOUNT} but with <i>exclusive</i> range.\n   * @see SortedSetCommands#zcount(String, double, double)\n   */\n  long zcount(String key, String min, String max);\n\n  /**\n   * Return all the elements in the sorted set at key with a score between min and max\n   * (including elements with score equal to min or max). The elements are considered to\n   * be ordered from low to high scores.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set\n   * and M the number of elements being returned.\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrangeByScore(String key, double min, double max);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but with <i>exclusive</i> range.\n   * @see SortedSetCommands#zrangeByScore(String, double, double)\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrangeByScore(String key, String min, String max);\n\n  /**\n   * Return all the elements in the sorted set at key with a score between max and min\n   * (including elements with score equal to max or min). In contrary to the default\n   * ordering of sorted sets, for this command the elements are considered to be ordered\n   * from high to low scores.\n   * <p>\n   * The elements having the same score are returned in reverse lexicographical order.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set\n   * and M the number of elements being returned.\n   * @param key the key to query\n   * @param max maximum score\n   * @param min minimum score\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrangeByScore(String key, double max, double min);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but with <i>exclusive</i> range.\n   * @see SortedSetCommands#zrangeByScore(String, double, double)\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrangeByScore(String key, double min, double max, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZREVRANGE} but with <i>exclusive</i> range.\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrangeByScore(String key, String max, String min);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but with <i>limit</i> option,\n   * @see SortedSetCommands#zrangeByScore(String, double, double)\n   * and with <i>exclusive</i> range.\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrangeByScore(String key, String min, String max, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZRANGE} but with <i>limit</i> option,\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * @param key the key to query\n   * @param max maximum score\n   * @param min minimum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrangeByScore(String key, double max, double min, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but return with scores.\n   * @see SortedSetCommands#zrangeByScore(String, double, double)\n   * return both the element and its score, instead of the element alone.\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @return A List of elements with scores in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(String key, double min, double max);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZREVRANGE} but return with scores.\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * return both the element and its score, instead of the element alone.\n   * @param key the key to query\n   * @param max maximum score\n   * @param min minimum score\n   * @return A List of elements with scores in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(String key, double max, double min);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but with <i>limit</i> option,\n   * and return with scores.\n   * @see SortedSetCommands#zrangeByScore(String, double, double)\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(String key, double min, double max, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZREVRANGE} but with <i>limit</i> option,\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * and with <i>exclusive</i> range.\n   * @param key the key to query\n   * @param max maximum score\n   * @param min minimum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrangeByScore(String key, String max, String min, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but with <i>exclusive</i> range,\n   * and return with scores.\n   * @see SortedSetCommands#zrangeByScore(String, double, double)\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(String key, String min, String max);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZREVRANGE} but with <i>exclusive</i> range,\n   * and return with scores.\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(String key, String max, String min);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByScore(String, double, double) ZRANGE} but with <i>exclusive</i> range,\n   * with <i>limit</i> options and return with scores.\n   * @see SortedSetCommands#zrangeByScore(String, String, String)\n   * @param key the key to query\n   * @param min minimum score\n   * @param max maximum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrangeByScoreWithScores(String key, String min, String max, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZREVRANGE} but with\n   * <i>limit</i> options and return with scores.\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * @param key the key to query\n   * @param max maximum score\n   * @param min minimum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByScore(String, double, double) ZREVRANGE} but with\n   * <i>exclusive</i> range, with <i>limit</i> options and return with scores.\n   * @see SortedSetCommands#zrevrangeByScore(String, double, double)\n   * @param key the key to query\n   * @param max maximum score\n   * @param min minimum score\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrangeWithScores(String, ZRangeParams)} with {@link ZRangeParams#zrangeByScoreParams(double, double)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<Tuple> zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count);\n\n  /**\n   * Remove all elements in the sorted set at key with rank between start and end. Start and end are\n   * 0-based with rank 0 being the element with the lowest score. Both start and end can be negative\n   * numbers, where they indicate offsets starting at the element with the highest rank. For\n   * example: -1 is the element with the highest score, -2 the element with the second highest score\n   * and so forth.\n   * <p>\n   * Time complexity O(log(N))+O(M) with N being the number of elements in the sorted set and M the\n   * number of elements removed by the operation.\n   * @param key\n   * @param start\n   * @param stop\n   * @return The number of elements removed\n   */\n  long zremrangeByRank(String key, long start, long stop);\n\n  /**\n   * Remove all the elements in the sorted set at key with a score between min and max (including\n   * elements with score equal to min or max).\n   * <p>\n   * Time complexity O(log(N))+O(M) with N being the number of elements in the sorted set and M the\n   * number of elements removed by the operation.\n   * @param key\n   * @param min minimum score to remove\n   * @param max maximum score to remove\n   * @return The number of elements removed\n   */\n  long zremrangeByScore(String key, double min, double max);\n\n  /**\n   * Similar to {@link SortedSetCommands#zremrangeByScore(String, double, double) ZREMRANGE} but with <i>limit</i> option.\n   * @see SortedSetCommands#zremrangeByScore(String, double, double)\n   */\n  long zremrangeByScore(String key, String min, String max);\n\n  /**\n   * Return the number of elements in the sorted set at key with a value between min and max, when all\n   * the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering.\n   * <p>\n   * Time complexity O(log(N)) with N being the number of elements in the sorted set.\n   * @param key\n   * @param min minimum value\n   * @param max maximum value\n   * @return The number of elements in the specified score range\n   */\n  long zlexcount(String key, String min, String max);\n\n  /**\n   * Return all the elements in the sorted set at key with a value between min and max, when all\n   * the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set and M the number of\n   * elements being returned.\n   * @param key\n   * @param min minimum value\n   * @param max maximum value\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrangeByLex(String key, String min, String max);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrangeByLex(String, String, String) ZRANGE} but with <i>limit</i> option.\n   * @see SortedSetCommands#zrangeByLex(String, String, String)\n   * @param key\n   * @param min minimum value\n   * @param max maximum value\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrangeByLex(String key, String min, String max, int offset, int count);\n\n  /**\n   * Return all the elements in the sorted set at key with a value between max and min, when all\n   * the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set and M the number of\n   * elements being returned.\n   * @param key\n   * @param max maximum value\n   * @param min minimum value\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrangeByLex(String key, String max, String min);\n\n  /**\n   * Similar to {@link SortedSetCommands#zrevrangeByLex(String, String, String) ZRANGE} but with <i>limit</i> option.\n   * @see SortedSetCommands#zrevrangeByLex(String, String, String)\n   * @param key\n   * @param max maximum value\n   * @param min minimum value\n   * @param offset the first index of the sub-range\n   * @param count count of the sub-range. A negative count returns all elements from the offset\n   * @return A List of elements in the specified score range\n   * @deprecated Use {@link SortedSetCommands#zrange(String, ZRangeParams)} with {@link ZRangeParams#zrangeByLexParams(String, String)} and {@link ZRangeParams#rev()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 6.2.0.\n   */\n  @Deprecated\n  List<String> zrevrangeByLex(String key, String max, String min, int offset, int count);\n\n  /**\n   * Remove all elements in the sorted set stored at key between the lexicographical range specified by min and max,\n   * when all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering.\n   * <p>\n   * Time complexity O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements\n   * removed by the operation.\n   * @param key\n   * @param min minimum value to remove\n   * @param max maximum value to remove\n   * @return The number of elements removed\n   */\n  long zremrangeByLex(String key, String min, String max);\n\n  default ScanResult<Tuple> zscan(String key, String cursor) {\n    return zscan(key, cursor, new ScanParams());\n  }\n\n  ScanResult<Tuple> zscan(String key, String cursor, ScanParams params);\n\n  /**\n   * The blocking version of {@link SortedSetCommands#zpopmax(String) ZPOPMAX}\n   * @param timeout specifying the maximum number of seconds to block. A timeout of zero can\n   *               be used to block indefinitely.\n   * @param keys\n   */\n  KeyValue<String, Tuple> bzpopmax(double timeout, String... keys);\n\n  /**\n   * The blocking version of {@link SortedSetCommands#zpopmin(String) ZPOPMIN}\n   * @param timeout specifying the maximum number of seconds to block. A timeout of zero can\n   *               be used to block indefinitely.\n   * @param keys\n   */\n  KeyValue<String, Tuple> bzpopmin(double timeout, String... keys);\n\n  /**\n   * Compute the difference between all the sets in the given keys.\n   * <p>\n   * Time complexity O(L + (N-K)log(N)) worst case where L is the total number of elements in\n   * all the sets, N is the size of the first set, and K is the size of the result set.\n   * @param keys group of sets\n   * @return The result of the difference\n   */\n  List<String> zdiff(String... keys);\n\n  /**\n   * Compute the difference between all the sets in the given keys. Return the result with scores.\n   * @param keys group of sets\n   * @return The result of the difference with their scores\n   */\n  List<Tuple> zdiffWithScores(String... keys);\n\n  /**\n   * Compute the difference between all the sets in the given keys. Store the result in dstkey.\n   * @param dstkey\n   * @param keys group of sets\n   * @return The number of elements in the resulting sorted set at dstkey.\n   * @deprecated Use {@link #zdiffstore(java.lang.String, java.lang.String...)}.\n   */\n  @Deprecated\n  long zdiffStore(String dstkey, String... keys);\n\n  /**\n   * Compute the difference between all the sets in the given keys. Store the result in dstkey.\n   * @param dstkey\n   * @param keys group of sets\n   * @return The number of elements in the resulting sorted set at dstkey.\n   */\n  long zdiffstore(String dstkey, String... keys);\n\n  /**\n   * Compute the intersection between all the sets in the given keys.\n   * <p>\n   * Time complexity O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being\n   * the number of input sorted sets and M being the number of elements in the resulting sorted set.\n   * @param params {@link ZParams}\n   * @param keys group of sets\n   * @return The result of the intersection\n   */\n  List<String> zinter(ZParams params, String... keys);\n\n  /**\n   * Compute the intersection between all the sets in the given keys. Return the result with scores.\n   * @param params {@link ZParams}\n   * @param keys group of sets\n   * @return The result of the intersection with their scores\n   */\n  List<Tuple> zinterWithScores(ZParams params, String... keys);\n\n  /**\n   * Compute the intersection between all the sets in the given keys. Store the result in dstkey.\n   * @param dstkey\n   * @param sets group of sets\n   * @return The number of elements in the resulting sorted set at dstkey\n   */\n  long zinterstore(String dstkey, String... sets);\n\n  /**\n   * Compute the intersection between all the sets in the given keys. Store the result in dstkey.\n   * @param dstkey\n   * @param params {@link ZParams}\n   * @param sets group of sets\n   * @return The number of elements in the resulting sorted set at dstkey\n   */\n  long zinterstore(String dstkey, ZParams params, String... sets);\n\n  /**\n   * Similar to {@link SortedSetCommands#zinter(ZParams, String...) ZINTER}, but\n   * instead of returning the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*K) worst case with N being the smallest input sorted set, K\n   * being the number of input sorted sets\n   * @see SortedSetCommands#zinter(ZParams, String...)\n   * @param keys group of sets\n   * @return The number of elements in the resulting intersection\n   */\n  long zintercard(String... keys);\n\n  /**\n   * Similar to {@link SortedSetCommands#zinter(ZParams, String...) ZINTER}, but\n   * instead of returning the result set, it returns just the cardinality of the result.\n   * <p>\n   * Time complexity O(N*K) worst case with N being the smallest input sorted set, K\n   * being the number of input sorted sets\n   * @see SortedSetCommands#zinter(ZParams, String...)\n   * @param limit If the intersection cardinality reaches limit partway through the computation,\n   *              the algorithm will exit and yield limit as the cardinality\n   * @param keys group of sets\n   * @return The number of elements in the resulting intersection\n   */\n  long zintercard(long limit, String... keys);\n\n  /**\n   * Compute the union between all the sets in the given keys.\n   * <p>\n   * Time complexity O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets,\n   * and M being the number of elements in the resulting sorted set.\n   * @param params {@link ZParams}\n   * @param keys group of sets\n   * @return The result of the union\n   */\n  List<String> zunion(ZParams params, String... keys);\n\n  /**\n   * Compute the union between all the sets in the given keys. Return the result with scores.\n   * @param params {@link ZParams}\n   * @param keys group of sets\n   * @return The result of the union with their scores\n   */\n  List<Tuple> zunionWithScores(ZParams params, String... keys);\n\n  /**\n   * Compute the union between all the sets in the given keys. Store the result in dstkey.\n   * @param dstkey\n   * @param sets group of sets\n   * @return The number of elements in the resulting sorted set at dstkey\n   */\n  long zunionstore(String dstkey, String... sets);\n\n  /**\n   * Compute the union between all the sets in the given keys. Store the result in dstkey.\n   * @param dstkey\n   * @param params {@link ZParams}\n   * @param sets group of sets\n   * @return The number of elements in the resulting sorted set at dstkey\n   */\n  long zunionstore(String dstkey, ZParams params, String... sets);\n\n  KeyValue<String, List<Tuple>> zmpop(SortedSetOption option, String... keys);\n\n  KeyValue<String, List<Tuple>> zmpop(SortedSetOption option, int count, String... keys);\n\n  KeyValue<String, List<Tuple>> bzmpop(double timeout, SortedSetOption option, String... keys);\n\n  KeyValue<String, List<Tuple>> bzmpop(double timeout, SortedSetOption option, int count, String... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SortedSetPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SortedSetPipelineBinaryCommands {\n\n  Response<Long> zadd(byte[] key, double score, byte[] member);\n\n  Response<Long> zadd(byte[] key, double score, byte[] member, ZAddParams params);\n\n  Response<Long> zadd(byte[] key, Map<byte[], Double> scoreMembers);\n\n  Response<Long> zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params);\n\n  Response<Double> zaddIncr(byte[] key, double score, byte[] member, ZAddParams params);\n\n  Response<Long> zrem(byte[] key, byte[]... members);\n\n  Response<Double> zincrby(byte[] key, double increment, byte[] member);\n\n  Response<Double> zincrby(byte[] key, double increment, byte[] member, ZIncrByParams params);\n\n  Response<Long> zrank(byte[] key, byte[] member);\n\n  Response<Long> zrevrank(byte[] key, byte[] member);\n\n  Response<KeyValue<Long, Double>> zrankWithScore(byte[] key, byte[] member);\n\n  Response<KeyValue<Long, Double>> zrevrankWithScore(byte[] key, byte[] member);\n\n  Response<List<byte[]>> zrange(byte[] key, long start, long stop);\n\n  Response<List<byte[]>> zrevrange(byte[] key, long start, long stop);\n\n  Response<List<Tuple>> zrangeWithScores(byte[] key, long start, long stop);\n\n  Response<List<Tuple>> zrevrangeWithScores(byte[] key, long start, long stop);\n\n  Response<byte[]> zrandmember(byte[] key);\n\n  Response<List<byte[]>> zrandmember(byte[] key, long count);\n\n  Response<List<Tuple>> zrandmemberWithScores(byte[] key, long count);\n\n  Response<Long> zcard(byte[] key);\n\n  Response<Double> zscore(byte[] key, byte[] member);\n\n  Response<List<Double>> zmscore(byte[] key, byte[]... members);\n\n  Response<Tuple> zpopmax(byte[] key);\n\n  Response<List<Tuple>> zpopmax(byte[] key, int count);\n\n  Response<Tuple> zpopmin(byte[] key);\n\n  Response<List<Tuple>> zpopmin(byte[] key, int count);\n\n  Response<Long> zcount(byte[] key, double min, double max);\n\n  Response<Long> zcount(byte[] key, byte[] min, byte[] max);\n\n  Response<List<byte[]>> zrangeByScore(byte[] key, double min, double max);\n\n  Response<List<byte[]>> zrangeByScore(byte[] key, byte[] min, byte[] max);\n\n  Response<List<byte[]>> zrevrangeByScore(byte[] key, double max, double min);\n\n  Response<List<byte[]>> zrangeByScore(byte[] key, double min, double max, int offset, int count);\n\n  Response<List<byte[]>> zrevrangeByScore(byte[] key, byte[] max, byte[] min);\n\n  Response<List<byte[]>> zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count);\n\n  Response<List<byte[]>> zrevrangeByScore(byte[] key, double max, double min, int offset, int count);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, double min, double max);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, double max, double min);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count);\n\n  Response<List<byte[]>> zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count);\n\n  Response<Long> zremrangeByRank(byte[] key, long start, long stop);\n\n  Response<Long> zremrangeByScore(byte[] key, double min, double max);\n\n  Response<Long> zremrangeByScore(byte[] key, byte[] min, byte[] max);\n\n  Response<Long> zlexcount(byte[] key, byte[] min, byte[] max);\n\n  Response<List<byte[]>> zrangeByLex(byte[] key, byte[] min, byte[] max);\n\n  Response<List<byte[]>> zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count);\n\n  Response<List<byte[]>> zrevrangeByLex(byte[] key, byte[] max, byte[] min);\n\n  Response<List<byte[]>> zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count);\n\n  Response<List<byte[]>> zrange(byte[] key, ZRangeParams zRangeParams);\n\n  Response<List<Tuple>> zrangeWithScores(byte[] key, ZRangeParams zRangeParams);\n\n  Response<Long> zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams);\n\n  Response<Long> zremrangeByLex(byte[] key, byte[] min, byte[] max);\n\n  default Response<ScanResult<Tuple>> zscan(byte[] key, byte[] cursor) {\n    return zscan(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<Tuple>> zscan(byte[] key, byte[] cursor, ScanParams params);\n\n  Response<KeyValue<byte[], Tuple>> bzpopmax(double timeout, byte[]... keys);\n\n  Response<KeyValue<byte[], Tuple>> bzpopmin(double timeout, byte[]... keys);\n\n  Response<List<byte[]>> zdiff(byte[]... keys);\n\n  Response<List<Tuple>> zdiffWithScores(byte[]... keys);\n\n  /**\n   * @deprecated Use {@link #zdiffstore(byte[], byte[][])}.\n   */\n  @Deprecated\n  Response<Long> zdiffStore(byte[] dstkey, byte[]... keys);\n\n  Response<Long> zdiffstore(byte[] dstkey, byte[]... keys);\n\n  Response<List<byte[]>> zinter(ZParams params, byte[]... keys);\n\n  Response<List<Tuple>> zinterWithScores(ZParams params, byte[]... keys);\n\n  Response<Long> zinterstore(byte[] dstkey, byte[]... sets);\n\n  Response<Long> zinterstore(byte[] dstkey, ZParams params, byte[]... sets);\n\n  Response<Long> zintercard(byte[]... keys);\n\n  Response<Long> zintercard(long limit, byte[]... keys);\n\n  Response<List<byte[]>> zunion(ZParams params, byte[]... keys);\n\n  Response<List<Tuple>> zunionWithScores(ZParams params, byte[]... keys);\n\n  Response<Long> zunionstore(byte[] dstkey, byte[]... sets);\n\n  Response<Long> zunionstore(byte[] dstkey, ZParams params, byte[]... sets);\n\n  Response<KeyValue<byte[], List<Tuple>>> zmpop(SortedSetOption option, byte[]... keys);\n\n  Response<KeyValue<byte[], List<Tuple>>> zmpop(SortedSetOption option, int count, byte[]... keys);\n\n  Response<KeyValue<byte[], List<Tuple>>> bzmpop(double timeout, SortedSetOption option, byte[]... keys);\n\n  Response<KeyValue<byte[], List<Tuple>>> bzmpop(double timeout, SortedSetOption option, int count, byte[]... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/SortedSetPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\npublic interface SortedSetPipelineCommands {\n\n  Response<Long> zadd(String key, double score, String member);\n\n  Response<Long> zadd(String key, double score, String member, ZAddParams params);\n\n  Response<Long> zadd(String key, Map<String, Double> scoreMembers);\n\n  Response<Long> zadd(String key, Map<String, Double> scoreMembers, ZAddParams params);\n\n  Response<Double> zaddIncr(String key, double score, String member, ZAddParams params);\n\n  Response<Long> zrem(String key, String... members);\n\n  Response<Double> zincrby(String key, double increment, String member);\n\n  Response<Double> zincrby(String key, double increment, String member, ZIncrByParams params);\n\n  Response<Long> zrank(String key, String member);\n\n  Response<Long> zrevrank(String key, String member);\n\n  Response<KeyValue<Long, Double>> zrankWithScore(String key, String member);\n\n  Response<KeyValue<Long, Double>> zrevrankWithScore(String key, String member);\n\n  Response<List<String>> zrange(String key, long start, long stop);\n\n  Response<List<String>> zrevrange(String key, long start, long stop);\n\n  Response<List<Tuple>> zrangeWithScores(String key, long start, long stop);\n\n  Response<List<Tuple>> zrevrangeWithScores(String key, long start, long stop);\n\n  Response<String> zrandmember(String key);\n\n  Response<List<String>> zrandmember(String key, long count);\n\n  Response<List<Tuple>> zrandmemberWithScores(String key, long count);\n\n  Response<Long> zcard(String key);\n\n  Response<Double> zscore(String key, String member);\n\n  Response<List<Double>> zmscore(String key, String... members);\n\n  Response<Tuple> zpopmax(String key);\n\n  Response<List<Tuple>> zpopmax(String key, int count);\n\n  Response<Tuple> zpopmin(String key);\n\n  Response<List<Tuple>> zpopmin(String key, int count);\n\n  Response<Long> zcount(String key, double min, double max);\n\n  Response<Long> zcount(String key, String min, String max);\n\n  Response<List<String>> zrangeByScore(String key, double min, double max);\n\n  Response<List<String>> zrangeByScore(String key, String min, String max);\n\n  Response<List<String>> zrevrangeByScore(String key, double max, double min);\n\n  Response<List<String>> zrangeByScore(String key, double min, double max, int offset, int count);\n\n  Response<List<String>> zrevrangeByScore(String key, String max, String min);\n\n  Response<List<String>> zrangeByScore(String key, String min, String max, int offset, int count);\n\n  Response<List<String>> zrevrangeByScore(String key, double max, double min, int offset, int count);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(String key, double min, double max);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(String key, double max, double min);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(String key, double min, double max, int offset, int count);\n\n  Response<List<String>> zrevrangeByScore(String key, String max, String min, int offset, int count);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(String key, String min, String max);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(String key, String max, String min);\n\n  Response<List<Tuple>> zrangeByScoreWithScores(String key, String min, String max, int offset, int count);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count);\n\n  Response<List<Tuple>> zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count);\n\n  Response<List<String>> zrange(String key, ZRangeParams zRangeParams);\n\n  Response<List<Tuple>> zrangeWithScores(String key, ZRangeParams zRangeParams);\n\n  Response<Long> zrangestore(String dest, String src, ZRangeParams zRangeParams);\n\n  Response<Long> zremrangeByRank(String key, long start, long stop);\n\n  Response<Long> zremrangeByScore(String key, double min, double max);\n\n  Response<Long> zremrangeByScore(String key, String min, String max);\n\n  Response<Long> zlexcount(String key, String min, String max);\n\n  Response<List<String>> zrangeByLex(String key, String min, String max);\n\n  Response<List<String>> zrangeByLex(String key, String min, String max, int offset, int count);\n\n  Response<List<String>> zrevrangeByLex(String key, String max, String min);\n\n  Response<List<String>> zrevrangeByLex(String key, String max, String min, int offset, int count);\n\n  Response<Long> zremrangeByLex(String key, String min, String max);\n\n  default Response<ScanResult<Tuple>> zscan(String key, String cursor) {\n    return zscan(key, cursor, new ScanParams());\n  }\n\n  Response<ScanResult<Tuple>> zscan(String key, String cursor, ScanParams params);\n\n  Response<KeyValue<String, Tuple>> bzpopmax(double timeout, String... keys);\n\n  Response<KeyValue<String, Tuple>> bzpopmin(double timeout, String... keys);\n\n  Response<List<String>> zdiff(String... keys);\n\n  Response<List<Tuple>> zdiffWithScores(String... keys);\n\n  /**\n   * @deprecated Use {@link #zdiffstore(java.lang.String, java.lang.String...)}.\n   */\n  @Deprecated\n  Response<Long> zdiffStore(String dstKey, String... keys);\n\n  Response<Long> zdiffstore(String dstKey, String... keys);\n\n  Response<Long> zinterstore(String dstKey, String... sets);\n\n  Response<Long> zinterstore(String dstKey, ZParams params, String... sets);\n\n  Response<List<String>> zinter(ZParams params, String... keys);\n\n  Response<List<Tuple>> zinterWithScores(ZParams params, String... keys);\n\n  Response<Long> zintercard(String... keys);\n\n  Response<Long> zintercard(long limit, String... keys);\n\n  Response<List<String>> zunion(ZParams params, String... keys);\n\n  Response<List<Tuple>> zunionWithScores(ZParams params, String... keys);\n\n  Response<Long> zunionstore(String dstKey, String... sets);\n\n  Response<Long> zunionstore(String dstKey, ZParams params, String... sets);\n\n  Response<KeyValue<String, List<Tuple>>> zmpop(SortedSetOption option, String... keys);\n\n  Response<KeyValue<String, List<Tuple>>> zmpop(SortedSetOption option, int count, String... keys);\n\n  Response<KeyValue<String, List<Tuple>>> bzmpop(double timeout, SortedSetOption option, String... keys);\n\n  Response<KeyValue<String, List<Tuple>>> bzmpop(double timeout, SortedSetOption option, int count, String... keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.StreamEntryBinary;\nimport redis.clients.jedis.resps.StreamEntryDeletionResult;\n\npublic interface StreamBinaryCommands {\n\n  default byte[] xadd(byte[] key, Map<byte[], byte[]> hash, XAddParams params) {\n    return xadd(key, params, hash);\n  }\n\n  byte[] xadd(byte[] key, XAddParams params, Map<byte[], byte[]> hash);\n\n  long xlen(byte[] key);\n\n  List<Object> xrange(byte[] key, byte[] start, byte[] end);\n\n  List<Object> xrange(byte[] key, byte[] start, byte[] end, int count);\n\n  List<Object> xrevrange(byte[] key, byte[] end, byte[] start);\n\n  List<Object> xrevrange(byte[] key, byte[] end, byte[] start, int count);\n\n  long xack(byte[] key, byte[] group, byte[]... ids);\n\n  /**\n   * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  List<StreamEntryDeletionResult> xackdel(byte[] key, byte[] group, byte[]... ids);\n\n  /**\n   * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  List<StreamEntryDeletionResult> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids);\n\n  String xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream);\n\n  String xgroupSetID(byte[] key, byte[] groupName, byte[] id);\n\n  long xgroupDestroy(byte[] key, byte[] groupName);\n\n  boolean xgroupCreateConsumer(byte[] key, byte[] groupName, byte[] consumerName);\n\n  long xgroupDelConsumer(byte[] key, byte[] groupName, byte[] consumerName);\n\n  long xdel(byte[] key, byte[]... ids);\n\n  /**\n   * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  List<StreamEntryDeletionResult> xdelex(byte[] key, byte[]... ids);\n\n  /**\n   * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  List<StreamEntryDeletionResult> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids);\n\n  long xtrim(byte[] key, long maxLen, boolean approximateLength);\n\n  long xtrim(byte[] key, XTrimParams params);\n\n  Object xpending(byte[] key, byte[] groupName);\n\n  List<Object> xpending(byte[] key, byte[] groupName, XPendingParams params);\n\n  List<byte[]> xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids);\n\n  List<byte[]> xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids);\n\n  List<Object> xautoclaim(byte[] key, byte[] groupName, byte[] consumerName,\n      long minIdleTime, byte[] start, XAutoClaimParams params);\n\n  List<Object> xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName,\n      long minIdleTime, byte[] start, XAutoClaimParams params);\n\n  Object xinfoStream(byte[] key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   */\n  Object xinfoStreamFull(byte[] key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   * @param count stream info count\n   */\n  Object xinfoStreamFull(byte[] key, int count);\n\n  List<Object> xinfoGroups(byte[] key);\n\n  List<Object> xinfoConsumers(byte[] key, byte[] group);\n\n  /**\n   * @deprecated As of Jedis 6.1.0, replaced by {@link #xreadBinary(XReadParams, Map)} or\n   * {@link #xreadBinaryAsMap(XReadParams, Map)} for type safety and better stream entry parsing.\n   */\n  @Deprecated\n  List<Object> xread(XReadParams xReadParams, Map.Entry<byte[], byte[]>... streams);\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use {@link #xreadGroupBinary(byte[], byte[], XReadGroupParams, Map)} or\n   * {@link #xreadGroupBinaryAsMap(byte[], byte[], XReadGroupParams, Map)} instead.\n   */\n  @Deprecated\n  List<Object> xreadGroup(byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams,\n      Map.Entry<byte[], byte[]>... streams);\n\n  /**\n   * Read from one or more streams.\n   * @param xReadParams {@link XReadParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return List of entries. Each entry in the list is a pair of stream name and the entries\n   *     reported for that key.\n   */\n  List<Map.Entry<byte[], List<StreamEntryBinary>>> xreadBinary(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams);\n\n  /**\n   * Read from one or more streams and return a map of stream name to list of entries.\n   * @param xReadParams {@link XReadParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return Map of stream name to list of entries. key is the stream name and value is the list of\n   *     entries reported for that key.\n   */\n  Map<byte[], List<StreamEntryBinary>> xreadBinaryAsMap(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams);\n\n  /**\n   * Read from one or more streams as a consumer group.\n   * @param groupName Consumer group name.\n   * @param consumer Consumer name.\n   * @param xReadGroupParams {@link XReadGroupParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return List of entries. Each entry in the list is a pair of stream name and the entries\n   *     reported for that key.\n   */\n  List<Map.Entry<byte[], List<StreamEntryBinary>>> xreadGroupBinary(byte[] groupName,\n      byte[] consumer, XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams);\n\n  /**\n   * Read from one or more streams as a consumer group and return a map of stream name to list of\n   * entries.\n   * @param groupName Consumer group name.\n   * @param consumer Consumer name.\n   * @param xReadGroupParams {@link XReadGroupParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return Map of stream name to list of entries. key is the stream name and value is the list of\n   *     entries reported for that key.\n   */\n  Map<byte[], List<StreamEntryBinary>> xreadGroupBinaryAsMap(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams);\n\n  /**\n   * XCFGSET key [IDMP-DURATION duration] [IDMP-MAXSIZE maxsize]\n   * Configure idempotent producer settings for a stream.\n   *\n   * @param key Stream name\n   * @param params Configuration parameters\n   * @return OK if successful\n   */\n  byte[] xcfgset(byte[] key, XCfgSetParams params);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StreamCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\n\npublic interface StreamCommands {\n\n  /**\n   * XADD key ID field string [field string ...]\n   *\n   * @return the ID of the added entry\n   */\n  StreamEntryID xadd(String key, StreamEntryID id, Map<String, String> hash);\n\n  /**\n   * XADD key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|ID field value [field value ...]\n   *\n   * @return the ID of the added entry\n   */\n  // Legacy\n  default StreamEntryID xadd(String key, Map<String, String> hash, XAddParams params) {\n    return xadd(key, params, hash);\n  }\n\n  StreamEntryID xadd(String key, XAddParams params, Map<String, String> hash);\n\n  /**\n   * XLEN key\n   *\n   * @return length of stream\n   */\n  long xlen(String key);\n\n  /**\n   * XRANGE key start end\n   *\n   * @param key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate maximum ID possible in the stream\n   * @return The entries with IDs matching the specified range.\n   */\n  List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end);\n\n  /**\n   * XRANGE key start end COUNT count\n   *\n   * @param key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate maximum ID possible in the stream\n   * @param count maximum number of entries returned\n   * @return The entries with IDs matching the specified range.\n   */\n  List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end, int count);\n\n  /**\n   * XREVRANGE key end start\n   *\n   * @param key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate maximum ID possible in the stream\n   * @return the entries with IDs matching the specified range, from the higher ID to the lower ID matching.\n   */\n  List<StreamEntry> xrevrange(String key, StreamEntryID end, StreamEntryID start);\n\n  /**\n   * XREVRANGE key end start COUNT count\n   *\n   * @param key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing {@code null} will\n   * indicate maximum ID possible in the stream\n   * @param count The entries with IDs matching the specified range.\n   * @return the entries with IDs matching the specified range, from the higher ID to the lower ID matching.\n   */\n  List<StreamEntry> xrevrange(String key, StreamEntryID end, StreamEntryID start, int count);\n\n  List<StreamEntry> xrange(String key, String start, String end);\n\n  List<StreamEntry> xrange(String key, String start, String end, int count);\n\n  List<StreamEntry> xrevrange(String key, String end, String start);\n\n  List<StreamEntry> xrevrange(String key, String end, String start, int count);\n\n  /**\n   * XACK key group ID [ID ...]\n   */\n  long xack(String key, String group, StreamEntryID... ids);\n\n  /**\n   * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   * Combines XACK and XDEL functionalities. Acknowledges specified message IDs\n   * in the given consumer group and attempts to delete corresponding stream entries.\n   */\n  List<StreamEntryDeletionResult> xackdel(String key, String group, StreamEntryID... ids);\n\n  /**\n   * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   * Combines XACK and XDEL functionalities. Acknowledges specified message IDs\n   * in the given consumer group and attempts to delete corresponding stream entries.\n   */\n  List<StreamEntryDeletionResult> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids);\n\n  /**\n   * {@code XGROUP CREATE key groupName <id or $>}\n   */\n  String xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream);\n\n  /**\n   * {@code XGROUP SETID key groupName <id or $>}\n   */\n  String xgroupSetID(String key, String groupName, StreamEntryID id);\n\n  /**\n   * XGROUP DESTROY key groupName\n   */\n  long xgroupDestroy(String key, String groupName);\n\n  /**\n   * XGROUP CREATECONSUMER key groupName consumerName\n   */\n  boolean xgroupCreateConsumer(String key, String groupName, String consumerName);\n\n  /**\n   * XGROUP DELCONSUMER key groupName consumerName\n   */\n  long xgroupDelConsumer(String key, String groupName, String consumerName);\n\n  /**\n   * XDEL key ID [ID ...]\n   */\n  long xdel(String key, StreamEntryID... ids);\n\n  /**\n   * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   * Extended XDEL command with enhanced control over message entry deletion\n   * with respect to consumer groups.\n   */\n  List<StreamEntryDeletionResult> xdelex(String key, StreamEntryID... ids);\n\n  /**\n   * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   * Extended XDEL command with enhanced control over message entry deletion\n   * with respect to consumer groups.\n   */\n  List<StreamEntryDeletionResult> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids);\n\n  /**\n   * XTRIM key MAXLEN [~] count\n   */\n  long xtrim(String key, long maxLen, boolean approximate);\n\n  /**\n   * XTRIM key MAXLEN|MINID [=|~] threshold [LIMIT count]\n   */\n  long xtrim(String key, XTrimParams params);\n\n  /**\n   * XPENDING key group\n   */\n  StreamPendingSummary xpending(String key, String groupName);\n\n  /**\n   * XPENDING key group [[IDLE min-idle-time] start end count [consumer]]\n   */\n  List<StreamPendingEntry> xpending(String key, String groupName, XPendingParams params);\n\n  /**\n   * {@code XCLAIM key group consumer min-idle-time <ID-1> ... <ID-N>\n   *        [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]\n   *        [FORCE]}\n   */\n  List<StreamEntry> xclaim(String key, String group, String consumerName, long minIdleTime,\n      XClaimParams params, StreamEntryID... ids);\n\n  /**\n   * {@code XCLAIM key group consumer min-idle-time <ID-1> ... <ID-N>\n   *        [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]\n   *        [FORCE] JUSTID}\n   */\n  List<StreamEntryID> xclaimJustId(String key, String group, String consumerName, long minIdleTime,\n      XClaimParams params, StreamEntryID... ids);\n\n  /**\n   * XAUTOCLAIM key group consumer min-idle-time start [COUNT count]\n   *\n   * @param key Stream Key\n   * @param group Consumer Group\n   * @param consumerName Consumer name to transfer the auto claimed entries\n   * @param minIdleTime Entries pending more than minIdleTime will be transferred ownership\n   * @param start {@link StreamEntryID} - Entries &ge; start will be transferred ownership, passing\n   * {@code null} will indicate '-'\n   * @param params {@link XAutoClaimParams}\n   */\n  Map.Entry<StreamEntryID, List<StreamEntry>> xautoclaim(String key, String group, String consumerName,\n      long minIdleTime, StreamEntryID start, XAutoClaimParams params);\n\n  /**\n   * XAUTOCLAIM key group consumer min-idle-time start [COUNT count] JUSTID\n   *\n   * @param key Stream Key\n   * @param group Consumer Group\n   * @param consumerName Consumer name to transfer the auto claimed entries\n   * @param minIdleTime Entries pending more than minIdleTime will be transferred ownership\n   * @param start {@link StreamEntryID} - Entries &ge; start will be transferred ownership, passing\n   * {@code null} will indicate '-'\n   * @param params {@link XAutoClaimParams}\n   */\n  Map.Entry<StreamEntryID, List<StreamEntryID>> xautoclaimJustId(String key, String group, String consumerName,\n      long minIdleTime, StreamEntryID start, XAutoClaimParams params);\n\n  /**\n   * Introspection command used in order to retrieve different information about the stream\n   * @param key Stream name\n   * @return {@link StreamInfo} that contains information about the stream\n   */\n  StreamInfo xinfoStream(String key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   * @return {@link StreamFullInfo} that contains information about the stream\n   */\n  StreamFullInfo xinfoStreamFull(String key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   * @param count stream info count\n   * @return {@link StreamFullInfo} that contains information about the stream\n   */\n  StreamFullInfo xinfoStreamFull(String key, int count);\n\n  /**\n   * Introspection command used in order to retrieve different information about groups in the stream\n   * @param key Stream name\n   * @return List of {@link StreamGroupInfo} containing information about groups\n   */\n  List<StreamGroupInfo> xinfoGroups(String key);\n\n  /**\n   * Introspection command used in order to retrieve different information about consumers in the group\n   * @param key Stream name\n   * @param group Group name\n   * @return List of {@link StreamConsumersInfo} containing information about consumers that belong\n   * to the group\n   * @deprecated Use {@link #xinfoConsumers2(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated // keep it till at least Jedis 6/7\n  List<StreamConsumersInfo> xinfoConsumers(String key, String group);\n\n  /**\n   * Introspection command used in order to retrieve different information about consumers in the group\n   * @param key Stream name\n   * @param group Group name\n   * @return List of {@link StreamConsumerInfo} containing information about consumers that belong\n   * to the group\n   */\n  List<StreamConsumerInfo> xinfoConsumers2(String key, String group);\n\n  /**\n   * XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]\n   */\n  List<Map.Entry<String, List<StreamEntry>>> xread(XReadParams xReadParams,\n      Map<String, StreamEntryID> streams);\n\n  /**\n   * XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]\n   */\n  Map<String, List<StreamEntry>> xreadAsMap(XReadParams xReadParams,\n      Map<String, StreamEntryID> streams);\n\n  /**\n   * XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]\n   */\n  List<Map.Entry<String, List<StreamEntry>>> xreadGroup(String groupName, String consumer,\n      XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams);\n\n  /**\n   * XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]\n   */\n  Map<String, List<StreamEntry>> xreadGroupAsMap(String groupName, String consumer,\n      XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams);\n\n  /**\n   * XCFGSET key [IDMP-DURATION duration] [IDMP-MAXSIZE maxsize]\n   * Configure idempotent producer settings for a stream.\n   *\n   * @param key Stream name\n   * @param params Configuration parameters\n   * @return OK if successful\n   */\n  String xcfgset(String key, XCfgSetParams params);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.StreamEntryBinary;\nimport redis.clients.jedis.resps.StreamEntryDeletionResult;\n\npublic interface StreamPipelineBinaryCommands {\n\n  default Response<byte[]> xadd(byte[] key, Map<byte[], byte[]> hash, XAddParams params) {\n    return xadd(key, params, hash);\n  }\n\n  Response<byte[]> xadd(byte[] key, XAddParams params, Map<byte[], byte[]> hash);\n\n  Response<Long> xlen(byte[] key);\n\n  Response<List<Object>> xrange(byte[] key, byte[] start, byte[] end);\n\n  Response<List<Object>> xrange(byte[] key, byte[] start, byte[] end, int count);\n\n  Response<List<Object>> xrevrange(byte[] key, byte[] end, byte[] start);\n\n  Response<List<Object>> xrevrange(byte[] key, byte[] end, byte[] start, int count);\n\n  Response<Long> xack(byte[] key, byte[] group, byte[]... ids);\n\n  Response<List<StreamEntryDeletionResult>> xackdel(byte[] key, byte[] group, byte[]... ids);\n\n  Response<List<StreamEntryDeletionResult>> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids);\n\n  Response<String> xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream);\n\n  Response<String> xgroupSetID(byte[] key, byte[] groupName, byte[] id);\n\n  Response<Long> xgroupDestroy(byte[] key, byte[] groupName);\n\n  Response<Boolean> xgroupCreateConsumer(byte[] key, byte[] groupName, byte[] consumerName);\n\n  Response<Long> xgroupDelConsumer(byte[] key, byte[] groupName, byte[] consumerName);\n\n  Response<Long> xdel(byte[] key, byte[]... ids);\n\n  Response<List<StreamEntryDeletionResult>> xdelex(byte[] key, byte[]... ids);\n\n  Response<List<StreamEntryDeletionResult>> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids);\n\n  Response<Long> xtrim(byte[] key, long maxLen, boolean approximateLength);\n\n  Response<Long> xtrim(byte[] key, XTrimParams params);\n\n  Response<Object> xpending(byte[] key, byte[] groupName);\n\n  Response<List<Object>> xpending(byte[] key, byte[] groupName, XPendingParams params);\n\n  Response<List<byte[]>> xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids);\n\n  Response<List<byte[]>> xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids);\n\n  Response<List<Object>> xautoclaim(byte[] key, byte[] groupName, byte[] consumerName,\n      long minIdleTime, byte[] start, XAutoClaimParams params);\n\n  Response<List<Object>> xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName,\n      long minIdleTime, byte[] start, XAutoClaimParams params);\n\n  Response<Object> xinfoStream(byte[] key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   */\n  Response<Object> xinfoStreamFull(byte[] key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   * @param count stream info count\n   */\n  Response<Object> xinfoStreamFull(byte[] key, int count);\n\n  Response<List<Object>> xinfoGroups(byte[] key);\n\n  Response<List<Object>> xinfoConsumers(byte[] key, byte[] group);\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use {@link #xreadBinary(XReadParams, Map)} or\n   *     {@link #xreadBinaryAsMap(XReadParams, Map)} for type safety and better stream entry\n   *     parsing.\n   */\n  @Deprecated\n  Response<List<Object>> xread(XReadParams xReadParams, Map.Entry<byte[], byte[]>... streams);\n\n  /**\n   * @deprecated As of Jedis 6.1.0, use\n   *     {@link #xreadGroupBinary(byte[], byte[], XReadGroupParams, Map)} or\n   *     {@link #xreadGroupBinaryAsMap(byte[], byte[], XReadGroupParams, Map)} instead.\n   */\n  @Deprecated\n  Response<List<Object>> xreadGroup(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map.Entry<byte[], byte[]>... streams);\n\n\n  /**\n   * Read from one or more streams.\n   *\n   * @param xReadParams {@link XReadParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return List of entries. Each entry in the list is a pair of stream name and the entries reported for that key.\n   */\n  Response<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadBinary(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams);\n\n  /**\n   * Read from one or more streams and return a map of stream name to list of entries.\n   *\n   * @param xReadParams {@link XReadParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return Map of stream name to list of entries.\n   */\n  Response<Map<byte[], List<StreamEntryBinary>>> xreadBinaryAsMap(XReadParams xReadParams,\n      Map<byte[], StreamEntryID> streams);\n\n  /**\n   * Read from one or more streams using a consumer group.\n   *\n   * @param groupName Consumer group name\n   * @param consumer Consumer name\n   * @param xReadGroupParams {@link XReadGroupParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return List of entries. Each entry in the list is a pair of stream name and the entries reported for that key.\n   */\n  Response<List<Map.Entry<byte[], List<StreamEntryBinary>>>> xreadGroupBinary(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams);\n\n  /**\n   * Read from one or more streams using a consumer group and return a map of stream name to list of entries.\n   *\n   * @param groupName Consumer group name\n   * @param consumer Consumer name\n   * @param xReadGroupParams {@link XReadGroupParams}\n   * @param streams Map of stream name and ID to read from.\n   * @return Map of stream name to list of entries.\n   */\n  Response<Map<byte[], List<StreamEntryBinary>>> xreadGroupBinaryAsMap(byte[] groupName, byte[] consumer,\n      XReadGroupParams xReadGroupParams, Map<byte[], StreamEntryID> streams);\n\n  /**\n   * XCFGSET key [IDMP-DURATION duration] [IDMP-MAXSIZE maxsize]\n   * Configure idempotent producer settings for a stream.\n   *\n   * @param key Stream name\n   * @param params Configuration parameters\n   * @return OK if successful\n   */\n  Response<byte[]> xcfgset(byte[] key, XCfgSetParams params);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\n\npublic interface StreamPipelineCommands {\n\n  /**\n   * XADD key ID field string [field string ...]\n   *\n   * @return the ID of the added entry\n   */\n  Response<StreamEntryID> xadd(String key, StreamEntryID id, Map<String, String> hash);\n\n  /**\n   * XADD key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|ID field value [field value ...]\n   *\n   * @return the ID of the added entry\n   */\n  // Legacy\n  default Response<StreamEntryID> xadd(String key, Map<String, String> hash, XAddParams params) {\n    return xadd(key, params, hash);\n  }\n\n  Response<StreamEntryID> xadd(String key, XAddParams params, Map<String, String> hash);\n\n  /**\n   * XLEN key\n   *\n   * @return length of stream\n   */\n  Response<Long> xlen(String key);\n\n  /**\n   * XRANGE key start end\n   *\n   * @param key key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate maximum ID possible in the stream\n   * @return The entries with IDs matching the specified range.\n   */\n  Response<List<StreamEntry>> xrange(String key, StreamEntryID start, StreamEntryID end);\n\n  /**\n   * XRANGE key start end COUNT count\n   *\n   * @param key key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate maximum ID possible in the stream\n   * @param count maximum number of entries returned\n   * @return The entries with IDs matching the specified range.\n   */\n  Response<List<StreamEntry>> xrange(String key, StreamEntryID start, StreamEntryID end, int count);\n\n  /**\n   * XREVRANGE key end start\n   *\n   * @param key key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate maximum ID possible in the stream\n   * @return the entries with IDs matching the specified range, from the higher ID to the lower ID matching.\n   */\n  Response<List<StreamEntry>> xrevrange(String key, StreamEntryID end, StreamEntryID start);\n\n  /**\n   * XREVRANGE key end start COUNT count\n   *\n   * @param key key\n   * @param start minimum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate minimum ID possible in the stream\n   * @param end maximum {@link StreamEntryID} for the retrieved range, passing <code>null</code> will indicate maximum ID possible in the stream\n   * @param count The entries with IDs matching the specified range.\n   * @return the entries with IDs matching the specified range, from the higher ID to the lower ID matching.\n   */\n  Response<List<StreamEntry>> xrevrange(String key, StreamEntryID end, StreamEntryID start, int count);\n\n  Response<List<StreamEntry>> xrange(String key, String start, String end);\n\n  Response<List<StreamEntry>> xrange(String key, String start, String end, int count);\n\n  Response<List<StreamEntry>> xrevrange(String key, String end, String start);\n\n  Response<List<StreamEntry>> xrevrange(String key, String end, String start, int count);\n\n  /**\n   * XACK key group ID [ID ...]\n   */\n  Response<Long> xack(String key, String group, StreamEntryID... ids);\n\n  /**\n   * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  Response<List<StreamEntryDeletionResult>> xackdel(String key, String group, StreamEntryID... ids);\n\n  /**\n   * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  Response<List<StreamEntryDeletionResult>> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids);\n\n  /**\n   * {@code XGROUP CREATE key groupName <id or $>}\n   */\n  Response<String> xgroupCreate( String key, String groupName, StreamEntryID id, boolean makeStream);\n\n  /**\n   * {@code XGROUP SETID key groupName <id or $>}\n   */\n  Response<String> xgroupSetID( String key, String groupName, StreamEntryID id);\n\n  /**\n   * XGROUP DESTROY key groupName\n   */\n  Response<Long> xgroupDestroy(String key, String groupName);\n\n  /**\n   * XGROUP CREATECONSUMER key groupName consumerName\n   */\n  Response<Boolean> xgroupCreateConsumer( String key, String groupName, String consumerName);\n\n  /**\n   * XGROUP DELCONSUMER key groupName consumerName\n   */\n  Response<Long> xgroupDelConsumer( String key, String groupName, String consumerName);\n\n  /**\n   * XPENDING key group\n   */\n  Response<StreamPendingSummary> xpending(String key, String groupName);\n\n  /**\n   * XPENDING key group [[IDLE min-idle-time] start end count [consumer]]\n   */\n  Response<List<StreamPendingEntry>> xpending(String key, String groupName, XPendingParams params);\n\n  /**\n   * XDEL key ID [ID ...]\n   */\n  Response<Long> xdel(String key, StreamEntryID... ids);\n\n  /**\n   * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  Response<List<StreamEntryDeletionResult>> xdelex(String key, StreamEntryID... ids);\n\n  /**\n   * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...]\n   */\n  Response<List<StreamEntryDeletionResult>> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids);\n\n  /**\n   * XTRIM key MAXLEN [~] count\n   */\n  Response<Long> xtrim(String key, long maxLen, boolean approximate);\n\n  /**\n   * XTRIM key MAXLEN|MINID [=|~] threshold [LIMIT count]\n   */\n  Response<Long> xtrim(String key, XTrimParams params);\n\n  /**\n   * {@code XCLAIM key group consumer min-idle-time <ID-1> ... <ID-N>\n   *        [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]\n   *        [FORCE]}\n   */\n  Response<List<StreamEntry>> xclaim(String key, String group, String consumerName, long minIdleTime,\n      XClaimParams params, StreamEntryID... ids);\n\n  /**\n   * {@code XCLAIM key group consumer min-idle-time <ID-1> ... <ID-N>\n   *        [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]\n   *        [FORCE] JUSTID}\n   */\n  Response<List<StreamEntryID>> xclaimJustId(String key, String group, String consumerName, long minIdleTime,\n      XClaimParams params, StreamEntryID... ids);\n\n  /**\n   * XAUTOCLAIM key group consumer min-idle-time start [COUNT count]\n   *\n   * @param key Stream Key\n   * @param group Consumer Group\n   * @param consumerName Consumer name to transfer the auto claimed entries\n   * @param minIdleTime Entries pending more than minIdleTime will be transferred ownership\n   * @param start {@link StreamEntryID} - Entries &ge; start will be transferred ownership, passing\n   * {@code null} will indicate '-'\n   * @param params {@link XAutoClaimParams}\n   */\n  Response<Map.Entry<StreamEntryID, List<StreamEntry>>> xautoclaim(String key, String group, String consumerName,\n      long minIdleTime, StreamEntryID start, XAutoClaimParams params);\n\n  /**\n   * XAUTOCLAIM key group consumer min-idle-time start [COUNT count] JUSTID\n   *\n   * @param key Stream Key\n   * @param group Consumer Group\n   * @param consumerName Consumer name to transfer the auto claimed entries\n   * @param minIdleTime Entries pending more than minIdleTime will be transferred ownership\n   * @param start {@link StreamEntryID} - Entries &ge; start will be transferred ownership, passing\n   * {@code null} will indicate '-'\n   * @param params {@link XAutoClaimParams}\n   */\n  Response<Map.Entry<StreamEntryID, List<StreamEntryID>>> xautoclaimJustId(String key, String group, String consumerName,\n      long minIdleTime, StreamEntryID start, XAutoClaimParams params);\n\n  /**\n   * Introspection command used in order to retrieve different information about the stream\n   * @param key Stream name\n   * @return {@link StreamInfo} that contains information about the stream\n   */\n  Response<StreamInfo> xinfoStream(String key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   * @return {@link StreamFullInfo} that contains information about the stream\n   */\n  Response<StreamFullInfo> xinfoStreamFull(String key);\n\n  /**\n   * Introspection command used in order to retrieve all information about the stream\n   * @param key Stream name\n   * @param count stream info count\n   * @return {@link StreamFullInfo} that contains information about the stream\n   */\n  Response<StreamFullInfo> xinfoStreamFull(String key, int count);\n\n  /**\n   * Introspection command used in order to retrieve different information about groups in the stream\n   * @param key Stream name\n   * @return List of {@link StreamGroupInfo} containing information about groups\n   */\n  Response<List<StreamGroupInfo>> xinfoGroups(String key);\n\n  /**\n   * Introspection command used in order to retrieve different information about consumers in the group\n   * @param key Stream name\n   * @param group Group name\n   * @return List of {@link StreamConsumersInfo} containing information about consumers that belong\n   * to the group\n   * @deprecated Use {@link #xinfoConsumers2(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated // keep it till at least Jedis 6/7\n  Response<List<StreamConsumersInfo>> xinfoConsumers(String key, String group);\n\n  /**\n   * Introspection command used in order to retrieve different information about consumers in the group\n   * @param key Stream name\n   * @param group Group name\n   * @return List of {@link StreamConsumerInfo} containing information about consumers that belong\n   * to the group\n   */\n  Response<List<StreamConsumerInfo>> xinfoConsumers2(String key, String group);\n\n  /**\n   * XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]\n   */\n  Response<List<Map.Entry<String, List<StreamEntry>>>> xread(XReadParams xReadParams,\n      Map<String, StreamEntryID> streams);\n\n  /**\n   * XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]\n   */\n  Response<Map<String, List<StreamEntry>>> xreadAsMap(XReadParams xReadParams,\n      Map<String, StreamEntryID> streams);\n\n  /**\n   * XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]\n   */\n  Response<List<Map.Entry<String, List<StreamEntry>>>> xreadGroup(String groupName, String consumer,\n      XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams);\n\n  /**\n   * XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]\n   */\n  Response<Map<String, List<StreamEntry>>> xreadGroupAsMap(String groupName, String consumer,\n      XReadGroupParams xReadGroupParams, Map<String, StreamEntryID> streams);\n\n  /**\n   * XCFGSET key [IDMP-DURATION duration] [IDMP-MAXSIZE maxsize]\n   * Configure idempotent producer settings for a stream.\n   *\n   * @param key Stream name\n   * @param params Configuration parameters\n   * @return OK if successful\n   */\n  Response<String> xcfgset(String key, XCfgSetParams params);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StringBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.resps.LCSMatchResult;\n\npublic interface StringBinaryCommands extends BitBinaryCommands {\n\n  String set(byte[] key, byte[] value);\n\n  String set(byte[] key, byte[] value, SetParams params);\n\n  byte[] get(byte[] key);\n\n  byte[] setGet(byte[] key, byte[] value);\n\n  byte[] setGet(byte[] key, byte[] value, SetParams params);\n\n  byte[] getDel(byte[] key);\n\n  byte[] getEx(byte[] key, GetExParams params);\n\n  long setrange(byte[] key, long offset, byte[] value);\n\n  byte[] getrange(byte[] key, long startOffset, long endOffset);\n\n  /**\n   * @deprecated Use {@link StringBinaryCommands#setGet(byte[], byte[])}.\n   */\n  @Deprecated\n  byte[] getSet(byte[] key, byte[] value);\n\n  /**\n   * @deprecated Use {@link StringBinaryCommands#set(byte[], byte[], SetParams)} with {@link SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  long setnx(byte[] key, byte[] value);\n\n  /**\n   * @deprecated Use {@link StringBinaryCommands#set(byte[], byte[], SetParams)} with {@link SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  String setex(byte[] key, long seconds, byte[] value);\n\n  /**\n   * @deprecated Use {@link StringBinaryCommands#set(byte[], byte[], SetParams)} with {@link SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  String psetex(byte[] key, long milliseconds, byte[] value);\n\n  List<byte[]> mget(byte[]... keys);\n\n  String mset(byte[]... keysvalues);\n\n  long msetnx(byte[]... keysvalues);\n\n  /**\n   * Multi-set with optional condition and expiration.\n   * <p>\n   * Sets the respective keys to the respective values, similar to {@link #mset(byte[]...) MSET},\n   * but allows conditional set (NX|XX) and expiration options via {@link MSetExParams}.\n   * If the condition is not met for any key, no key is set.\n   * <p>\n   * Both MSET and MSETEX are atomic operations. This means that if multiple keys are provided,\n   * another client will either see the changes for all keys at once, or no changes at all.\n   * <p>\n   * Options (in {@link MSetExParams}): NX or XX, and expiration: EX seconds | PX milliseconds |\n   * EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL.\n   * <p>\n   * Time complexity: O(N) where N is the number of keys to set.\n   * @param params condition and expiration parameters\n   * @param keysvalues pairs of keys and their values, e.g. {@code msetex(params, \"foo\".getBytes(), \"foovalue\".getBytes(), \"bar\".getBytes(), \"barvalue\".getBytes())}\n   * @return {@code true} if all the keys were set, {@code false} if none were set (condition not satisfied)\n   * @see #mset(byte[]...)\n   * @see #msetnx(byte[]...)\n   */\n  boolean msetex(MSetExParams params, byte[]... keysvalues);\n\n  long incr(byte[] key);\n\n  long incrBy(byte[] key, long increment);\n\n  double incrByFloat(byte[] key, double increment);\n\n  long decr(byte[] key);\n\n  long decrBy(byte[] key, long decrement);\n\n  long append(byte[] key, byte[] value);\n\n  /**\n   * @deprecated Use {@link StringBinaryCommands#getrange(byte[], long, long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  byte[] substr(byte[] key, int start, int end);\n\n  long strlen(byte[] key);\n\n  /**\n   * Calculate the longest common subsequence of keyA and keyB.\n   * @param keyA\n   * @param keyB\n   * @param params\n   * @return According to LCSParams to decide to return content to fill LCSMatchResult.\n   */\n  LCSMatchResult lcs(byte[] keyA, byte[] keyB, LCSParams params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StringCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.resps.LCSMatchResult;\n\npublic interface StringCommands extends BitCommands {\n\n  /**\n   * <b><a href=\"http://redis.io/commands/set\">Set Command</a></b>\n   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1 GB).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return OK\n   */\n  String set(String key, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/set\">Set Command</a></b>\n   * Set the string value as value of the key. Can be used with optional params.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @param params {@link SetParams}\n   * @return simple-string-reply {@code OK} if {@code SET} was executed correctly, or {@code null}\n   * if the {@code SET} operation was not performed because the user specified the NX or XX option\n   * but the condition was not met.\n   */\n  String set(String key, String value, SetParams params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/get\">Get Command</a></b>\n   * Get the value of the specified key. If the key does not exist the special value 'nil' is\n   * returned. If the value stored at key is not a string an error is returned because GET can only\n   * handle string values.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The value stored in key\n   */\n  String get(String key);\n\n  String setGet(String key, String value);\n\n  String setGet(String key, String value, SetParams params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/getdel\">GetDel Command</a></b>\n   * Get the value of key and delete the key. This command is similar to GET, except for the fact\n   * that it also deletes the key on success (if and only if the key's value type is a string).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @return The value stored in key\n   */\n  String getDel(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/getex\">GetEx Command</a></b>\n   * Get the value of key and optionally set its expiration. GETEX is similar to {@link StringCommands#get(String) GET},\n   * but is a write command with additional options:\n   * EX seconds -- Set the specified expire time, in seconds.\n   * PX milliseconds -- Set the specified expire time, in milliseconds.\n   * EXAT timestamp-seconds -- Set the specified Unix time at which the key will expire, in seconds.\n   * PXAT timestamp-milliseconds -- Set the specified Unix time at which the key will expire, in milliseconds.\n   * PERSIST -- Remove the time to live associated with the key.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param params {@link GetExParams}\n   * @return The value stored in key\n   */\n  String getEx(String key, GetExParams params);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/setrange\">SetRange Command</a></b>\n   * GETRANGE overwrite part of the string stored at key, starting at the specified offset, for the entire\n   * length of value. If the offset is larger than the current length of the string at key, the string is\n   * padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this\n   * command will make sure it holds a string large enough to be able to set value at offset.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param offset\n   * @param value\n   * @return The length of the string after it was modified by the command\n   */\n  long setrange(String key, long offset, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/getrange\">GetRange Command</a></b>\n   * Return the substring of the string value stored at key, determined by the offsets start\n   * and end (both are inclusive). Negative offsets can be used in order to provide an offset starting\n   * from the end of the string. So -1 means the last character, -2 the penultimate and so forth.\n   * <p>\n   * Time complexity: O(N) where N is the length of the returned string\n   * @param key\n   * @param startOffset\n   * @param endOffset\n   * @return The substring\n   */\n  String getrange(String key, long startOffset, long endOffset);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/getset\">GetSet Command</a></b>\n   * GETSET is an atomic set this value and return the old value command. Set key to the string\n   * value and return the old value stored at key. The string can't be longer than 1073741824 byte (1 GB).\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return The old value that was stored in key\n   * @deprecated Use {@link StringCommands#setGet(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  String getSet(String key, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/setnx\">SetNX Command</a></b>\n   * SETNX works exactly like {@link StringCommands#set(String, String) SET} with the only difference that if\n   * the key already exists no operation is performed. SETNX actually means \"SET if Not Exists\".\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param value\n   * @return 1 if the key was set, 0 otherwise\n   * @deprecated Use {@link StringCommands#set(String, String, SetParams)} with {@link SetParams#nx()}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  long setnx(String key, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/setex\">SetEx Command</a></b>\n   * The command is exactly equivalent to the following group of commands:\n   * {@link StringCommands#set(String, String) SET} + {@link KeyBinaryCommands#expire(byte[], long) EXPIRE}.\n   * The operation is atomic.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param seconds\n   * @param value\n   * @return OK\n   * @deprecated Use {@link StringCommands#set(String, String, SetParams)} with {@link SetParams#ex(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  String setex(String key, long seconds, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/psetex\">PSetEx Command</a></b>\n   * PSETEX works exactly like {@link StringCommands#setex(String, long, String) SETEX} with the sole difference\n   * that the expire time is specified in milliseconds instead of seconds.\n   * <p>\n   * Time complexity: O(1)\n   * @param key\n   * @param milliseconds\n   * @param value\n   * @return OK\n   * @deprecated Use {@link StringCommands#set(String, String, SetParams)} with {@link SetParams#px(long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.6.12.\n   */\n  @Deprecated\n  String psetex(String key, long milliseconds, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/mget\">MGet Command</a></b>\n   * Get the values of all the specified keys. If one or more keys don't exist or is not of type\n   * String, a 'nil' value is returned instead of the value of the specified key, but the operation\n   * never fails.\n   * <p>\n   * Time complexity: O(1) for every key\n   * @param keys\n   * @return Multi bulk reply\n   */\n  List<String> mget(String... keys);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/mset\">MSet Command</a></b>\n   * Set the respective keys to the respective values. MSET will replace old values with new\n   * values, while {@link StringCommands#msetnx(String...) MSETNX} will not perform any operation at all even\n   * if just a single key already exists.\n   * <p>\n   * Because of this semantic MSETNX can be used in order to set different keys representing\n   * different fields of an unique logic object in a way that ensures that either all the fields or\n   * none at all are set.\n   * <p>\n   * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B\n   * are modified, another connection talking to Redis can either see the changes to both A and B at\n   * once, or no modification at all.\n   * @param keysvalues pairs of keys and their values\n   *                   e.g mset(\"foo\", \"foovalue\", \"bar\", \"barvalue\")\n   * @return OK\n   */\n  String mset(String... keysvalues);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/msetnx\">MSetNX Command</a></b>\n   * Set the respective keys to the respective values. {@link StringCommands#mset(String...) MSET} will\n   * replace old values with new values, while MSETNX will not perform any operation at all even if\n   * just a single key already exists.\n   * <p>\n   * Because of this semantic MSETNX can be used in order to set different keys representing\n   * different fields of an unique logic object in a way that ensures that either all the fields or\n   * none at all are set.\n   * <p>\n   * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B\n   * are modified, another connection talking to Redis can either see the changes to both A and B at\n   * once, or no modification at all.\n   * @param keysvalues pairs of keys and their values\n   *                   e.g msetnx(\"foo\", \"foovalue\", \"bar\", \"barvalue\")\n   * @return 1 if the all the keys were set, 0 if no key was set (at least one key already existed)\n   */\n  long msetnx(String... keysvalues);\n\n  /**\n   * Multi-set with optional condition and expiration.\n   * <p>\n   * Sets the respective keys to the respective values, similar to {@link #mset(String...) MSET},\n   * but allows conditional set (NX|XX) and expiration options via {@link MSetExParams}.\n   * If the condition is not met for any key, no key is set.\n   * <p>\n   * Both MSET and MSETEX are atomic operations. This means that if multiple keys are provided,\n   * another client will either see the changes for all keys at once, or no changes at all.\n   * <p>\n   * Options (in {@link MSetExParams}): NX or XX, and expiration: EX seconds | PX milliseconds |\n   * EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL.\n   * <p>\n   * Time complexity: O(N) where N is the number of keys to set.\n   * @param params condition and expiration parameters\n   * @param keysvalues pairs of keys and their values, e.g. {@code msetex(params, \"foo\", \"foovalue\", \"bar\", \"barvalue\")}\n   * @return {@code true} if all the keys were set, {@code false} if none were set (condition not satisfied)\n   * @see #mset(String...)\n   * @see #msetnx(String...)\n   */\n  boolean msetex(MSetExParams params, String... keysvalues);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/incr\">Incr Command</a></b>\n   * Increment the number stored at key by one. If the key does not exist or contains a value of a\n   * wrong type, set the key to the value of \"0\" before to perform the increment operation.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the key to increment\n   * @return The value of the key after the increment\n   */\n  long incr(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/incrby\">IncrBy Command</a></b>\n   * INCRBY work just like {@link StringCommands#incr(String) INCR} but instead to increment by 1 the\n   * increment is integer.\n   * <p>\n   * INCR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the key to increment\n   * @param increment the value to increment by\n   * @return The value of the key after the increment\n   */\n  long incrBy(String key, long increment);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/incrbyfloat\">IncrByFloat Command</a></b>\n   * INCRBYFLOAT work just like {@link StringCommands#incrBy(String, long)} INCRBY} but increments by floats\n   * instead of integers.\n   * <p>\n   * INCRBYFLOAT commands are limited to double precision floating point values.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"double\" types.\n   * Simply the string stored at the key is parsed as a base double precision floating point value,\n   * incremented, and then converted back as a string. There is no DECRYBYFLOAT but providing a\n   * negative value will work as expected.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the key to increment\n   * @param increment the value to increment by\n   * @return The value of the key after the increment\n   */\n  double incrByFloat(String key, double increment);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/decr\">Decr Command</a></b>\n   * Decrement the number stored at key by one. If the key does not exist or contains a value of a\n   * wrong type, set the key to the value of \"0\" before to perform the decrement operation.\n   * <p>\n   * DECR commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the key to decrement\n   * @return The value of the key after the decrement\n   */\n  long decr(String key);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/decrby\">DecrBy Command</a></b>\n   * DECRBY work just like {@link StringCommands#decr(String) DECR} but instead to decrement by 1 the\n   * decrement is integer.\n   * <p>\n   * DECRBY commands are limited to 64-bit signed integers.\n   * <p>\n   * Note: this is actually a string operation, that is, in Redis there are not \"integer\" types.\n   * Simply the string stored at the key is parsed as a base 10 64-bit signed integer, incremented,\n   * and then converted back as a string.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the key to decrement\n   * @param decrement the value to decrement by\n   * @return The value of the key after the decrement\n   */\n  long decrBy(String key, long decrement);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/append\">Append Command</a></b>\n   * If the key already exists and is a string, this command appends the provided value at the end\n   * of the string. If the key does not exist it is created and set as an empty string, so APPEND\n   * will be very similar to SET in this special case.\n   * <p>\n   * Time complexity: O(1). The amortized time complexity is O(1) assuming the appended value is\n   * small and the already present value is of any size, since the dynamic string library used by\n   * Redis will double the free space available on every reallocation.\n   * @param key the key to append to\n   * @param value the value to append\n   * @return The total length of the string after the append operation.\n   */\n  long append(String key, String value);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/substr\">SubStr Command</a></b>\n   * Return a subset of the string from offset start to offset end (both offsets are inclusive).\n   * Negative offsets can be used in order to provide an offset starting from the end of the string.\n   * So -1 means the last char, -2 the penultimate and so forth.\n   * <p>\n   * The function handles out of range requests without raising an error, but just limiting the\n   * resulting range to the actual length of the string.\n   * <p>\n   * Time complexity: O(start+n) (with start being the start index and n the total length of the\n   * requested range). Note that the lookup part of this command is O(1) so for small strings this\n   * is actually an O(1) command.\n   * @param key\n   * @param start\n   * @param end\n   * @return The substring\n   * @deprecated Use {@link StringCommands#getrange(String, long, long)}.\n   * Deprecated in Jedis 7.3.0. Mirrors Redis deprecation since 2.0.0.\n   */\n  @Deprecated\n  String substr(String key, int start, int end);\n\n  /**\n   * <b><a href=\"http://redis.io/commands/strlen\">StrLen Command</a></b>\n   * Return the length of the string value stored at key.\n   * @param key\n   * @return The length of the string at key, or 0 when key does not exist\n   */\n  long strlen(String key);\n\n  /**\n   * Calculate the longest common subsequence of keyA and keyB.\n   * @param keyA\n   * @param keyB\n   * @param params\n   * @return According to LCSParams to decide to return content to fill LCSMatchResult.\n   */\n  LCSMatchResult lcs(String keyA, String keyB, LCSParams params);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StringPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.resps.LCSMatchResult;\n\npublic interface StringPipelineBinaryCommands extends BitPipelineBinaryCommands {\n\n  Response<String> set(byte[] key, byte[] value);\n\n  Response<String> set(byte[] key, byte[] value, SetParams params);\n\n  Response<byte[]> get(byte[] key);\n\n  Response<byte[]> setGet(byte[] key, byte[] value);\n\n  Response<byte[]> setGet(byte[] key, byte[] value, SetParams params);\n\n  Response<byte[]> getDel(byte[] key);\n\n  Response<byte[]> getEx(byte[] key, GetExParams params);\n\n  Response<Long> setrange(byte[] key, long offset, byte[] value);\n\n  Response<byte[]> getrange(byte[] key, long startOffset, long endOffset);\n\n  /**\n   * @deprecated {@link StringPipelineBinaryCommands#setGet(byte[], byte[], redis.clients.jedis.params.SetParams)}.\n   */\n  @Deprecated\n  Response<byte[]> getSet(byte[] key, byte[] value);\n\n  Response<Long> setnx(byte[] key, byte[] value);\n\n  Response<String> setex(byte[] key, long seconds, byte[] value);\n\n  Response<String> psetex(byte[] key, long milliseconds, byte[] value);\n\n  Response<List<byte[]>> mget(byte[]... keys);\n\n  Response<String> mset(byte[]... keysvalues);\n\n  Response<Long> msetnx(byte[]... keysvalues);\n\n  /**\n   * Multi-set with optional condition and expiration.\n   * <p>\n   * Sets the respective keys to the respective values, similar to {@link #mset(byte[]...) MSET},\n   * but allows conditional set (NX|XX) and expiration options via {@link MSetExParams}.\n   * If the condition is not met for any key, no key is set.\n   * <p>\n   * Both MSET and MSETEX are atomic operations. This means that if multiple keys are provided,\n   * another client will either see the changes for all keys at once, or no changes at all.\n   * <p>\n   * Options (in {@link MSetExParams}): NX or XX, and expiration: EX seconds | PX milliseconds |\n   * EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL.\n   * <p>\n   * Time complexity: O(N) where N is the number of keys to set.\n   * @param params condition and expiration parameters\n   * @param keysvalues pairs of keys and their values, e.g. {@code msetex(params, \"foo\".getBytes(), \"foovalue\".getBytes(), \"bar\".getBytes(), \"barvalue\".getBytes())}\n   * @return {@code Response<Boolean>} that is {@code true} if all keys were set, {@code false} if none were set (condition not satisfied)\n   * @see #mset(byte[]...)\n   * @see #msetnx(byte[]...)\n   */\n  Response<Boolean> msetex(MSetExParams params, byte[]... keysvalues);\n\n  Response<Long> incr(byte[] key);\n\n  Response<Long> incrBy(byte[] key, long increment);\n\n  Response<Double> incrByFloat(byte[] key, double increment);\n\n  Response<Long> decr(byte[] key);\n\n  Response<Long> decrBy(byte[] key, long decrement);\n\n  Response<Long> append(byte[] key, byte[] value);\n\n  Response<byte[]> substr(byte[] key, int start, int end);\n\n  Response<Long> strlen(byte[] key);\n\n  Response<LCSMatchResult> lcs(byte[] keyA, byte[] keyB, LCSParams params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/StringPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.resps.LCSMatchResult;\n\nimport java.util.List;\n\npublic interface StringPipelineCommands extends BitPipelineCommands {\n\n  Response<String> set(String key, String value);\n\n  Response<String> set(String key, String value, SetParams params);\n\n  Response<String> get(String key);\n\n  Response<String> setGet(String key, String value);\n\n  Response<String> setGet(String key, String value, SetParams params);\n\n  Response<String> getDel(String key);\n\n  Response<String> getEx(String key, GetExParams params);\n\n  Response<Long> setrange(String key, long offset, String value);\n\n  Response<String> getrange(String key, long startOffset, long endOffset);\n\n  /**\n   * @deprecated Use {@link StringPipelineCommands#setGet(java.lang.String, java.lang.String)}.\n   */\n  @Deprecated\n  Response<String> getSet(String key, String value);\n\n  Response<Long> setnx(String key, String value);\n\n  Response<String> setex(String key, long seconds, String value);\n\n  Response<String> psetex(String key, long milliseconds, String value);\n\n  Response<List<String>> mget(String... keys);\n\n  Response<String> mset(String... keysvalues);\n\n  Response<Long> msetnx(String... keysvalues);\n\n  /**\n   * Multi-set with optional condition and expiration.\n   * <p>\n   * Sets the respective keys to the respective values, similar to {@link #mset(String...) MSET},\n   * but allows conditional set (NX|XX) and expiration options via {@link MSetExParams}.\n   * If the condition is not met for any key, no key is set.\n   * <p>\n   * Both MSET and MSETEX are atomic operations. This means that if multiple keys are provided,\n   * another client will either see the changes for all keys at once, or no changes at all.\n   * <p>\n   * Options (in {@link MSetExParams}): NX or XX, and expiration: EX seconds | PX milliseconds |\n   * EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL.\n   * <p>\n   * Time complexity: O(N) where N is the number of keys to set.\n   * @param params condition and expiration parameters\n   * @param keysvalues pairs of keys and their values, e.g. {@code msetex(params, \"foo\", \"foovalue\", \"bar\", \"barvalue\")}\n   * @return {@code Response<Boolean>} that is {@code true} if all keys were set, {@code false} if none were set (condition not satisfied)\n   * @see #mset(String...)\n   * @see #msetnx(String...)\n   */\n  Response<Boolean> msetex(MSetExParams params, String... keysvalues);\n\n  Response<Long> incr(String key);\n\n  Response<Long> incrBy(String key, long increment);\n\n  Response<Double> incrByFloat(String key, double increment);\n\n  Response<Long> decr(String key);\n\n  Response<Long> decrBy(String key, long decrement);\n\n  Response<Long> append(String key, String value);\n\n  Response<String> substr(String key, int start, int end);\n\n  Response<Long> strlen(String key);\n\n  Response<LCSMatchResult> lcs(String keyA, String keyB, LCSParams params);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/VectorSetBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\nimport redis.clients.jedis.resps.VSimScoreAttribs;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Interface for Redis Vector Set binary commands. Vector sets are a new data type introduced in\n * Redis 8.0 for vector similarity operations.\n */\npublic interface VectorSetBinaryCommands {\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vadd(byte[] key, float[] vector, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vadd(byte[] key, float[] vector, byte[] element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with dimension reduction and additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vadd(byte[] key, float[] vector, byte[] element, int reduceDim, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with dimension reduction and\n   * additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, int reduceDim, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @return list of similar elements\n   */\n  @Experimental\n  List<byte[]> vsim(byte[] key, float[] vector);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return list of similar elements\n   */\n  @Experimental\n  List<byte[]> vsim(byte[] key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return map of element names to their similarity scores\n   */\n  @Experimental\n  Map<byte[], Double> vsimWithScores(byte[] key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with their similarity scores and attributes.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be\n   *          automatically added)\n   * @return map of element names to their similarity scores and attributes\n   */\n  @Experimental\n  Map<byte[], VSimScoreAttribs> vsimWithScoresAndAttribs(byte[] key, float[] vector,\n      VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @return list of similar elements\n   */\n  @Experimental\n  List<byte[]> vsimByElement(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return list of similar elements\n   */\n  @Experimental\n  List<byte[]> vsimByElement(byte[] key, byte[] element, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return map of element names to their similarity scores\n   */\n  @Experimental\n  Map<byte[], Double> vsimByElementWithScores(byte[] key, byte[] element, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with their similarity scores and attributes.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be\n   *          automatically added)\n   * @return map of element names to their similarity scores and attributes\n   */\n  @Experimental\n  Map<byte[], VSimScoreAttribs> vsimByElementWithScoresAndAttribs(byte[] key, byte[] element,\n      VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vdim/\">VDIM Command</a></b> Return the number\n   * of dimensions of the vectors in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return the number of vector set elements\n   */\n  @Experimental\n  long vdim(byte[] key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vcard/\">VCARD Command</a></b> Return the\n   * number of elements in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return the number of elements in the vector set\n   */\n  @Experimental\n  long vcard(byte[] key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the\n   * approximate vector associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return list of real numbers representing the vector\n   */\n  @Experimental\n  List<Double> vemb(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the raw\n   * vector data associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return RawVector containing raw vector data, quantization type, and metadata\n   */\n  RawVector vembRaw(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrem/\">VREM Command</a></b> Remove an element\n   * from a vector set.\n   * <p>\n   * Time complexity: O(log(N)) for each element removed, where N is the number of elements in the\n   * vector set\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element to remove from the vector set\n   * @return true if the element was removed, false if either element or key do not exist\n   */\n  @Experimental\n  boolean vrem(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return list of neighbor element names\n   */\n  @Experimental\n  List<List<byte[]>> vlinks(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set with similarity scores.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return List of map of neighbor element names to similarity scores per layer\n   */\n  @Experimental\n  List<Map<byte[], Double>> vlinksWithScores(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return a random element from a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return a random element name, or null if the key does not exist\n   */\n  @Experimental\n  byte[] vrandmember(byte[] key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return random elements from a vector set.\n   * <p>\n   * Time complexity: O(N) where N is the absolute value of the count argument\n   * @param key the name of the key that holds the vector set\n   * @param count the number of elements to return. Positive values return distinct elements;\n   *          negative values allow duplicates\n   * @return list of random element names\n   */\n  @Experimental\n  List<byte[]> vrandmember(byte[] key, int count);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vgetattr/\">VGETATTR Command</a></b> Get the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to retrieve\n   * @return the attributes of the element as a JSON string, or null if the element doesn't exist or\n   *         has no attributes\n   */\n  @Experimental\n  byte[] vgetattr(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsetattr/\">VSETATTR Command</a></b> Set the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to set\n   * @param attributes the attributes to set as a JSON string\n   * @return true if the attributes were set successfully\n   */\n  @Experimental\n  boolean vsetattr(byte[] key, byte[] element, byte[] attributes);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/VectorSetCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\nimport redis.clients.jedis.resps.VSimScoreAttribs;\nimport redis.clients.jedis.resps.VectorInfo;\n\n/**\n * Interface for Redis Vector Set commands. Vector sets are a new data type introduced in Redis 8.0\n * for vector similarity operations.\n */\npublic interface VectorSetCommands {\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vadd(String key, float[] vector, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vadd(String key, float[] vector, String element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vaddFP32(String key, byte[] vectorBlob, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vaddFP32(String key, byte[] vectorBlob, String element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with dimension reduction and additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vadd(String key, float[] vector, String element, int reduceDim, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with dimension reduction and\n   * additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  boolean vaddFP32(String key, byte[] vectorBlob, String element, int reduceDim, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @return list of similar elements\n   */\n  @Experimental\n  List<String> vsim(String key, float[] vector);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return list of similar elements\n   */\n  @Experimental\n  List<String> vsim(String key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return map of element names to their similarity scores\n   */\n  @Experimental\n  Map<String, Double> vsimWithScores(String key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with their similarity scores and attributes.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be\n   *          automatically added)\n   * @return map of element names to their similarity scores and attributes\n   */\n  @Experimental\n  Map<String, VSimScoreAttribs> vsimWithScoresAndAttribs(String key, float[] vector,\n      VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @return list of similar elements\n   */\n  @Experimental\n  List<String> vsimByElement(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return list of similar elements\n   */\n  @Experimental\n  List<String> vsimByElement(String key, String element, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return map of element names to their similarity scores\n   */\n  @Experimental\n  Map<String, Double> vsimByElementWithScores(String key, String element, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with their similarity scores and attributes.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be\n   *          automatically added)\n   * @return map of element names to their similarity scores and attributes\n   */\n  @Experimental\n  Map<String, VSimScoreAttribs> vsimByElementWithScoresAndAttribs(String key, String element,\n      VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vdim/\">VDIM Command</a></b> Return the number\n   * of dimensions of the vectors in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return the number of vector set elements\n   */\n  @Experimental\n  long vdim(String key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vcard/\">VCARD Command</a></b> Return the\n   * number of elements in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return the number of elements in the vector set\n   */\n  @Experimental\n  long vcard(String key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the\n   * approximate vector associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return list of real numbers representing the vector\n   */\n  @Experimental\n  List<Double> vemb(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the raw\n   * vector data associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return RawVector containing raw vector data, quantization type, and metadata\n   */\n  @Experimental\n  RawVector vembRaw(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrem/\">VREM Command</a></b> Remove an element\n   * from a vector set.\n   * <p>\n   * Time complexity: O(log(N)) for each element removed, where N is the number of elements in the\n   * vector set\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element to remove from the vector set\n   * @return true if the element was removed, false if either element or key do not exist\n   */\n  @Experimental\n  boolean vrem(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return list of neighbor element names\n   */\n  @Experimental\n  List<List<String>> vlinks(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set with similarity scores.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return List of map of neighbor element names to similarity scores per layer\n   */\n  @Experimental\n  List<Map<String, Double>> vlinksWithScores(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return a random element from a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return a random element name, or null if the key does not exist\n   */\n  @Experimental\n  String vrandmember(String key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return random elements from a vector set.\n   * <p>\n   * Time complexity: O(N) where N is the absolute value of the count argument\n   * @param key the name of the key that holds the vector set\n   * @param count the number of elements to return. Positive values return distinct elements;\n   *          negative values allow duplicates\n   * @return list of random element names\n   */\n  @Experimental\n  List<String> vrandmember(String key, int count);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vgetattr/\">VGETATTR Command</a></b> Get the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to retrieve\n   * @return the attributes of the element as a JSON string, or null if the element doesn't exist or\n   *         has no attributes\n   */\n  @Experimental\n  String vgetattr(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsetattr/\">VSETATTR Command</a></b> Set the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to set\n   * @param attributes the attributes to set as a JSON string\n   * @return true if the attributes were set successfully\n   */\n  @Experimental\n  boolean vsetattr(String key, String element, String attributes);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vinfo/\">VINFO Command</a></b> Get information\n   * about a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return information about the vector set\n   */\n  @Experimental\n  VectorInfo vinfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/VectorSetPipelineBinaryCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\n\n/**\n * Interface for Redis Vector Set binary pipeline commands. Vector sets are a new data type\n * introduced in Redis 8.0 for vector similarity operations.\n */\npublic interface VectorSetPipelineBinaryCommands {\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vadd(byte[] key, float[] vector, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vadd(byte[] key, float[] vector, byte[] element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with dimension reduction and additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vadd(byte[] key, float[] vector, byte[] element, int reduceDim,\n      VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with dimension reduction and\n   * additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vaddFP32(byte[] key, byte[] vectorBlob, byte[] element, int reduceDim,\n      VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<byte[]>> vsim(byte[] key, float[] vector);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<byte[]>> vsim(byte[] key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return Response wrapping map of element names to their similarity scores\n   */\n  @Experimental\n  Response<Map<byte[], Double>> vsimWithScores(byte[] key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<byte[]>> vsimByElement(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<byte[]>> vsimByElement(byte[] key, byte[] element, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return Response wrapping map of element names to their similarity scores\n   */\n  @Experimental\n  Response<Map<byte[], Double>> vsimByElementWithScores(byte[] key, byte[] element,\n      VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vdim/\">VDIM Command</a></b> Return the number\n   * of dimensions of the vectors in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping the number of vector set elements\n   */\n  @Experimental\n  Response<Long> vdim(byte[] key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vcard/\">VCARD Command</a></b> Return the\n   * number of elements in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping the number of elements in the vector set\n   */\n  @Experimental\n  Response<Long> vcard(byte[] key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the\n   * approximate vector associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return Response wrapping list of real numbers representing the vector\n   */\n  @Experimental\n  Response<List<Double>> vemb(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the raw\n   * vector data associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return Response wrapping RawVector containing raw vector data, quantization type, and metadata\n   */\n  @Experimental\n  Response<RawVector> vembRaw(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrem/\">VREM Command</a></b> Remove an element\n   * from a vector set.\n   * <p>\n   * Time complexity: O(log(N)) for each element removed, where N is the number of elements in the\n   * vector set\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element to remove from the vector set\n   * @return Response wrapping true if the element was removed, false if either element or key do\n   *         not exist\n   */\n  @Experimental\n  Response<Boolean> vrem(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return Response wrapping list of neighbor element names\n   */\n  @Experimental\n  Response<List<List<byte[]>>> vlinks(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set with similarity scores.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return Response wrapping map of neighbor element names to similarity scores\n   */\n  @Experimental\n  Response<List<Map<byte[], Double>>> vlinksWithScores(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return a random element from a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping a random element name, or null if the key does not exist\n   */\n  @Experimental\n  Response<byte[]> vrandmember(byte[] key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return random elements from a vector set.\n   * <p>\n   * Time complexity: O(N) where N is the absolute value of the count argument\n   * @param key the name of the key that holds the vector set\n   * @param count the number of elements to return. Positive values return distinct elements;\n   *          negative values allow duplicates\n   * @return Response wrapping list of random element names\n   */\n  @Experimental\n  Response<List<byte[]>> vrandmember(byte[] key, int count);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vgetattr/\">VGETATTR Command</a></b> Get the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to retrieve\n   * @return Response wrapping the attributes of the element as a JSON string, or null if the\n   *         element doesn't exist or has no attributes\n   */\n  @Experimental\n  Response<byte[]> vgetattr(byte[] key, byte[] element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsetattr/\">VSETATTR Command</a></b> Set the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to set\n   * @param attributes the attributes to set as a JSON string\n   * @return Response wrapping true if the attributes were set successfully\n   */\n  @Experimental\n  Response<Boolean> vsetattr(byte[] key, byte[] element, byte[] attributes);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/VectorSetPipelineCommands.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\nimport redis.clients.jedis.resps.VectorInfo;\n\n/**\n * Interface for Redis Vector Set pipeline commands. Vector sets are a new data type introduced in\n * Redis 8.0 for vector similarity operations.\n */\npublic interface VectorSetPipelineCommands {\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vadd(String key, float[] vector, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vadd(String key, float[] vector, String element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vaddFP32(String key, byte[] vectorBlob, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vaddFP32(String key, byte[] vectorBlob, String element, VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key with dimension reduction and additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vector the vector as floating point numbers\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vadd(String key, float[] vector, String element, int reduceDim,\n      VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vadd/\">VADD Command</a></b> Add a new element\n   * into the vector set specified by key using FP32 binary format with dimension reduction and\n   * additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) for each element added, where N is the number of elements in the\n   * vector set.\n   * @param key the name of the key that will hold the vector set data\n   * @param vectorBlob the vector as FP32 binary blob\n   * @param element the name of the element that is being added to the vector set\n   * @param reduceDim the target dimension after reduction using random projection\n   * @param params additional parameters for the VADD command\n   * @return Response wrapping 1 if key was added; 0 if key was not added\n   */\n  @Experimental\n  Response<Boolean> vaddFP32(String key, byte[] vectorBlob, String element, int reduceDim,\n      VAddParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<String>> vsim(String key, float[] vector);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<String>> vsim(String key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given vector with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param vector the vector to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return Response wrapping map of element names to their similarity scores\n   */\n  @Experimental\n  Response<Map<String, Double>> vsimWithScores(String key, float[] vector, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<String>> vsimByElement(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with additional parameters.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command\n   * @return Response wrapping list of similar elements\n   */\n  @Experimental\n  Response<List<String>> vsimByElement(String key, String element, VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsim/\">VSIM Command</a></b> Return elements\n   * similar to a given element in the vector set with their similarity scores.\n   * <p>\n   * Time complexity: O(log(N)) where N is the number of elements in the vector set.\n   * @param key the name of the key that holds the vector set data\n   * @param element the name of the element to use as similarity reference\n   * @param params additional parameters for the VSIM command (WITHSCORES will be automatically\n   *          added)\n   * @return Response wrapping map of element names to their similarity scores\n   */\n  @Experimental\n  Response<Map<String, Double>> vsimByElementWithScores(String key, String element,\n      VSimParams params);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vdim/\">VDIM Command</a></b> Return the number\n   * of dimensions of the vectors in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping the number of vector set elements\n   */\n  @Experimental\n  Response<Long> vdim(String key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vcard/\">VCARD Command</a></b> Return the\n   * number of elements in the specified vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping the number of elements in the vector set\n   */\n  @Experimental\n  Response<Long> vcard(String key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the\n   * approximate vector associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return Response wrapping list of real numbers representing the vector\n   */\n  @Experimental\n  Response<List<Double>> vemb(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vemb/\">VEMB Command</a></b> Return the raw\n   * vector data associated with a given element in the vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose vector you want to retrieve\n   * @return Response wrapping RawVector containing raw vector data, quantization type, and metadata\n   */\n  @Experimental\n  Response<RawVector> vembRaw(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrem/\">VREM Command</a></b> Remove an element\n   * from a vector set.\n   * <p>\n   * Time complexity: O(log(N)) for each element removed, where N is the number of elements in the\n   * vector set\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element to remove from the vector set\n   * @return Response wrapping true if the element was removed, false if either element or key do\n   *         not exist\n   */\n  @Experimental\n  Response<Boolean> vrem(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return Response wrapping list of neighbor element names\n   */\n  @Experimental\n  Response<List<List<String>>> vlinks(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vlinks/\">VLINKS Command</a></b> Return the\n   * neighbors of a specified element in a vector set with similarity scores.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose HNSW neighbors you want to inspect\n   * @return Response wrapping map of neighbor element names to similarity scores\n   */\n  @Experimental\n  Response<List<Map<String, Double>>> vlinksWithScores(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return a random element from a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping a random element name, or null if the key does not exist\n   */\n  @Experimental\n  Response<String> vrandmember(String key);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vrandmember/\">VRANDMEMBER Command</a></b>\n   * Return random elements from a vector set.\n   * <p>\n   * Time complexity: O(N) where N is the absolute value of the count argument\n   * @param key the name of the key that holds the vector set\n   * @param count the number of elements to return. Positive values return distinct elements;\n   *          negative values allow duplicates\n   * @return Response wrapping list of random element names\n   */\n  @Experimental\n  Response<List<String>> vrandmember(String key, int count);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vgetattr/\">VGETATTR Command</a></b> Get the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to retrieve\n   * @return Response wrapping the attributes of the element as a JSON string, or null if the\n   *         element doesn't exist or has no attributes\n   */\n  @Experimental\n  Response<String> vgetattr(String key, String element);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vsetattr/\">VSETATTR Command</a></b> Set the\n   * attributes of an element in a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @param element the name of the element whose attributes to set\n   * @param attributes the attributes to set as a JSON string\n   * @return Response wrapping true if the attributes were set successfully\n   */\n  @Experimental\n  Response<Boolean> vsetattr(String key, String element, String attributes);\n\n  /**\n   * <b><a href=\"https://redis.io/docs/latest/commands/vinfo/\">VINFO Command</a></b> Get information\n   * about a vector set.\n   * <p>\n   * Time complexity: O(1)\n   * @param key the name of the key that holds the vector set\n   * @return Response wrapping information about the vector set\n   */\n  @Experimental\n  Response<VectorInfo> vinfo(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/commands/package-info.java",
    "content": "/**\n * This package contains the interfaces that contain methods representing Redis core commands.\n */\npackage redis.clients.jedis.commands;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/AbstractCache.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * The class to manage the client-side caching. User can provide an of implementation of this class\n * to the client object.\n */\n@Experimental\npublic abstract class AbstractCache implements Cache {\n\n  private Cacheable cacheable;\n  private final Map<ByteBuffer, Set<CacheKey<?>>> redisKeysToCacheKeys = new ConcurrentHashMap<>();\n  private final int maximumSize;\n  private ReentrantLock lock = new ReentrantLock();\n  private volatile CacheStats stats = new CacheStats();\n\n  protected AbstractCache(int maximumSize) {\n    this(maximumSize, DefaultCacheable.INSTANCE);\n  }\n\n  protected AbstractCache(int maximumSize, Cacheable cacheable) {\n    this.maximumSize = maximumSize;\n    this.cacheable = cacheable;\n  }\n\n  // Cache interface methods\n\n  @Override\n  public int getMaxSize() {\n    return maximumSize;\n  }\n\n  @Override\n  public abstract int getSize();\n\n  @Override\n  public abstract Collection<CacheEntry> getCacheEntries();\n\n  @Override\n  public CacheEntry get(CacheKey cacheKey) {\n    CacheEntry entry = getFromStore(cacheKey);\n    if (entry != null) {\n      getEvictionPolicy().touch(cacheKey);\n    }\n    return entry;\n  }\n\n  @Override\n  public CacheEntry set(CacheKey cacheKey, CacheEntry entry) {\n    lock.lock();\n    try {\n      entry = putIntoStore(cacheKey, entry);\n      EvictionPolicy policy = getEvictionPolicy();\n      policy.touch(cacheKey);\n      CacheKey evictedKey = policy.evictNext();\n      if (evictedKey != null) {\n        delete(evictedKey);\n        stats.evict();\n      }\n      for (Object redisKey : cacheKey.getRedisKeys()) {\n        ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey);\n        if (redisKeysToCacheKeys.containsKey(mapKey)) {\n          redisKeysToCacheKeys.get(mapKey).add(cacheKey);\n        } else {\n          Set<CacheKey<?>> set = ConcurrentHashMap.newKeySet();\n          set.add(cacheKey);\n          redisKeysToCacheKeys.put(mapKey, set);\n        }\n      }\n      stats.load();\n      return entry;\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public boolean delete(CacheKey cacheKey) {\n    lock.lock();\n    try {\n      boolean removed = removeFromStore(cacheKey);\n      getEvictionPolicy().reset(cacheKey);\n\n      // removing it from redisKeysToCacheKeys as well\n      // TODO: considering not doing it, what is the impact of not doing it ??\n      for (Object redisKey : cacheKey.getRedisKeys()) {\n        ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey);\n        Set<CacheKey<?>> cacheKeysRelatedtoRedisKey = redisKeysToCacheKeys.get(mapKey);\n        if (cacheKeysRelatedtoRedisKey != null) {\n          cacheKeysRelatedtoRedisKey.remove(cacheKey);\n        }\n      }\n      return removed;\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public List<Boolean> delete(List<CacheKey> cacheKeys) {\n    lock.lock();\n    try {\n      return cacheKeys.stream().map(this::delete).collect(Collectors.toList());\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public List<CacheKey> deleteByRedisKey(Object key) {\n    lock.lock();\n    try {\n      final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key);\n\n      Set<CacheKey<?>> commands = redisKeysToCacheKeys.get(mapKey);\n      List<CacheKey> cacheKeys = new ArrayList<>();\n      if (commands != null) {\n        cacheKeys.addAll(commands.stream().filter(this::removeFromStore).collect(Collectors.toList()));\n        stats.invalidationByServer(cacheKeys.size());\n        redisKeysToCacheKeys.remove(mapKey);\n      }\n      stats.invalidationMessages();\n      return cacheKeys;\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public List<CacheKey> deleteByRedisKeys(List keys) {\n    if (keys == null) {\n      flush();\n      return null;\n    }\n    lock.lock();\n    try {\n      return ((List<Object>) keys).stream()\n          .map(this::deleteByRedisKey).flatMap(List::stream).collect(Collectors.toList());\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public int flush() {\n    lock.lock();\n    try {\n      int result = this.getSize();\n      clearStore();\n      redisKeysToCacheKeys.clear();\n      getEvictionPolicy().resetAll();\n      getStats().flush();\n      return result;\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public boolean isCacheable(CacheKey cacheKey) {\n    return cacheable.isCacheable(cacheKey.getRedisCommand(), cacheKey.getRedisKeys());\n  }\n\n  @Override\n  public boolean hasCacheKey(CacheKey cacheKey) {\n    return containsKeyInStore(cacheKey);\n  }\n\n  @Override\n  public abstract EvictionPolicy getEvictionPolicy();\n\n  @Override\n  public CacheStats getStats() {\n    return stats;\n  }\n\n  @Override\n  public CacheStats getAndResetStats() {\n    CacheStats result = stats;\n    stats = new CacheStats();\n    return result;\n  }\n\n  @Override\n  public boolean compatibilityMode() {\n    return false;\n  }\n  // End of Cache interface methods\n\n  // abstract methods to be implemented by the concrete classes\n  protected abstract CacheEntry getFromStore(CacheKey cacheKey);\n\n  protected abstract CacheEntry putIntoStore(CacheKey cacheKey, CacheEntry entry);\n\n  protected abstract boolean removeFromStore(CacheKey cacheKey);\n\n  // protected abstract Collection<CacheKey> remove(Set<CacheKey<?>> commands);\n\n  protected abstract void clearStore();\n\n  protected abstract boolean containsKeyInStore(CacheKey cacheKey);\n\n  // End of abstract methods to be implemented by the concrete classes\n\n  /**\n   * Normalizes Redis keys to ByteBuffer for use as map keys in {@link #redisKeysToCacheKeys}.\n   * <p>\n   * This method provides type safety by accepting only {@link String} and {@code byte[]} types,\n   * which are the only types stored by {@link redis.clients.jedis.CommandArguments#getKeys()}.\n   * <p>\n   * <b>Normalization strategy:</b>\n   * <ul>\n   *   <li>{@link String} keys are converted to {@code byte[]} using UTF-8 encoding via {@link SafeEncoder#encode(String)}</li>\n   *   <li>{@code byte[]} keys are used directly</li>\n   *   <li>Both are wrapped in {@link ByteBuffer} for content-based equality (similar to {@link redis.clients.jedis.util.JedisByteMap})</li>\n   * </ul>\n   * <p>\n   * <b>Why ByteBuffer:</b> {@link ByteBuffer} provides content-based {@code equals()} and {@code hashCode()}\n   * for byte arrays, which is required for proper map key behavior. Plain {@code byte[]} uses identity-based\n   * equality, which would break key lookups.\n   * <p>\n   * This normalization ensures that:\n   * <ul>\n   *   <li>String key {@code \"user:1\"} and byte key {@code byte[]{0x75, 0x73, 0x65, 0x72, 0x3a, 0x31}} are treated as equal</li>\n   *   <li>Cache invalidation works correctly regardless of whether keys were added as String or byte[]</li>\n   *   <li>Type mismatches are caught early with clear error messages</li>\n   * </ul>\n   *\n   * @param key the Redis key (must be {@link String} or {@code byte[]})\n   * @return ByteBuffer wrapping the normalized byte representation\n   * @throws IllegalArgumentException if key is not {@link String} or {@code byte[]}\n   */\n  private ByteBuffer makeKeyForRedisKeysToCacheKeys(Object key) {\n    if (key instanceof byte[]) {\n      return makeKeyForRedisKeysToCacheKeys((byte[]) key);\n    } else if (key instanceof String) {\n      return makeKeyForRedisKeysToCacheKeys(SafeEncoder.encode((String) key));\n    } else {\n      throw new IllegalArgumentException(key.getClass().getSimpleName() + \" is not supported.\"\n          + \" Value: \\\"\" + String.valueOf(key) + \"\\\".\");\n    }\n  }\n\n  /**\n   * Wraps a byte array in a ByteBuffer for use as a map key.\n   * <p>\n   * ByteBuffer provides content-based equality, which is required for proper map key behavior\n   * with byte arrays.\n   *\n   * @param b the byte array to wrap\n   * @return ByteBuffer wrapping the byte array\n   */\n  private static ByteBuffer makeKeyForRedisKeysToCacheKeys(byte[] b) {\n    return ByteBuffer.wrap(b);\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/Cache.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * The cache that is used by a connection\n */\npublic interface Cache {\n\n    /**\n     * @return The size of the cache\n     */\n    int getMaxSize();\n\n    /**\n     * @return The current size of the cache\n     */\n    int getSize();\n\n    /**\n     * @return All the entries within the cache\n     */\n    Collection<CacheEntry> getCacheEntries();\n\n    /**\n     * Fetches a value from the cache\n     *\n     * @param cacheKey The key within the cache\n     * @return The entry within the cache\n     */\n    CacheEntry get(CacheKey cacheKey);\n\n    /**\n     * Puts a value into the cache\n     *\n     * @param cacheKey The key by which the value can be accessed within the cache\n     * @param value The value to be put into the cache\n     * @return The cache entry\n     */\n    CacheEntry set(CacheKey cacheKey, CacheEntry value);\n\n    /**\n     * Delete an entry by cache key\n     * @param cacheKey The cache key of the entry in the cache\n     * @return True if the entry could be deleted, false if the entry wasn't found.\n     */\n    boolean delete(CacheKey cacheKey);\n\n    /**\n     * Delete entries by cache key from the cache\n     *\n     * @param cacheKeys The cache keys of the entries that should be deleted\n     * @return True for every entry that could be deleted. False if the entry was not there.\n     */\n    List<Boolean> delete(List<CacheKey> cacheKeys);\n\n    /**\n     * Delete an entry by the Redis key from the cache\n     *\n     * @param key The Redis key as binary\n     * @return True if the entry could be deleted. False if the entry was not there.\n     */\n    List<CacheKey> deleteByRedisKey(Object key);\n\n    /**\n     * Delete entries by the Redis key from the cache\n     *\n     * @param keys The Redis keys as binaries\n     * @return True for every entry that could be deleted. False if the entry was not there.\n     */\n    List<CacheKey> deleteByRedisKeys(List keys);\n\n    /**\n     * Flushes the entire cache\n     *\n     * @return Return the number of entries that were flushed\n     */\n    int flush();\n\n    /**\n     * @param cacheKey The key of the cache entry\n     * @return True if the entry is cachable, false otherwise\n     */\n    boolean isCacheable(CacheKey cacheKey);\n\n    /**\n     *\n     * @param cacheKey The key of the cache entry\n     * @return True if the cache already contains the key\n     */\n    boolean hasCacheKey(CacheKey cacheKey);\n\n    /**\n     * @return The eviction policy that is used by the cache\n     */\n    EvictionPolicy getEvictionPolicy();\n\n    /**\n     * @return The statistics of the cache\n     */\n    CacheStats getStats();\n\n    /**\n     * @return The statistics of the cache\n     */\n    CacheStats getAndResetStats();\n\n    /**\n     * @return The compatibility of cache against different Redis versions\n     */\n    boolean compatibilityMode();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/CacheConfig.java",
    "content": "package redis.clients.jedis.csc;\n\npublic class CacheConfig {\n\n    private int maxSize;\n    private Cacheable cacheable;\n    private EvictionPolicy evictionPolicy;\n    private Class cacheClass;\n\n    public int getMaxSize() {\n        return maxSize;\n    }\n\n    public Cacheable getCacheable() {\n        return cacheable;\n    }\n\n    public EvictionPolicy getEvictionPolicy() {\n        return evictionPolicy;\n    }\n\n    public Class getCacheClass() {\n        return cacheClass;\n    }\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static class Builder {\n        private final int DEFAULT_MAX_SIZE = 10000;\n        private int maxSize = DEFAULT_MAX_SIZE;\n        private Cacheable cacheable = DefaultCacheable.INSTANCE;\n        private EvictionPolicy evictionPolicy;\n        private Class cacheClass;\n\n        public Builder maxSize(int maxSize) {\n            this.maxSize = maxSize;\n            return this;\n        }\n\n        public Builder evictionPolicy(EvictionPolicy policy) {\n            this.evictionPolicy = policy;\n            return this;\n        }\n\n        public Builder cacheable(Cacheable cacheable) {\n            this.cacheable = cacheable;\n            return this;\n        }\n\n        public Builder cacheClass(Class cacheClass) {\n            this.cacheClass = cacheClass;\n            return this;\n        }\n\n        public CacheConfig build() {\n            CacheConfig cacheConfig = new CacheConfig();\n            cacheConfig.maxSize = this.maxSize;\n            cacheConfig.cacheable = this.cacheable;\n            cacheConfig.evictionPolicy = this.evictionPolicy;\n            cacheConfig.cacheClass = this.cacheClass;\n            return cacheConfig;\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/CacheConnection.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.Objects;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.JedisSocketFactory;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.annots.VisibleForTesting;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.RedisInputStream;\n\npublic class CacheConnection extends Connection {\n\n  public static class Builder extends Connection.Builder {\n    private Cache cache;\n\n    private Builder(Cache cache) {\n      if (cache == null) {\n        throw new IllegalArgumentException(\"Cache cannot be null!\");\n      }\n      this.cache = cache;\n    }\n\n    public Cache getCache() {\n      return cache;\n    }\n\n    @Override\n    public Builder socketFactory(JedisSocketFactory socketFactory) {\n      super.socketFactory(socketFactory);\n      return this;\n    }\n\n    @Override\n    public Builder clientConfig(JedisClientConfig clientConfig) {\n      super.clientConfig(clientConfig);\n      return this;\n    }\n\n    @Override\n    public Connection build() {\n      CacheConnection conn = new CacheConnection(this);\n      conn.initializeFromClientConfig();\n      return conn;\n    }\n  }\n\n  public static Builder builder(Cache cache) {\n    return new Builder(cache);\n  }\n\n  @VisibleForTesting\n  public static Builder builder() {\n    throw new UnsupportedOperationException(\"Cache is required to build CacheConnection.\");\n  }\n\n  private final Cache cache;\n  private ReentrantLock lock;\n  private static final String REDIS = \"redis\";\n  private static final String MIN_REDIS_VERSION = \"7.4\";\n\n  public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache cache) {\n    super(socketFactory, clientConfig);\n\n    this.cache = Objects.requireNonNull(cache);\n    initializeClientSideCache();\n  }\n\n  private CacheConnection(Builder builder) {\n    super(builder);\n    this.cache = builder.getCache();\n  }\n\n  @Override\n  protected void initializeFromClientConfig(JedisClientConfig config) {\n    lock = new ReentrantLock();\n    super.initializeFromClientConfig(config);\n    // this is required for the case ctor(builder).\n    // will also be called for the case ctor(socketFactory, clientConfig, cache) but will return\n    if (cache == null) return;\n    initializeClientSideCache();\n  }\n\n  @Override\n  protected Object protocolRead(RedisInputStream inputStream) {\n    lock.lock();\n    try {\n      return Protocol.read(inputStream, cache);\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  protected void protocolReadPushes(RedisInputStream inputStream) {\n    if (lock.tryLock()) {\n      try {\n        Protocol.readPushes(inputStream, cache, true);\n      } finally {\n        lock.unlock();\n      }\n    }\n  }\n\n  @Override\n  public void disconnect() {\n    super.disconnect();\n    cache.flush();\n  }\n\n  @Override\n  public <T> T executeCommand(final CommandObject<T> commandObject) {\n    final CacheKey cacheKey = new CacheKey(commandObject);\n    if (!cache.isCacheable(cacheKey)) {\n      cache.getStats().nonCacheable();\n      return super.executeCommand(commandObject);\n    }\n\n    CacheEntry<T> cacheEntry = cache.get(cacheKey);\n    if (cacheEntry != null) { // (probable) CACHE HIT !!\n      cacheEntry = validateEntry(cacheEntry);\n      if (cacheEntry != null) {\n        // CACHE HIT confirmed !!!\n        cache.getStats().hit();\n        return cacheEntry.getValue();\n      }\n    }\n\n    // CACHE MISS !!\n    cache.getStats().miss();\n    T value = super.executeCommand(commandObject);\n    cacheEntry = new CacheEntry<>(cacheKey, value, this);\n    cache.set(cacheKey, cacheEntry);\n    // this line actually provides a deep copy of cached object instance\n    value = cacheEntry.getValue();\n    return value;\n  }\n\n  public Cache getCache() {\n    return cache;\n  }\n\n  private void initializeClientSideCache() {\n    if (protocol != RedisProtocol.RESP3) {\n      throw new JedisException(\"Client side caching is only supported with RESP3.\");\n    }\n    Objects.requireNonNull(cache);\n    if (!cache.compatibilityMode()) {\n      RedisVersion current = new RedisVersion(version);\n      RedisVersion required = new RedisVersion(MIN_REDIS_VERSION);\n      if (!REDIS.equals(server) || current.compareTo(required) < 0) {\n        throw new JedisException(\n          String.format(\"Client side caching is only supported with 'Redis %s' or later.\", MIN_REDIS_VERSION));\n      }\n    }\n\n    sendCommand(Protocol.Command.CLIENT, \"TRACKING\", \"ON\");\n    String reply = getStatusCodeReply();\n    if (!\"OK\".equals(reply)) {\n      throw new JedisException(\"Could not enable client tracking. Reply: \" + reply);\n    }\n  }\n\n  private CacheEntry validateEntry(CacheEntry cacheEntry) {\n    CacheConnection cacheOwner = cacheEntry.getConnection();\n    if (cacheOwner == null || cacheOwner.isBroken() || !cacheOwner.isConnected()) {\n      cache.delete(cacheEntry.getCacheKey());\n      return null;\n    } else {\n      try {\n        cacheOwner.readPushesWithCheckingBroken();\n      } catch (JedisException e) {\n        cache.delete(cacheEntry.getCacheKey());\n        return null;\n      }\n\n      return cache.get(cacheEntry.getCacheKey());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/CacheEntry.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.lang.ref.WeakReference;\n\nimport redis.clients.jedis.exceptions.JedisCacheException;\n\npublic class CacheEntry<T> {\n\n  private final CacheKey<T> cacheKey;\n  private final WeakReference<CacheConnection> connection;\n  private final byte[] bytes;\n\n  public CacheEntry(CacheKey<T> cacheKey, T value, CacheConnection connection) {\n    this.cacheKey = cacheKey;\n    this.connection = new WeakReference<>(connection);\n    this.bytes = toBytes(value);\n  }\n\n  public CacheKey<T> getCacheKey() {\n    return cacheKey;\n  }\n\n  public T getValue() {\n    return toObject(bytes);\n  }\n\n  public CacheConnection getConnection() {\n    return connection.get();\n  }\n\n  private static byte[] toBytes(Object object) {\n    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        ObjectOutputStream oos = new ObjectOutputStream(baos)) {\n      oos.writeObject(object);\n      oos.flush();\n      oos.close();\n      return baos.toByteArray();\n    } catch (IOException e) {\n      throw new JedisCacheException(\"Failed to serialize object\", e);\n    }\n  }\n\n  private T toObject(byte[] data) {\n    try (ByteArrayInputStream bais = new ByteArrayInputStream(data);\n        ObjectInputStream ois = new ObjectInputStream(bais)) {\n      return (T) ois.readObject();\n    } catch (IOException | ClassNotFoundException e) {\n      throw new JedisCacheException(\"Failed to deserialize object\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/CacheFactory.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.Arrays;\n\nimport redis.clients.jedis.exceptions.JedisCacheException;\n\npublic final class CacheFactory {\n\n    public static Cache getCache(CacheConfig config) {\n        if (config.getCacheClass() == null) {\n            if (config.getCacheable() == null) {\n                throw new JedisCacheException(\"Cacheable is required to create the default cache!\");\n            }\n            return new DefaultCache(config.getMaxSize(), config.getCacheable(), getEvictionPolicy(config));\n        }\n        return instantiateCustomCache(config);\n    }\n\n    private static Cache instantiateCustomCache(CacheConfig config) {\n        try {\n            if (config.getCacheable() != null) {\n                Constructor ctorWithCacheable = findConstructorWithCacheable(config.getCacheClass());\n                if (ctorWithCacheable != null) {\n                    return (Cache) ctorWithCacheable.newInstance(config.getMaxSize(), getEvictionPolicy(config), config.getCacheable());\n                }\n            }\n            Constructor ctor = getConstructor(config.getCacheClass());\n            return (Cache) ctor.newInstance(config.getMaxSize(), getEvictionPolicy(config));\n        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\n                | SecurityException e) {\n            throw new JedisCacheException(\"Failed to insantiate custom cache type!\", e);\n        }\n    }\n\n    private static Constructor findConstructorWithCacheable(Class customCacheType) {\n        return Arrays.stream(customCacheType.getConstructors())\n                .filter(ctor -> Arrays.equals(ctor.getParameterTypes(), new Class[] { int.class, EvictionPolicy.class, Cacheable.class }))\n                .findFirst().orElse(null);\n    }\n\n    private static Constructor getConstructor(Class customCacheType) {\n        try {\n            return customCacheType.getConstructor(int.class, EvictionPolicy.class);\n        } catch (NoSuchMethodException e) {\n            String className = customCacheType.getName();\n            throw new JedisCacheException(String.format(\n                \"Failed to find compatible constructor for custom cache type!  Provide one of these;\"\n                        // give hints about the compatible constructors\n                        + \"\\n - %s(int maxSize, EvictionPolicy evictionPolicy)\\n - %s(int maxSize, EvictionPolicy evictionPolicy, Cacheable cacheable)\",\n                className, className), e);\n        }\n    }\n\n    private static EvictionPolicy getEvictionPolicy(CacheConfig config) {\n        if (config.getEvictionPolicy() == null) {\n            // It will be default to LRUEviction, until we have other eviction implementations\n            return new LRUEviction(config.getMaxSize());\n        }\n        return config.getEvictionPolicy();\n    }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/CacheKey.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.List;\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.commands.ProtocolCommand;\n\npublic class CacheKey<T> {\n\n  private final CommandObject<T> command;\n\n  public CacheKey(CommandObject<T> command) {\n    this.command = Objects.requireNonNull(command);\n  }\n\n  @Override\n  public int hashCode() {\n    return command.hashCode();\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) return true;\n    if (obj == null || getClass() != obj.getClass()) return false;\n    final CacheKey other = (CacheKey) obj;\n    return Objects.equals(this.command, other.command);\n  }\n\n  public List<Object> getRedisKeys() {\n    return command.getArguments().getKeys();\n  }\n\n  public ProtocolCommand getRedisCommand() {\n    return command.getArguments().getCommand();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/CacheStats.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class CacheStats {\n\n    private AtomicLong hits = new AtomicLong(0);\n    private AtomicLong misses = new AtomicLong(0);\n    private AtomicLong loads = new AtomicLong(0);\n    private AtomicLong evicts = new AtomicLong(0);\n    private AtomicLong nonCacheable = new AtomicLong(0);\n    private AtomicLong flush = new AtomicLong(0);\n    private AtomicLong invalidationsByServer = new AtomicLong(0);\n    private AtomicLong invalidationMessages = new AtomicLong(0);\n\n    protected void hit() {\n        hits.incrementAndGet();\n    }\n\n    protected void miss() {\n        misses.incrementAndGet();\n    }\n\n    protected void load() {\n        loads.incrementAndGet();\n    }\n\n    protected void evict() {\n        evicts.incrementAndGet();\n    }\n\n    protected void nonCacheable() {\n        nonCacheable.incrementAndGet();\n    }\n\n    protected void flush() {\n        flush.incrementAndGet();\n    }\n\n    protected void invalidationByServer(int size) {\n        invalidationsByServer.addAndGet(size);\n    }\n\n    protected void invalidationMessages() {\n        invalidationMessages.incrementAndGet();\n    }\n\n    public long getHitCount() {\n        return hits.get();\n    }\n\n    public long getMissCount() {\n        return misses.get();\n    }\n\n    public long getLoadCount() {\n        return loads.get();\n    }\n\n    public long getEvictCount() {\n        return evicts.get();\n    }\n\n    public long getNonCacheableCount() {\n        return nonCacheable.get();\n    }\n\n    public long getFlushCount() {\n        return flush.get();\n    }\n\n    public long getInvalidationCount() {\n        return invalidationsByServer.get();\n    }\n\n    public String toString() {\n        return \"CacheStats{\" +\n                \"hits=\" + hits +\n                \", misses=\" + misses +\n                \", loads=\" + loads +\n                \", evicts=\" + evicts +\n                \", nonCacheable=\" + nonCacheable +\n                \", flush=\" + flush +\n                \", invalidationsByServer=\" + invalidationsByServer +\n                \", invalidationMessages=\" + invalidationMessages +\n                '}';\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/Cacheable.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.List;\nimport redis.clients.jedis.commands.ProtocolCommand;\n\npublic interface Cacheable {\n\n  boolean isCacheable(ProtocolCommand command, List<Object> keys);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/DefaultCache.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class DefaultCache extends AbstractCache {\n\n    protected final Map<CacheKey, CacheEntry> cache;\n    private final EvictionPolicy evictionPolicy;\n\n    protected DefaultCache(int maximumSize) {\n        this(maximumSize, new HashMap<CacheKey, CacheEntry>());\n    }\n\n    protected DefaultCache(int maximumSize, Map<CacheKey, CacheEntry> map) {\n        this(maximumSize, map, DefaultCacheable.INSTANCE, new LRUEviction(maximumSize));\n    }\n\n    protected DefaultCache(int maximumSize, Cacheable cacheable) {\n        this(maximumSize, new HashMap<CacheKey, CacheEntry>(), cacheable, new LRUEviction(maximumSize));\n    }\n\n    protected DefaultCache(int maximumSize, Cacheable cacheable, EvictionPolicy evictionPolicy) {\n        this(maximumSize, new HashMap<CacheKey, CacheEntry>(), cacheable, evictionPolicy);\n    }\n\n    protected DefaultCache(int maximumSize, Map<CacheKey, CacheEntry> map, Cacheable cacheable, EvictionPolicy evictionPolicy) {\n        super(maximumSize, cacheable);\n        this.cache = map;\n        this.evictionPolicy = evictionPolicy;\n        this.evictionPolicy.setCache(this);\n    }\n\n    @Override\n    public int getSize() {\n        return cache.size();\n    }\n\n    @Override\n    public Collection<CacheEntry> getCacheEntries() {\n        return cache.values();\n    }\n\n    @Override\n    public EvictionPolicy getEvictionPolicy() {\n        return this.evictionPolicy;\n    }\n\n    @Override\n    public CacheEntry getFromStore(CacheKey key) {\n        return cache.get(key);\n    }\n\n    @Override\n    public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) {\n        return cache.put(key, entry);\n    }\n\n    @Override\n    public boolean removeFromStore(CacheKey key) {\n        return cache.remove(key) != null;\n    }\n\n    @Override\n    protected final void clearStore() {\n        cache.clear();\n    }\n\n    @Override\n    protected boolean containsKeyInStore(CacheKey cacheKey) {\n        return cache.containsKey(cacheKey);\n    }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/DefaultCacheable.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.json.JsonProtocol.JsonCommand;\nimport redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesCommand;\n\npublic class DefaultCacheable implements Cacheable {\n\n  public static final DefaultCacheable INSTANCE = new DefaultCacheable();\n\n  private static final Set<ProtocolCommand> DEFAULT_CACHEABLE_COMMANDS = new HashSet<ProtocolCommand>() {\n    {\n      add(Command.BITCOUNT);\n      add(Command.BITFIELD_RO);\n      add(Command.BITPOS);\n      add(Command.EXISTS);\n      add(Command.GEODIST);\n      add(Command.GEOHASH);\n      add(Command.GEOPOS);\n      add(Command.GEORADIUSBYMEMBER_RO);\n      add(Command.GEORADIUS_RO);\n      add(Command.GEOSEARCH);\n      add(Command.GET);\n      add(Command.GETBIT);\n      add(Command.GETRANGE);\n      add(Command.HEXISTS);\n      add(Command.HGET);\n      add(Command.HGETALL);\n      add(Command.HKEYS);\n      add(Command.HLEN);\n      add(Command.HMGET);\n      add(Command.HSTRLEN);\n      add(Command.HVALS);\n      add(JsonCommand.ARRINDEX);\n      add(JsonCommand.ARRLEN);\n      add(JsonCommand.GET);\n      add(JsonCommand.MGET);\n      add(JsonCommand.OBJKEYS);\n      add(JsonCommand.OBJLEN);\n      add(JsonCommand.STRLEN);\n      add(JsonCommand.TYPE);\n      add(Command.LCS);\n      add(Command.LINDEX);\n      add(Command.LLEN);\n      add(Command.LPOS);\n      add(Command.LRANGE);\n      add(Command.MGET);\n      add(Command.SCARD);\n      add(Command.SDIFF);\n      add(Command.SINTER);\n      add(Command.SISMEMBER);\n      add(Command.SMEMBERS);\n      add(Command.SMISMEMBER);\n      add(Command.STRLEN);\n      add(Command.SUBSTR);\n      add(Command.SUNION);\n      add(TimeSeriesCommand.GET);\n      add(TimeSeriesCommand.INFO);\n      add(TimeSeriesCommand.RANGE);\n      add(TimeSeriesCommand.REVRANGE);\n      add(Command.TYPE);\n      add(Command.XLEN);\n      add(Command.XPENDING);\n      add(Command.XRANGE);\n      add(Command.XREVRANGE);\n      add(Command.ZCARD);\n      add(Command.ZCOUNT);\n      add(Command.ZLEXCOUNT);\n      add(Command.ZMSCORE);\n      add(Command.ZRANGE);\n      add(Command.ZRANGEBYLEX);\n      add(Command.ZRANGEBYSCORE);\n      add(Command.ZRANK);\n      add(Command.ZREVRANGE);\n      add(Command.ZREVRANGEBYLEX);\n      add(Command.ZREVRANGEBYSCORE);\n      add(Command.ZREVRANK);\n      add(Command.ZSCORE);\n    }\n  };\n\n  public DefaultCacheable() {\n  }\n\n  public static boolean isDefaultCacheableCommand(ProtocolCommand command) {\n    return DEFAULT_CACHEABLE_COMMANDS.contains(command);\n  }\n\n  @Override\n  public boolean isCacheable(ProtocolCommand command, List<Object> keys) {\n    return isDefaultCacheableCommand(command);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/EvictionPolicy.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.List;\n\n/**\n * Describes the properties and functionality of an eviction policy\n * <p>\n * One policy instance belongs to exactly one cache instance\n */\npublic interface EvictionPolicy {\n\n    /**\n     * Types of eviction policies\n     *\n     * AGE - based on the time of access, e.g., LRU\n     * FREQ - based on the frequency of access, e.g., LFU\n     * HYBR - AGE + FREQ, e.g., CLOCK\n     * MISC - Anythin that isn't time based, frequency based or a combination of the two, e.g., FIFO\n     */\n    enum EvictionType {\n        AGE, FREQ, HYBR, MISC\n    }\n\n    /**\n     * @return The cache that is associated to this policy instance\n     */\n    Cache getCache();\n\n    /**\n     * Sets the cache that is associated to this policy instance\n     * @param cache The cache instance\n     */\n    void setCache(Cache cache);\n\n    /**\n     * @return The type of policy\n     */\n    EvictionType getType();\n\n    /**\n     * @return The name of the policy\n     */\n    String getName();\n\n    /**\n     * Evict the next element from the cache\n     * This one should provide O(1) complexity\n     * @return The key of the entry that was evicted\n     */\n    CacheKey evictNext();\n\n    /**\n     *\n     * @param n The number of entries to evict\n     * @return The list of keys of evicted entries\n     */\n    List<CacheKey> evictMany(int n);\n\n    /**\n     * Indicates that a cache key was touched\n     * This one should provide O(1) complexity\n     * @param cacheKey The key within the cache\n     */\n    void touch(CacheKey cacheKey);\n\n    /**\n     * Resets the state that the eviction policy maintains about the cache key\n     * @param cacheKey\n     */\n    boolean reset(CacheKey cacheKey);\n\n    /**\n     * Resets the entire state of the eviction data\n     * @return True if the reset could be performed successfully\n     */\n    int resetAll();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/LRUEviction.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentLinkedQueue;\n\n/**\n * Simple L(east) R(ecently) U(sed) eviction policy\n * ATTENTION: this class is not thread safe\n */\npublic class LRUEviction implements EvictionPolicy {\n\n    // For future reference, in case there is a need to make it thread safe,\n    // the LinkedHashMap can be wrapped in a Collections.synchronizedMap\n\n    /**\n     * The cache that is associated to that policy instance\n     */\n    protected Cache cache;\n    protected LinkedHashMap<CacheKey, Long> accessTimes;\n\n    protected ArrayDeque<CacheKey> pendingEvictions = new ArrayDeque<CacheKey>();\n\n    protected ConcurrentLinkedQueue msg = new ConcurrentLinkedQueue();\n\n    private int initialCapacity;\n\n    /**\n     *  Constructor that gets the cache passed\n     *\n     * @param initialCapacity\n     */\n    public LRUEviction(int initialCapacity) {\n        this.initialCapacity = initialCapacity;\n    }\n\n    @Override\n    public void setCache(Cache cache) {\n        this.cache = cache;\n        this.accessTimes = new LinkedHashMap<CacheKey, Long>(initialCapacity, 1f, true) {\n            @Override\n            protected boolean removeEldestEntry(Map.Entry<CacheKey, Long> eldest) {\n                boolean evictionRequired = cache.getSize() > cache.getMaxSize()\n                        || accessTimes.size() > cache.getMaxSize();\n                // here the cache check is only for performance gain; we are trying to avoid the sequence add + poll + hasCacheKey\n                // and prefer to check it in cache once in early stage.\n                // if there is nothing to remove in actual cache as of now, stop worrying about it.\n                if (evictionRequired && cache.hasCacheKey(eldest.getKey())) {\n                    pendingEvictions.addLast(eldest.getKey());\n\n                }\n                return evictionRequired;\n            }\n        };\n    }\n\n    @Override\n    public Cache getCache() {\n        return this.cache;\n    }\n\n    @Override\n    public EvictionType getType() {\n        return EvictionType.AGE;\n    }\n\n    @Override\n    public String getName() {\n        return \"Simple L(east) R(ecently) U(sed)\";\n    }\n\n    @Override\n    public synchronized CacheKey evictNext() {\n        CacheKey cacheKey = pendingEvictions.pollFirst();\n        while (cacheKey != null && !cache.hasCacheKey(cacheKey)) {\n            cacheKey = pendingEvictions.pollFirst();\n        }\n        return cacheKey;\n    }\n\n    @Override\n    public synchronized List<CacheKey> evictMany(int n) {\n        List<CacheKey> result = new ArrayList<>();\n        for (int i = 0; i < n; i++) {\n            result.add(this.evictNext());\n        }\n        return result;\n    }\n\n    @Override\n    public synchronized void touch(CacheKey cacheKey) {\n        this.accessTimes.put(cacheKey, new Date().getTime());\n    }\n\n    @Override\n    public synchronized boolean reset(CacheKey cacheKey) {\n        return this.accessTimes.remove(cacheKey) != null;\n    }\n\n    @Override\n    public synchronized int resetAll() {\n        int result = this.accessTimes.size();\n        accessTimes.clear();\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/RedisVersion.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.Arrays;\n\nclass RedisVersion implements Comparable<RedisVersion> {\n\n    private String version;\n    private Integer[] numbers;\n\n    public RedisVersion(String version) {\n        if (version == null) throw new IllegalArgumentException(\"Version can not be null\");\n        this.version = version;\n        this.numbers = Arrays.stream(version.split(\"\\\\.\")).map(n -> Integer.parseInt(n)).toArray(Integer[]::new);\n    }\n\n    @Override\n    public int compareTo(RedisVersion other) {\n        int max = Math.max(this.numbers.length, other.numbers.length);\n        for (int i = 0; i < max; i++) {\n            int thisNumber = this.numbers.length > i ? this.numbers[i]:0;\n            int otherNumber = other.numbers.length > i ? other.numbers[i]:0;\n            if (thisNumber < otherNumber) return -1;\n            if (thisNumber > otherNumber) return 1;\n        }\n        return 0;\n    }\n\n    @Override\n    public String toString() {\n        return this.version;\n    }\n\n    @Override\n    public boolean equals(Object that) {\n        if (this == that) return true;\n        if (that == null) return false;\n        if (this.getClass() != that.getClass()) return false;\n        return this.compareTo((RedisVersion) that) == 0;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/package-info.java",
    "content": "/**\n * This package contains the classes and interfaces related to Server-assisted Client-side Caching.\n */\n@Experimental\npackage redis.clients.jedis.csc;\n\nimport redis.clients.jedis.annots.Experimental;"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java",
    "content": "package redis.clients.jedis.csc.util;\n\nimport java.util.List;\nimport java.util.Set;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.csc.DefaultCacheable;\nimport redis.clients.jedis.csc.Cacheable;\n\npublic class AllowAndDenyListWithStringKeys implements Cacheable {\n\n  private final Set<ProtocolCommand> allowCommands;\n  private final Set<ProtocolCommand> denyCommands;\n\n  private final Set<String> allowKeys;\n  private final Set<String> denyKeys;\n\n  public AllowAndDenyListWithStringKeys(Set<ProtocolCommand> allowCommands, Set<ProtocolCommand> denyCommands,\n      Set<String> allowKeys, Set<String> denyKeys) {\n    this.allowCommands = allowCommands;\n    this.denyCommands = denyCommands;\n    this.allowKeys = allowKeys;\n    this.denyKeys = denyKeys;\n  }\n\n  @Override\n  public boolean isCacheable(ProtocolCommand command, List<Object> keys) {\n    if (allowCommands != null && !allowCommands.contains(command)) {\n      return false;\n    }\n    if (denyCommands != null && denyCommands.contains(command)) {\n      return false;\n    }\n\n    for (Object key : keys) {\n      if (!(key instanceof String)) {\n        return false;\n      }\n      if (allowKeys != null && !allowKeys.contains((String) key)) {\n        return false;\n      }\n      if (denyKeys != null && denyKeys.contains((String) key)) {\n        return false;\n      }\n    }\n\n    return DefaultCacheable.isDefaultCacheableCommand(command);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/csc/util/package-info.java",
    "content": "/**\n * This package contains the helper classes related to Server-assisted Client-side Caching.\n */\n@Experimental\npackage redis.clients.jedis.csc.util;\n\nimport redis.clients.jedis.annots.Experimental;"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/ClusterAggregationException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * Exception thrown when cluster reply aggregation fails.\n * <p>\n * This exception is thrown when aggregating replies from multiple cluster nodes and the aggregation\n * policy requirements are not met (e.g., ALL_SUCCEEDED policy requires all replies to be equal, but\n * different values were received).\n * </p>\n */\npublic class ClusterAggregationException extends JedisClusterOperationException {\n\n  private static final long serialVersionUID = 1L;\n\n  public ClusterAggregationException(String message) {\n    super(message);\n  }\n\n  public ClusterAggregationException(Throwable cause) {\n    super(cause);\n  }\n\n  public ClusterAggregationException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/InvalidURIException.java",
    "content": "package redis.clients.jedis.exceptions;\n\npublic class InvalidURIException extends JedisException {\n\n  private static final long serialVersionUID = -781691993326357802L;\n\n  public InvalidURIException(String message) {\n    super(message);\n  }\n\n  public InvalidURIException(Throwable cause) {\n    super(cause);\n  }\n\n  public InvalidURIException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisAccessControlException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * An access control error reply from Redis; i.e. {@code -WRONGPASS}, {@code -NOPERM}.\n */\npublic class JedisAccessControlException extends JedisDataException {\n\n  public JedisAccessControlException(String message) {\n    super(message);\n  }\n\n  public JedisAccessControlException(Throwable cause) {\n    super(cause);\n  }\n\n  public JedisAccessControlException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisAskDataException.java",
    "content": "package redis.clients.jedis.exceptions;\n\nimport redis.clients.jedis.HostAndPort;\n\n/**\n * {@code -ASK} reply from Redis.\n */\npublic class JedisAskDataException extends JedisRedirectionException {\n\n  private static final long serialVersionUID = 3878126572474819403L;\n\n  public JedisAskDataException(Throwable cause, HostAndPort targetHost, int slot) {\n    super(cause, targetHost, slot);\n  }\n\n  public JedisAskDataException(String message, Throwable cause, HostAndPort targetHost, int slot) {\n    super(message, cause, targetHost, slot);\n  }\n\n  public JedisAskDataException(String message, HostAndPort targetHost, int slot) {\n    super(message, targetHost, slot);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisBroadcastException.java",
    "content": "package redis.clients.jedis.exceptions;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport redis.clients.jedis.HostAndPort;\n\n/**\n * Exception thrown when a broadcast command fails on one or more cluster nodes.\n * <p>\n * This exception collects replies from all nodes, including both successful responses and errors.\n * Use {@link #getReplies()} to inspect the per-node results.\n * <p>\n * Note: This exception extends {@link JedisDataException} just so existing applications catching\n * JedisDataException do not get broken.\n */\n// TODO: extends JedisException\npublic class JedisBroadcastException extends JedisDataException {\n\n  private static final String BROADCAST_ERROR_MESSAGE = \"A failure occurred while broadcasting the command.\";\n\n  private final Map<HostAndPort, Object> replies = new HashMap<>();\n\n  public JedisBroadcastException() {\n    super(BROADCAST_ERROR_MESSAGE);\n  }\n\n  public void addReply(HostAndPort node, Object reply) {\n    replies.put(node, reply);\n  }\n\n  public Map<HostAndPort, Object> getReplies() {\n    return Collections.unmodifiableMap(replies);\n  }\n\n  /**\n   * Prepares the exception for throwing by:\n   * <ul>\n   *   <li>Refreshing the stack trace to show where it's thrown from (not where it was created)</li>\n   *   <li>Setting the cause to the first error encountered for better debugging</li>\n   * </ul>\n   * @return this exception, ready to be thrown\n   */\n  public JedisBroadcastException prepareToThrow() {\n    // Refresh stack trace to show throw location instead of creation location\n    fillInStackTrace();\n\n    // Set the first exception as the cause for better debugging\n    if (getCause() == null) {\n      for (Object reply : replies.values()) {\n        if (reply instanceof Throwable) {\n          initCause((Throwable) reply);\n          break;\n        }\n      }\n    }\n\n    return this;\n  }\n\n  @Override\n  public String getMessage() {\n    StringBuilder sb = new StringBuilder(BROADCAST_ERROR_MESSAGE);\n\n    int errorCount = 0;\n    int successCount = 0;\n    String firstErrorMessage = null;\n\n    for (Object reply : replies.values()) {\n      if (reply instanceof Throwable) {\n        errorCount++;\n        if (firstErrorMessage == null) {\n          firstErrorMessage = ((Throwable) reply).getMessage();\n        }\n      } else {\n        successCount++;\n      }\n    }\n\n    sb.append(\" (\").append(successCount).append(\" succeeded, \")\n        .append(errorCount).append(\" failed\");\n\n    if (firstErrorMessage != null) {\n      sb.append(\"; first error: \").append(firstErrorMessage);\n    }\n\n    sb.append(\")\");\n\n    return sb.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisBusyException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * {@code -BUSY} reply from Redis.\n */\npublic class JedisBusyException extends JedisDataException {\n\n  private static final long serialVersionUID = 3992655220229243478L;\n\n  public JedisBusyException(final String message) {\n    super(message);\n  }\n\n  public JedisBusyException(final Throwable cause) {\n    super(cause);\n  }\n\n  public JedisBusyException(final String message, final Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java",
    "content": "package redis.clients.jedis.exceptions;\n\npublic class JedisCacheException extends JedisException {\n\n    private static final long serialVersionUID = 3878126572474819403L;\n\n    public JedisCacheException(String message) {\n        super(message);\n    }\n\n    public JedisCacheException(Throwable cause) {\n        super(cause);\n    }\n\n    public JedisCacheException(String message, Throwable cause) {\n        super(message, cause);\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisClusterException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * Any {@code -CLUSTER...} reply from Redis.\n */\npublic class JedisClusterException extends JedisDataException {\n\n  private static final long serialVersionUID = 3878126572474819403L;\n\n  public JedisClusterException(Throwable cause) {\n    super(cause);\n  }\n\n  public JedisClusterException(String message, Throwable cause) {\n    super(message, cause);\n  }\n\n  public JedisClusterException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisClusterOperationException.java",
    "content": "package redis.clients.jedis.exceptions;\n\nimport redis.clients.jedis.HostAndPort;\n\n/**\n * Error while processing cluster operations. This is not an error reply from Redis.\n * <p>\n * This exception can optionally include the {@link HostAndPort} of the node that caused the\n * failure, which is useful for debugging and error reporting in multi-node operations.\n */\npublic class JedisClusterOperationException extends JedisException {\n\n  private static final long serialVersionUID = 8124535086306604887L;\n\n  private final HostAndPort node;\n\n  public JedisClusterOperationException(String message) {\n    super(message);\n    this.node = null;\n  }\n\n  public JedisClusterOperationException(Throwable cause) {\n    super(cause);\n    this.node = null;\n  }\n\n  public JedisClusterOperationException(String message, Throwable cause) {\n    super(message, cause);\n    this.node = null;\n  }\n\n  /**\n   * Creates an exception with the node that caused the failure.\n   *\n   * @param message the detail message\n   * @param node the node that caused the failure (may be null)\n   */\n  public JedisClusterOperationException(String message, HostAndPort node) {\n    super(message);\n    this.node = node;\n  }\n\n  /**\n   * Creates an exception with the node that caused the failure.\n   *\n   * @param message the detail message\n   * @param cause the underlying cause\n   * @param node the node that caused the failure (may be null)\n   */\n  public JedisClusterOperationException(String message, Throwable cause, HostAndPort node) {\n    super(message, cause);\n    this.node = node;\n  }\n\n  /**\n   * Returns the node that caused this exception, if known.\n   *\n   * @return the node that caused the failure, or null if unknown\n   */\n  public HostAndPort getNode() {\n    return node;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisConnectionException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * A connection error.\n */\npublic class JedisConnectionException extends JedisException {\n\n  private static final long serialVersionUID = 3878126572474819403L;\n\n  public JedisConnectionException(String message) {\n    super(message);\n  }\n\n  public JedisConnectionException(Throwable cause) {\n    super(cause);\n  }\n\n  public JedisConnectionException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisDataException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * Any error reply from Redis.\n */\npublic class JedisDataException extends JedisException {\n\n  private static final long serialVersionUID = 3878126572474819403L;\n\n  public JedisDataException(String message) {\n    super(message);\n  }\n\n  public JedisDataException(Throwable cause) {\n    super(cause);\n  }\n\n  public JedisDataException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * Umbrella exception class for all exceptions in Jedis library.\n */\npublic class JedisException extends RuntimeException {\n\n  private static final long serialVersionUID = -2946266495682282677L;\n\n  public JedisException(String message) {\n    super(message);\n  }\n\n  public JedisException(Throwable e) {\n    super(e);\n  }\n\n  public JedisException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisMovedDataException.java",
    "content": "package redis.clients.jedis.exceptions;\n\nimport redis.clients.jedis.HostAndPort;\n\n/**\n * {@code -MOVED} reply from Redis.\n */\npublic class JedisMovedDataException extends JedisRedirectionException {\n\n  private static final long serialVersionUID = 3878126572474819403L;\n\n  public JedisMovedDataException(String message, HostAndPort targetNode, int slot) {\n    super(message, targetNode, slot);\n  }\n\n  public JedisMovedDataException(Throwable cause, HostAndPort targetNode, int slot) {\n    super(cause, targetNode, slot);\n  }\n\n  public JedisMovedDataException(String message, Throwable cause, HostAndPort targetNode, int slot) {\n    super(message, cause, targetNode, slot);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisNoScriptException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * {@code -NOSCRIPT} reply from Redis.\n */\npublic class JedisNoScriptException extends JedisDataException {\n\n  private static final long serialVersionUID = 4674378093072060731L;\n\n  public JedisNoScriptException(final String message) {\n    super(message);\n  }\n\n  public JedisNoScriptException(final Throwable cause) {\n    super(cause);\n  }\n\n  public JedisNoScriptException(final String message, final Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisRedirectionException.java",
    "content": "package redis.clients.jedis.exceptions;\n\nimport redis.clients.jedis.HostAndPort;\n\n/**\n * Umbrella exception class representing all redirection replies from Redis.\n *\n * @see JedisAskDataException\n * @see JedisMovedDataException\n */\npublic class JedisRedirectionException extends JedisDataException {\n\n  private static final long serialVersionUID = 3878126572474819403L;\n\n  private final HostAndPort targetNode;\n  private final int slot;\n\n  public JedisRedirectionException(String message, HostAndPort targetNode, int slot) {\n    super(message);\n    this.targetNode = targetNode;\n    this.slot = slot;\n  }\n\n  public JedisRedirectionException(Throwable cause, HostAndPort targetNode, int slot) {\n    super(cause);\n    this.targetNode = targetNode;\n    this.slot = slot;\n  }\n\n  public JedisRedirectionException(String message, Throwable cause, HostAndPort targetNode, int slot) {\n    super(message, cause);\n    this.targetNode = targetNode;\n    this.slot = slot;\n  }\n\n  public final HostAndPort getTargetNode() {\n    return targetNode;\n  }\n\n  public final int getSlot() {\n    return slot;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/JedisValidationException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * A validation error.\n */\npublic class JedisValidationException extends JedisException {\n\n  private static final long serialVersionUID = 1134169242443303479L;\n\n  public JedisValidationException(String message) {\n    super(message);\n  }\n\n  public JedisValidationException(Throwable cause) {\n    super(cause);\n  }\n\n  public JedisValidationException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/UnsupportedAggregationException.java",
    "content": "package redis.clients.jedis.exceptions;\n\n/**\n * Exception thrown when an unsupported aggregation operation is attempted.\n * <p>\n * This exception is thrown when the aggregation policy cannot handle the given data types. For\n * example, when trying to sum non-numeric values or compare non-comparable types.\n * </p>\n */\npublic class UnsupportedAggregationException extends ClusterAggregationException {\n\n  private static final long serialVersionUID = 1L;\n\n  public UnsupportedAggregationException(String message) {\n    super(message);\n  }\n\n  public UnsupportedAggregationException(Throwable cause) {\n    super(cause);\n  }\n\n  public UnsupportedAggregationException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/exceptions/package-info.java",
    "content": "/**\n * This package contains the Exception classes.\n */\npackage redis.clients.jedis.exceptions;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java",
    "content": "package redis.clients.jedis.executors;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.annots.VisibleForTesting;\nimport redis.clients.jedis.exceptions.*;\nimport redis.clients.jedis.executors.aggregators.MultiNodeResultAggregator;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.util.JedisAsserts;\n\npublic class ClusterCommandExecutor implements CommandExecutor {\n\n  private final Logger log = LoggerFactory.getLogger(getClass());\n\n  /**\n   * Connection resolver used for keyed commands, to acquire connection based on slot.\n   */\n  private final ConnectionResolver slotBasedConnectionResolver;\n\n  /**\n   * Connection resolver used for keyless commands, to acquire connection in round-robin fashion\n   * from arbitrary node.\n   */\n  private final ConnectionResolver roundRobinConnectionResolver;\n\n  /**\n   * Connection resolver used to enforce command execution on replicas.\n   *\n   * @see #executeCommandToReplica(CommandObject)\n   */\n  private final ConnectionResolver replicaOnlyConnectionResolver;\n\n  public final ClusterConnectionProvider provider;\n  protected final int maxAttempts;\n  protected final Duration maxTotalRetriesDuration;\n  protected final CommandFlagsRegistry flags;\n\n  /**\n   * @deprecated use {@link #ClusterCommandExecutor(ClusterConnectionProvider, int, Duration, CommandFlagsRegistry)}\n   * instead. This constructor will be removed in the next major version.\n   */\n  @Deprecated\n  public ClusterCommandExecutor(ClusterConnectionProvider provider, int maxAttempts,\n      Duration maxTotalRetriesDuration) {\n    this(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry());\n  }\n\n  public ClusterCommandExecutor(ClusterConnectionProvider provider, int maxAttempts,\n      Duration maxTotalRetriesDuration, CommandFlagsRegistry flags) {\n    JedisAsserts.notNull(flags, \"CommandFlagsRegistry must not be null\");\n    JedisAsserts.notNull(provider, \"provider must not be null\");\n    JedisAsserts.isTrue(maxAttempts > 0, \"maxAttempts must be greater than 0\");\n    JedisAsserts.notNull(maxTotalRetriesDuration, \"maxTotalRetriesDuration must not be null\");\n\n    this.provider = provider;\n    this.maxAttempts = maxAttempts;\n    this.maxTotalRetriesDuration = maxTotalRetriesDuration;\n    this.flags = flags;\n\n    this.slotBasedConnectionResolver = ConnectionResolverFactory.createSlotBasedResolver(\n        provider);\n    this.roundRobinConnectionResolver = ConnectionResolverFactory.createRoundRobinResolver(\n        provider, flags);\n    this.replicaOnlyConnectionResolver = ConnectionResolverFactory.createReplicaOnlyResolver(\n        provider);\n  }\n\n  @Override\n  public void close() {\n    this.provider.close();\n  }\n\n  /**\n   * Broadcast a command to cluster nodes.\n   * <p>\n   * This method uses {@link #doExecuteCommand} with a {@link SingleConnectionResolver} for each\n   * node, which adds retry logic and connection failure handling to broadcast commands.\n   * Redirections are not followed since we want to execute on specific nodes.\n   * <p>\n   * Error handling depends on the command's response policy:\n   * <ul>\n   *   <li>{@code ONE_SUCCEEDED}: Returns success if at least one node succeeds</li>\n   *   <li>Other policies: Throws {@link JedisBroadcastException} if any node fails</li>\n   * </ul>\n   *\n   * @param commandObject the command to broadcast\n   * @param primaryOnly if true, broadcast only to primary nodes; if false, broadcast to all nodes\n   *        including replicas\n   * @return the reply from the command\n   * @throws JedisBroadcastException if error handling criteria based on response policy are not met\n   */\n  public final <T> T broadcastCommand(CommandObject<T> commandObject, boolean primaryOnly) {\n    Map<String, ConnectionPool> connectionMap = primaryOnly ? provider.getPrimaryNodesConnectionMap()\n        : provider.getConnectionMap();\n\n    // Get the response policy for aggregation\n    CommandFlagsRegistry.ResponsePolicy responsePolicy = flags.getResponsePolicy(commandObject\n        .getArguments());\n\n    MultiNodeResultAggregator<T> aggregator = new MultiNodeResultAggregator<>(responsePolicy);\n\n    for (Map.Entry<String, ConnectionPool> entry : connectionMap.entrySet()) {\n      HostAndPort node = HostAndPort.from(entry.getKey());\n      ConnectionPool pool = entry.getValue();\n      try {\n        // Create a resolver that acquires connections from this specific node's pool\n        // A fresh connection is obtained on each resolve() call, allowing retries to work correctly\n        // The doExecuteCommand method will close the connection after each attempt\n        SingleConnectionResolver resolver = new SingleConnectionResolver(pool);\n        // Don't follow redirections - we want to execute on specific nodes\n        T aReply = doExecuteCommand(commandObject, resolver, false);\n        aggregator.addSuccess(node, aReply);\n      } catch (Exception anError) {\n        aggregator.addError(node, anError);\n      }\n    }\n\n    return aggregator.getResult();\n  }\n\n  /**\n   * Execute multiple command objects across different cluster shards and aggregate the results.\n   * <p>\n   * This method is designed for commands that need to operate on keys distributed across multiple\n   * hash slots (e.g., DEL, EXISTS, MGET with keys from different slots). Each CommandObject in the\n   * list is executed on its appropriate shard based on the key's hash slot, and the results are\n   * aggregated using the command's response policy.\n   * <p>\n   * Error handling depends on the command's response policy:\n   * <ul>\n   *   <li>{@code ONE_SUCCEEDED}: Returns success if at least one shard succeeds</li>\n   *   <li>Other policies: Throws {@link JedisBroadcastException} if any shard fails</li>\n   * </ul>\n   *\n   * @param commandObjects list of CommandObject instances, each targeting keys in the same hash slot\n   * @param <T> the return type of the command\n   * @return the aggregated reply from all shards\n   * @throws JedisBroadcastException if error handling criteria based on response policy are not met\n   */\n  public final <T> T executeMultiShardCommand(List<CommandObject<T>> commandObjects) {\n    if (commandObjects == null || commandObjects.isEmpty()) {\n      throw new IllegalArgumentException(\"commandObjects must not be null or empty\");\n    }\n\n    // Get the response policy from the first command (all commands should have the same policy)\n    CommandFlagsRegistry.ResponsePolicy responsePolicy = flags.getResponsePolicy(\n        commandObjects.get(0).getArguments());\n\n    MultiNodeResultAggregator<T> aggregator = new MultiNodeResultAggregator<>(responsePolicy);\n\n    for (CommandObject<T> commandObject : commandObjects) {\n      try {\n        // Execute each command on its appropriate shard using the existing retry logic\n        T aReply = doExecuteCommand(commandObject, slotBasedConnectionResolver, true);\n        aggregator.addSuccess(aReply);\n      } catch (Exception anError) {\n        // Extract node from exception (JedisClusterOperationException includes node info)\n        aggregator.addError(anError);\n      }\n    }\n\n    return aggregator.getResult();\n  }\n\n  @Override\n  public final <T> T executeCommand(CommandObject<T> commandObject) {\n    CommandArguments args = commandObject.getArguments();\n    CommandFlagsRegistry.RequestPolicy requestPolicy = flags.getRequestPolicy(args);\n\n    switch (requestPolicy) {\n      case ALL_SHARDS:\n        // Execute on all primary nodes (shards)\n        return broadcastCommand(commandObject, true);\n\n      case ALL_NODES:\n        // Execute on all nodes including replicas\n        return broadcastCommand(commandObject, false);\n\n      // NOTE(imalinovskyi): Handling of special commands (SCAN, FT.CURSOR, etc.) should happen\n      // in the custom abstractions and dedicated executor methods.\n      case MULTI_SHARD: // Here we assume that MULTI_SHARD is already split into single-shard commands\n                        // and we just execute them one by one\n      case SPECIAL:\n      case DEFAULT:\n      default:\n        // Default behavior: check if keyless, otherwise use single-shard routing\n        if (args.isKeyless()) {\n          return executeKeylessCommand(commandObject);\n        } else {\n          return executeKeyedCommand(commandObject);\n        }\n    }\n  }\n\n  public final <T> T executeCommandToReplica(CommandObject<T> commandObject) {\n    return doExecuteCommand(commandObject, replicaOnlyConnectionResolver, true);\n  }\n\n  private <T> T executeKeylessCommand(CommandObject<T> commandObject) {\n    // For keyless commands, don't follow redirections - just retry with a different random node\n    return doExecuteCommand(commandObject, roundRobinConnectionResolver, false);\n  }\n\n  private <T> T executeKeyedCommand(CommandObject<T> commandObject) {\n    return doExecuteCommand(commandObject, slotBasedConnectionResolver, true);\n  }\n\n  /**\n   * Executes a command with retry logic using the provided connection resolver.\n   *\n   * @param commandObject the command to execute\n   * @param resolver the connection resolver to use for acquiring connections\n   * @param followRedirections whether to follow cluster redirections (MOVED/ASK). Set to false for\n   *     keyless commands that should retry with the resolver instead.\n   * @return the command result\n   * @throws JedisClusterOperationException if all retry attempts fail, includes the last node tried\n   */\n  private <T> T doExecuteCommand(CommandObject<T> commandObject, ConnectionResolver resolver,\n      boolean followRedirections) {\n    Instant deadline = Instant.now().plus(maxTotalRetriesDuration);\n\n    JedisRedirectionException redirect = null;\n    int consecutiveConnectionFailures = 0;\n    Exception lastException = null;\n    HostAndPort lastNode = null;  // Track the last node we attempted to use\n    for (int attemptsLeft = this.maxAttempts; attemptsLeft > 0; attemptsLeft--) {\n      Connection connection = null;\n      try {\n        if (followRedirections && redirect != null) {\n          // Following redirection, we need to use connection to the target node\n          connection = provider.getConnection(redirect.getTargetNode());\n          if (redirect instanceof JedisAskDataException) {\n            // TODO: Pipeline asking with the original command to make it faster....\n            connection.executeCommand(Protocol.Command.ASKING);\n          }\n        } else {\n          connection = resolver.resolve(commandObject);\n        }\n\n        // Track the node we're using for error reporting\n        lastNode = connection.getHostAndPort();\n\n        return execute(connection, commandObject);\n\n      } catch (JedisClusterOperationException jcoe) {\n        throw jcoe;\n      } catch (JedisConnectionException jce) {\n        lastException = jce;\n        ++consecutiveConnectionFailures;\n        log.debug(\"Failed connecting to Redis: {}\", connection, jce);\n        // \"- 1\" because we just did one, but the attemptsLeft counter hasn't been decremented yet\n        boolean reset = handleConnectionProblem(attemptsLeft - 1, consecutiveConnectionFailures, deadline);\n        if (reset) {\n          consecutiveConnectionFailures = 0;\n          redirect = null;\n        }\n      } catch (JedisRedirectionException jre) {\n        // avoid updating lastException if it is a connection exception\n        if (lastException == null || lastException instanceof JedisRedirectionException) {\n          lastException = jre;\n        }\n        if (followRedirections) {\n          log.debug(\"Redirected by server to {}\", jre.getTargetNode());\n          redirect = jre;\n          // if MOVED redirection occurred,\n          if (jre instanceof JedisMovedDataException) {\n            // it rebuilds cluster's slot cache recommended by Redis cluster specification\n            provider.renewSlotCache(connection);\n          }\n        } else {\n          // When followRedirections is false, throw the redirection exception immediately\n          // instead of silently handling or ignoring it\n          throw jre;\n        }\n        consecutiveConnectionFailures = 0;\n      } finally {\n        IOUtils.closeQuietly(connection);\n      }\n      if (Instant.now().isAfter(deadline)) {\n        throw new JedisClusterOperationException(\"Cluster retry deadline exceeded.\", lastException,\n            lastNode);\n      }\n    }\n\n    JedisClusterOperationException maxAttemptsException =\n        new JedisClusterOperationException(\"No more cluster attempts left.\", lastException,\n            lastNode);\n    throw maxAttemptsException;\n  }\n\n    /**\n   * WARNING: This method is accessible for the purpose of testing.\n   * This should not be used or overriden.\n   */\n  @VisibleForTesting\n  protected <T> T execute(Connection connection, CommandObject<T> commandObject) {\n    return connection.executeCommand(commandObject);\n  }\n\n  /**\n   * Related values should be reset if <code>TRUE</code> is returned.\n   * @param attemptsLeft\n   * @param consecutiveConnectionFailures\n   * @param doneDeadline\n   * @return true - if some actions are taken\n   * <br /> false - if no actions are taken\n   */\n  private boolean handleConnectionProblem(int attemptsLeft, int consecutiveConnectionFailures, Instant doneDeadline) {\n    if (this.maxAttempts < 3) {\n      // Since we only renew the slots cache after two consecutive connection\n      // failures (see consecutiveConnectionFailures above), we need to special\n      // case the situation where we max out after two or fewer attempts.\n      // Otherwise, on two or fewer max attempts, the slots cache would never be\n      // renewed.\n      if (attemptsLeft == 0) {\n        provider.renewSlotCache();\n        return true;\n      }\n      return false;\n    }\n\n    if (consecutiveConnectionFailures < 2) {\n      return false;\n    }\n\n    sleep(getBackoffSleepMillis(attemptsLeft, doneDeadline));\n    //We need this because if node is not reachable anymore - we need to finally initiate slots\n    //renewing, or we can stuck with cluster state without one node in opposite case.\n    //TODO make tracking of successful/unsuccessful operations for node - do renewing only\n    //if there were no successful responses from this node last few seconds\n    provider.renewSlotCache();\n    return true;\n  }\n\n  private static long getBackoffSleepMillis(int attemptsLeft, Instant deadline) {\n    if (attemptsLeft <= 0) {\n      return 0;\n    }\n\n    long millisLeft = Duration.between(Instant.now(), deadline).toMillis();\n    if (millisLeft < 0) {\n      throw new JedisClusterOperationException(\"Cluster retry deadline exceeded.\");\n    }\n\n    long maxBackOff = millisLeft / (attemptsLeft * attemptsLeft);\n    return ThreadLocalRandom.current().nextLong(maxBackOff + 1);\n  }\n\n  /**\n   * WARNING: This method is accessible for the purpose of testing.\n   * This should not be used or overriden.\n   */\n  @VisibleForTesting\n  protected void sleep(long sleepMillis) {\n    try {\n      TimeUnit.MILLISECONDS.sleep(sleepMillis);\n    } catch (InterruptedException e) {\n      throw new JedisClusterOperationException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/CommandExecutor.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandObject;\n\npublic interface CommandExecutor extends AutoCloseable {\n\n  <T> T executeCommand(CommandObject<T> commandObject);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/ConnectionResolver.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\n\n/**\n * Connection resolver interface for determining which connection to use for command execution.\n * <p>\n * This interface is used internally by the cluster command executor to resolve connections based on\n * the command type (keyed vs keyless) and read preference configuration.\n */\ninterface ConnectionResolver {\n\n  /**\n   * Intent of a connection request - whether the command is a read or write operation.\n   */\n  enum ConnectionIntent {\n    READ, WRITE\n  }\n\n  /**\n   * Resolves the appropriate connection for executing the given command.\n   * @param cmd the command object to execute\n   * @return the connection to use for command execution\n   */\n  Connection resolve(CommandObject<?> cmd);\n\n  /**\n   * Determines the intent (READ or WRITE) for a given command based on its flags.\n   * @param command the command object to check\n   * @param flags the command flags registry to use for flag lookup\n   * @return ConnectionIntent.READ if the command has the READONLY flag, otherwise\n   *         ConnectionIntent.WRITE\n   */\n  default ConnectionIntent getIntent(CommandObject<?> command, CommandFlagsRegistry flags) {\n    return flags.getFlags(command.getArguments()).contains(\n      CommandFlagsRegistry.CommandFlag.READONLY) ? ConnectionIntent.READ : ConnectionIntent.WRITE;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/ConnectionResolverFactory.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\n\n/**\n * Factory for creating {@link ConnectionResolver} instances.\n * <p>\n * This factory provides access to the package-private connection resolver implementations.\n */\nfinal class ConnectionResolverFactory {\n\n  private ConnectionResolverFactory() {\n    // Utility class\n  }\n\n  /**\n   * Creates a slot-based connection resolver for keyed commands.\n   * @param provider the cluster connection provider*\n   * @return a new SlotBasedConnectionResolver\n   */\n  public static ConnectionResolver createSlotBasedResolver(ClusterConnectionProvider provider) {\n    return new SlotBasedConnectionResolver(provider);\n  }\n\n  /**\n   * Creates a round-robin connection resolver for keyless commands.\n   * @param provider the cluster connection provider\n   * @param flags the command flags registry\n   * @return a new RoundRobinConnectionResolver\n   */\n  public static ConnectionResolver createRoundRobinResolver(ClusterConnectionProvider provider,\n      CommandFlagsRegistry flags) {\n    return new RoundRobinConnectionResolver(provider, flags);\n  }\n\n  /**\n   * Creates a replica-only connection resolver.\n   * @param provider the cluster connection provider\n   * @return a new ReplicaOnlyConnectionResolver\n   */\n  public static ConnectionResolver createReplicaOnlyResolver(ClusterConnectionProvider provider) {\n    return new ReplicaOnlyConnectionResolver(provider);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/DefaultCommandExecutor.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\npublic class DefaultCommandExecutor implements CommandExecutor {\n\n  protected final ConnectionProvider provider;\n\n  public DefaultCommandExecutor(ConnectionProvider provider) {\n    this.provider = provider;\n  }\n\n  @Override\n  public void close() {\n    IOUtils.closeQuietly(this.provider);\n  }\n\n  @Override\n  public final <T> T executeCommand(CommandObject<T> commandObject) {\n    try (Connection connection = provider.getConnection(commandObject.getArguments())) {\n      return connection.executeCommand(commandObject);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/ReplicaOnlyConnectionResolver.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\n\n/**\n * Connection resolver that always routes to replica nodes.\n * <p>\n * This resolver is used to enforce command execution on replicas, regardless of the command type or\n * global ReadFrom configuration.\n * @see redis.clients.jedis.executors.ClusterCommandExecutor#executeCommandToReplica(CommandObject)\n */\nfinal class ReplicaOnlyConnectionResolver implements ConnectionResolver {\n\n  private final ClusterConnectionProvider provider;\n\n  ReplicaOnlyConnectionResolver(ClusterConnectionProvider provider) {\n    this.provider = provider;\n  }\n\n  @Override\n  public Connection resolve(CommandObject<?> cmd) {\n    return provider.getReplicaConnection(cmd.getArguments());\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/RetryableCommandExecutor.java",
    "content": "package redis.clients.jedis.executors;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.annots.VisibleForTesting;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.util.JedisAsserts;\n\npublic class RetryableCommandExecutor implements CommandExecutor {\n\n  private final Logger log = LoggerFactory.getLogger(getClass());\n\n  protected final ConnectionProvider provider;\n  protected final int maxAttempts;\n  protected final Duration maxTotalRetriesDuration;\n\n  public RetryableCommandExecutor(ConnectionProvider provider, int maxAttempts,\n      Duration maxTotalRetriesDuration) {\n    JedisAsserts.notNull(provider, \"provider must not be null\");\n    JedisAsserts.isTrue(maxAttempts > 0, \"maxAttempts must be greater than 0\");\n    JedisAsserts.notNull(maxTotalRetriesDuration, \"maxTotalRetriesDuration must not be null\");\n\n    this.provider = provider;\n    this.maxAttempts = maxAttempts;\n    this.maxTotalRetriesDuration = maxTotalRetriesDuration;\n  }\n\n  @Override\n  public void close() {\n    IOUtils.closeQuietly(this.provider);\n  }\n\n  @Override\n  public final <T> T executeCommand(CommandObject<T> commandObject) {\n\n    Instant deadline = Instant.now().plus(maxTotalRetriesDuration);\n\n    int consecutiveConnectionFailures = 0;\n    JedisException lastException = null;\n    for (int attemptsLeft = this.maxAttempts; attemptsLeft > 0; attemptsLeft--) {\n      Connection connection = null;\n      try {\n        connection = provider.getConnection(commandObject.getArguments());\n\n        return execute(connection, commandObject);\n\n      } catch (JedisConnectionException jce) {\n        lastException = jce;\n        ++consecutiveConnectionFailures;\n        log.debug(\"Failed connecting to Redis: {}\", connection, jce);\n        // \"- 1\" because we just did one, but the attemptsLeft counter hasn't been decremented yet\n        boolean reset = handleConnectionProblem(attemptsLeft - 1, consecutiveConnectionFailures, deadline);\n        if (reset) {\n          consecutiveConnectionFailures = 0;\n        }\n      } finally {\n        if (connection != null) {\n          connection.close();\n        }\n      }\n      if (Instant.now().isAfter(deadline)) {\n        throw new JedisException(\"Retry deadline exceeded.\");\n      }\n    }\n\n    JedisException maxAttemptsException = new JedisException(\"No more attempts left.\");\n    if (lastException != null) {\n      maxAttemptsException.addSuppressed(lastException);\n    }\n    throw maxAttemptsException;\n  }\n\n  /**\n   * WARNING: This method is accessible for the purpose of testing.\n   * This should not be used or overriden.\n   */\n  @VisibleForTesting\n  protected <T> T execute(Connection connection, CommandObject<T> commandObject) {\n    return connection.executeCommand(commandObject);\n  }\n\n  /**\n   * Related values should be reset if <code>TRUE</code> is returned.\n   *\n   * @param attemptsLeft\n   * @param consecutiveConnectionFailures\n   * @param doneDeadline\n   * @return true - if some actions are taken\n   * <br /> false - if no actions are taken\n   */\n  private boolean handleConnectionProblem(int attemptsLeft, int consecutiveConnectionFailures, Instant doneDeadline) {\n\n    if (consecutiveConnectionFailures < 2) {\n      return false;\n    }\n\n    sleep(getBackoffSleepMillis(attemptsLeft, doneDeadline));\n    return true;\n  }\n\n  private static long getBackoffSleepMillis(int attemptsLeft, Instant deadline) {\n    if (attemptsLeft <= 0) {\n      return 0;\n    }\n\n    long millisLeft = Duration.between(Instant.now(), deadline).toMillis();\n    if (millisLeft < 0) {\n      throw new JedisException(\"Retry deadline exceeded.\");\n    }\n\n    return millisLeft / (attemptsLeft * (attemptsLeft + 1));\n  }\n\n  /**\n   * WARNING: This method is accessible for the purpose of testing.\n   * This should not be used or overriden.\n   */\n  @VisibleForTesting\n  protected void sleep(long sleepMillis) {\n    try {\n      TimeUnit.MILLISECONDS.sleep(sleepMillis);\n    } catch (InterruptedException e) {\n      throw new JedisException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/RoundRobinConnectionResolver.java",
    "content": "package redis.clients.jedis.executors;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\n\n/**\n * Connection resolver for keyless commands that acquires connections in round-robin fashion.\n * <p>\n * This resolver distributes keyless commands evenly across cluster nodes using round-robin\n * selection. Read operations can go to any node for load distribution, while write operations go to\n * primary nodes only.\n */\nfinal class RoundRobinConnectionResolver implements ConnectionResolver {\n\n  private final ClusterConnectionProvider provider;\n  private final CommandFlagsRegistry flags;\n  private final AtomicInteger roundRobinCounter = new AtomicInteger(0);\n\n  RoundRobinConnectionResolver(ClusterConnectionProvider provider, CommandFlagsRegistry flags) {\n    this.provider = provider;\n    this.flags = flags;\n  }\n\n  @Override\n  public Connection resolve(CommandObject<?> cmd) {\n    ConnectionResolver.ConnectionIntent intent = getIntent(cmd, flags);\n    List<Map.Entry<String, ConnectionPool>> nodeList = selectConnectionPool(intent);\n\n    int size = nodeList.size();\n    // Get and increment counter, then apply modulo with the current list size.\n    // This handles the race condition where another thread may have updated the counter\n    // based on a different (larger) node list size, which could result in an index\n    // that is out of bounds for the current thread's smaller node list after topology change.\n    int roundRobinIndex = Math.abs(roundRobinCounter.getAndIncrement() % size);\n    Map.Entry<String, ConnectionPool> selectedEntry = nodeList.get(roundRobinIndex);\n    ConnectionPool pool = selectedEntry.getValue();\n\n    return pool.getResource();\n  }\n\n  private List<Map.Entry<String, ConnectionPool>> selectConnectionPool(\n      ConnectionResolver.ConnectionIntent intent) {\n    Map<String, ConnectionPool> connectionMap;\n\n    if (intent == ConnectionResolver.ConnectionIntent.READ) {\n      // For keyless READ commands, use all nodes for load distribution\n      connectionMap = provider.getConnectionMap();\n    } else {\n      // Write operations always go to primary nodes\n      connectionMap = provider.getPrimaryNodesConnectionMap();\n    }\n\n    if (connectionMap.isEmpty()) {\n      throw new JedisClusterOperationException(\"No cluster nodes available.\");\n    }\n\n    return new ArrayList<>(connectionMap.entrySet());\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/SimpleCommandExecutor.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.util.IOUtils;\n\npublic class SimpleCommandExecutor implements CommandExecutor {\n\n  protected final Connection connection;\n\n  public SimpleCommandExecutor(Connection connection) {\n    this.connection = connection;\n  }\n\n  @Override\n  public void close() {\n    IOUtils.closeQuietly(connection);\n  }\n\n  @Override\n  public final <T> T executeCommand(CommandObject<T> commandObject) {\n    return connection.executeCommand(commandObject);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/SingleConnectionResolver.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPool;\n\n/**\n * Connection resolver that acquires connections from a specific connection pool.\n * <p>\n * This resolver is used for broadcast commands where we want to execute commands on a specific\n * node's pool with the standard retry logic in {@link ClusterCommandExecutor#doExecuteCommand}.\n * <p>\n * Unlike other resolvers, this one is bound to a specific pool rather than using the cluster's\n * routing logic. A fresh connection is obtained from the pool on each call to {@link #resolve},\n * which allows retry logic to work correctly since the {@code doExecuteCommand} method closes the\n * connection after each attempt.\n * @see ClusterCommandExecutor#broadcastCommand(CommandObject, boolean)\n */\nfinal class SingleConnectionResolver implements ConnectionResolver {\n\n  private final ConnectionPool pool;\n\n  /**\n   * Creates a resolver that acquires connections from the given pool.\n   * @param pool the connection pool to acquire connections from\n   */\n  SingleConnectionResolver(ConnectionPool pool) {\n    this.pool = pool;\n  }\n\n  @Override\n  public Connection resolve(CommandObject<?> cmd) {\n    return pool.getResource();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/SlotBasedConnectionResolver.java",
    "content": "package redis.clients.jedis.executors;\n\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\n\n/**\n * Connection resolver for keyed commands that acquires connections based on hash slot.\n * <p>\n * This resolver routes commands to the appropriate cluster node based on the key's hash slot. All\n * commands (both read and write) are routed to the primary node for the slot.\n */\nfinal class SlotBasedConnectionResolver implements ConnectionResolver {\n\n  private final ClusterConnectionProvider provider;\n\n  SlotBasedConnectionResolver(ClusterConnectionProvider provider) {\n    this.provider = provider;\n  }\n\n  @Override\n  public Connection resolve(CommandObject<?> cmd) {\n    // Always route to primary node for slot-based routing\n    return provider.getConnection(cmd.getArguments());\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/Aggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\ninterface Aggregator<I, R> {\n\n  void add(I input);\n\n  R getResult();\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/ClusterReplyAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\n\nimport java.util.Objects;\n\n/**\n * Stateful aggregator that keeps aggregating replies of the same type according to the given\n * ResponsePolicy. The proper underlying aggregator is lazily created on the first non-null value.\n * If all added values are null, getResult() returns null.\n */\nclass ClusterReplyAggregator<T> implements Aggregator<T, T> {\n\n  private final CommandFlagsRegistry.ResponsePolicy policy;\n  private Aggregator<T, T> delegate;\n\n  public ClusterReplyAggregator(CommandFlagsRegistry.ResponsePolicy policy) {\n    this.policy = Objects.requireNonNull(policy, \"policy cannot be null\");\n  }\n\n  /**\n   * Adds a new value to the aggregator. Null values are ignored.\n   */\n  @Override\n  @SuppressWarnings(\"unchecked\")\n  public void add(T newReply) {\n    if (newReply == null) {\n      return; // ignore nulls\n    }\n\n    // Lazy initialization of delegate based on policy and first non-null value\n    if (delegate == null) {\n      switch (policy) {\n        case AGG_SUM:\n          if (!(newReply instanceof Number)) {\n            throw new UnsupportedAggregationException(\n                \"AGG_SUM policy requires numeric type, but got: \" + newReply.getClass().getName());\n          }\n          delegate = (Aggregator<T, T>) new SumAggregator<>(); // safe cast\n          break;\n        case AGG_MIN:\n          delegate = new MinAggregator<>();\n          break;\n        case AGG_MAX:\n          delegate = new MaxAggregator<>();\n          break;\n        case AGG_LOGICAL_AND:\n          delegate = new LogicalAndAggregator<>();\n          break;\n        case AGG_LOGICAL_OR:\n          delegate = new LogicalOrAggregator<>();\n          break;\n        case DEFAULT:\n          delegate = new DefaultPolicyAggregator<>();\n          break;\n        case ALL_SUCCEEDED:\n          delegate = new FirstNonNullAggregator<>();\n          break;\n        case ONE_SUCCEEDED:\n          delegate = new FirstNonNullAggregator<>();\n          break;\n        default:\n          delegate = new FirstNonNullAggregator<>();\n          break;\n      }\n\n    }\n\n    // Delegate the addition\n    delegate.add(newReply);\n  }\n\n  /**\n   * Returns the aggregated result so far. Returns null if no meaningful value has been added.\n   */\n  @Override\n  public T getResult() {\n    return delegate == null ? null : delegate.getResult();\n  }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/DefaultPolicyAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\nimport redis.clients.jedis.util.JedisByteHashMap;\nimport redis.clients.jedis.util.JedisByteMap;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Aggregator for DEFAULT policy. Lazily creates a delegate aggregator based on the first non-null\n * sample value. All subsequent additions are delegated to the same aggregator. If all values are\n * null, getResult() returns null.\n */\nclass DefaultPolicyAggregator<T> implements Aggregator<T, T> {\n\n  private Aggregator<T, T> delegate;\n\n  @Override\n  public void add(T sample) {\n    if (sample == null) {\n      return; // ignore nulls\n    }\n\n    // Lazy initialization of delegate aggregator\n    if (delegate == null) {\n      delegate = createDelegateAggregator(sample);\n    }\n\n    delegate.add(sample);\n  }\n\n  @Override\n  public T getResult() {\n    return delegate == null ? null : delegate.getResult();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private Aggregator<T, T> createDelegateAggregator(T sample) {\n    Objects.requireNonNull(sample, \"Sample value must not be null\");\n\n    if (sample instanceof List) {\n      return (Aggregator<T, T>) new ListAggregator<>();\n    }\n\n    if (sample instanceof Set) {\n      return (Aggregator<T, T>) new SetAggregator<>();\n    }\n\n    if (sample instanceof JedisByteHashMap) {\n      return (Aggregator<T, T>) new JedisByteHashMapAggregator();\n    }\n\n    if (sample instanceof JedisByteMap) {\n      return (Aggregator<T, T>) new JedisByteMapAggregator<>();\n    }\n\n    if (sample instanceof Map) {\n      return (Aggregator<T, T>) new MapAggregator<>();\n    }\n\n    throw new UnsupportedAggregationException(\n        \"DEFAULT policy requires List, Set, Map, JedisByteHashMap, or JedisByteMap types, but got: \"\n            + sample.getClass().getName());\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/FirstNonNullAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\n/**\n * Aggregator that returns the first non-null value added. Subsequent additions are ignored.\n */\nclass FirstNonNullAggregator<T> implements Aggregator<T, T> {\n\n  private T value;\n\n  @Override\n  public void add(T input) {\n    if (value == null && input != null) {\n      value = input;\n    }\n  }\n\n  @Override\n  public T getResult() {\n    return value;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/JedisByteHashMapAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.util.JedisByteHashMap;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass JedisByteHashMapAggregator implements Aggregator<JedisByteHashMap, JedisByteHashMap> {\n\n  // Parts stores references to the maps added to the aggregator.\n  // Defines the initial capacity of the list holding the parts.\n  // Hard to come up with a reasonable default.\n  // Start with 3 as min redis cluster has 3 masters.\n  private static final int INITIAL_CAPACITY = 3;\n\n  private List<JedisByteHashMap> parts;\n\n  @Override\n  public void add(JedisByteHashMap map) {\n    if (map == null) {\n      return;\n    }\n\n    if (parts == null) {\n      parts = new ArrayList<>(INITIAL_CAPACITY);\n    }\n\n    parts.add(map);\n  }\n\n  @Override\n  public JedisByteHashMap getResult() {\n    if (parts == null) {\n      return null;\n    }\n\n    // Fast path: only one map added → return it\n    if (parts.size() == 1) {\n      return parts.get(0);\n    }\n\n    JedisByteHashMap result = new JedisByteHashMap();\n\n    for (JedisByteHashMap part : parts) {\n      result.putAll(part);\n    }\n\n    return result;\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/JedisByteMapAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.util.JedisByteMap;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass JedisByteMapAggregator<T> implements Aggregator<JedisByteMap<T>, JedisByteMap<T>> {\n\n  // Parts stores references to the maps added to the aggregator.\n  // Defines the initial capacity of the list holding the parts.\n  // Hard to come up with a reasonable default.\n  // Start with 3 as min redis cluster has 3 masters.\n  private static final int INITIAL_CAPACITY = 3;\n\n  private List<JedisByteMap<T>> parts;\n\n  @Override\n  public void add(JedisByteMap<T> map) {\n    if (map == null) {\n      return;\n    }\n\n    if (parts == null) {\n      parts = new ArrayList<>(INITIAL_CAPACITY);\n    }\n\n    parts.add(map);\n  }\n\n  @Override\n  public JedisByteMap<T> getResult() {\n    if (parts == null) {\n      return null;\n    }\n\n    // Fast path: only one non-null map\n    if (parts.size() == 1) {\n      return parts.get(0);\n    }\n\n    JedisByteMap<T> result = new JedisByteMap<>();\n\n    for (JedisByteMap<T> part : parts) {\n      result.putAll(part);\n    }\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/ListAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass ListAggregator<T> implements Aggregator<List<T>, List<T>> {\n\n  // Parts stores references to the maps added to the aggregator.\n  // Defines the initial capacity of the list holding the parts.\n  // Hard to come up with a reasonable default.\n  // Start with 3 as min redis cluster has 3 masters.\n  private static final int INITIAL_CAPACITY = 3;\n\n  private List<List<T>> parts;\n  private int totalSize;\n\n  @Override\n  public void add(List<T> list) {\n\n    if (list == null) {\n      return;\n    }\n\n    if (parts == null) {\n      parts = new ArrayList<>(INITIAL_CAPACITY);\n    }\n\n    parts.add(list);\n    totalSize += list.size();\n  }\n\n  @Override\n  public List<T> getResult() {\n\n    if (parts == null) {\n      return null;\n    }\n\n    if (parts.size() == 1) {\n      return parts.get(0);\n    }\n\n    List<T> result = new ArrayList<>(totalSize);\n\n    for (List<T> part : parts) {\n      result.addAll(part);\n    }\n\n    return result;\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/LogicalAndAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\n/**\n * Aggregator that performs logical AND on added values. Supports Boolean, Long,\n * ArrayList&lt;Boolean&gt;, ArrayList&lt;Long&gt;.\n */\nclass LogicalAndAggregator<T> extends LogicalBinaryAggregator<T> {\n\n  LogicalAndAggregator() {\n    super(\"AGG_LOGICAL_AND\");\n  }\n\n  @Override\n  protected boolean applyBooleanOp(boolean a, boolean b) {\n    return a && b;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/LogicalBinaryAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\nimport java.util.ArrayList;\n\n/**\n * Abstract base class for logical binary operations (AND, OR). Supports Boolean, Long,\n * ArrayList<Boolean>, ArrayList<Long>.\n */\nabstract class LogicalBinaryAggregator<T> implements Aggregator<T, T> {\n\n  private T result;\n  private final String operationName;\n\n  protected LogicalBinaryAggregator(String operationName) {\n    this.operationName = operationName;\n  }\n\n  @Override\n  public void add(T input) {\n    if (input == null) {\n      return; // ignore nulls\n    }\n\n    if (result == null) {\n      // First non-null input initializes the result\n      if (input instanceof Boolean || input instanceof Long || input instanceof ArrayList) {\n        result = input;\n        return;\n      } else {\n        throw new UnsupportedAggregationException(operationName\n            + \" requires Boolean, Long, ArrayList<Boolean>, or ArrayList<Long>, but got: \"\n            + input.getClass().getName());\n      }\n    }\n\n    // Handle Boolean\n    if (result instanceof Boolean && input instanceof Boolean) {\n      result = (T) Boolean.valueOf(applyBooleanOp((Boolean) result, (Boolean) input));\n      return;\n    }\n\n    // Handle Long\n    if (result instanceof Long && input instanceof Long) {\n      boolean existingBool = (Long) result != 0;\n      boolean newBool = (Long) input != 0;\n      result = (T) Long.valueOf(applyBooleanOp(existingBool, newBool) ? 1L : 0L);\n      return;\n    }\n\n    // Handle ArrayList\n    if (result instanceof ArrayList && input instanceof ArrayList) {\n      ArrayList<?> existingList = (ArrayList<?>) result;\n      ArrayList<?> newList = (ArrayList<?>) input;\n\n      if (existingList.size() != newList.size()) {\n        throw new UnsupportedAggregationException(\n            operationName + \" requires ArrayLists of equal size, but got sizes: \"\n                + existingList.size() + \" and \" + newList.size());\n      }\n\n      if (!existingList.isEmpty()) {\n        Object firstExisting = existingList.get(0);\n        Object firstNew = newList.get(0);\n\n        // ArrayList<Boolean>\n        if (firstExisting instanceof Boolean && firstNew instanceof Boolean) {\n          ArrayList<Boolean> res = new ArrayList<>(existingList.size());\n          for (int i = 0; i < existingList.size(); i++) {\n            res.add(applyBooleanOp((Boolean) existingList.get(i), (Boolean) newList.get(i)));\n          }\n          result = (T) res;\n          return;\n        }\n\n        // ArrayList<Long> treated as boolean\n        if (firstExisting instanceof Long && firstNew instanceof Long) {\n          ArrayList<Long> res = new ArrayList<>(existingList.size());\n          for (int i = 0; i < existingList.size(); i++) {\n            boolean e = ((Long) existingList.get(i)) != 0;\n            boolean n = ((Long) newList.get(i)) != 0;\n            res.add(applyBooleanOp(e, n) ? 1L : 0L);\n          }\n          result = (T) res;\n          return;\n        }\n      } else {\n        // Empty lists → result remains empty list\n        result = (T) new ArrayList<>();\n        return;\n      }\n    }\n\n    throw new UnsupportedAggregationException(\n        operationName + \" requires Boolean, Long, ArrayList<Boolean>, or ArrayList<Long>, but got: \"\n            + result.getClass().getName() + \" and \" + input.getClass().getName());\n  }\n\n  @Override\n  public T getResult() {\n    return result;\n  }\n\n  /**\n   * Template method for subclasses to implement the specific logical operation.\n   * @param a first boolean operand\n   * @param b second boolean operand\n   * @return result of the logical operation\n   */\n  protected abstract boolean applyBooleanOp(boolean a, boolean b);\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/LogicalOrAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\n/**\n * Aggregator that performs logical OR on added values. Supports Boolean, Long,\n * ArrayList&lt;Boolean&gt;, ArrayList&lt;Long&gt;.\n */\nclass LogicalOrAggregator<T> extends LogicalBinaryAggregator<T> {\n\n  LogicalOrAggregator() {\n    super(\"AGG_LOGICAL_OR\");\n  }\n\n  @Override\n  protected boolean applyBooleanOp(boolean a, boolean b) {\n    return a || b;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/MapAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nclass MapAggregator<K, V> implements Aggregator<Map<K, V>, Map<K, V>> {\n\n  // Parts stores references to the maps added to the aggregator.\n  // Defines the initial capacity of the list holding the parts.\n  // Hard to come up with a reasonable default.\n  // Start with 3 as min redis cluster has 3 masters.\n  private static final int INITIAL_CAPACITY = 3;\n\n  private List<Map<K, V>> parts;\n  private int totalSize;\n\n  @Override\n  public void add(Map<K, V> map) {\n\n    if (map == null) {\n      return;\n    }\n\n    if (parts == null) {\n      parts = new ArrayList<>(INITIAL_CAPACITY);\n    }\n\n    parts.add(map);\n    totalSize += map.size();\n  }\n\n  @Override\n  public Map<K, V> getResult() {\n\n    if (parts == null) {\n      return null;\n    }\n\n    if (parts.size() == 1) {\n      return parts.get(0);\n    }\n\n    Map<K, V> result = new HashMap<>(totalSize);\n\n    for (Map<K, V> part : parts) {\n      result.putAll(part); // last write wins\n    }\n\n    return result;\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/MaxAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * Aggregator that returns the maximum value added so far. Supports Comparable types and\n * KeyValue<K,V> where both key and value are Comparable.\n */\nclass MaxAggregator<T> implements Aggregator<T, T> {\n\n  private T max;\n\n  @Override\n  public void add(T input) {\n    if (input == null) {\n      return;\n    }\n\n    if (max == null) {\n      max = input;\n      return;\n    }\n\n    // Handle KeyValue types\n    if (max instanceof KeyValue && input instanceof KeyValue) {\n      max = (T) aggregateKeyValueMax((KeyValue<?, ?>) max, (KeyValue<?, ?>) input);\n      return;\n    }\n\n    // Handle Comparable types\n    if (max instanceof Comparable && input instanceof Comparable) {\n      Comparable<Object> maxComp = (Comparable<Object>) max;\n      if (maxComp.compareTo(input) < 0) {\n        max = input;\n      }\n      return;\n    }\n\n    throw new UnsupportedAggregationException(\n        \"AGG_MAX policy requires Comparable types or KeyValue, but got: \" + max.getClass().getName()\n            + \" and \" + input.getClass().getName());\n  }\n\n  @Override\n  public T getResult() {\n    return max;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private KeyValue<?, ?> aggregateKeyValueMax(KeyValue<?, ?> kv1, KeyValue<?, ?> kv2) {\n    Object maxKey = ((Comparable<Object>) kv1.getKey()).compareTo(kv2.getKey()) >= 0 ? kv1.getKey()\n        : kv2.getKey();\n    Object maxValue = ((Comparable<Object>) kv1.getValue()).compareTo(kv2.getValue()) >= 0\n        ? kv1.getValue()\n        : kv2.getValue();\n    return new KeyValue<>(maxKey, maxValue);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/MinAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * Aggregator that returns the minimum value added so far. Supports Comparable types and\n * KeyValue<K,V> where both key and value are Comparable.\n */\nclass MinAggregator<T> implements Aggregator<T, T> {\n\n  private T min;\n\n  @Override\n  public void add(T input) {\n    if (input == null) {\n      return;\n    }\n\n    if (min == null) {\n      min = input;\n      return;\n    }\n\n    // Handle KeyValue types\n    if (min instanceof KeyValue && input instanceof KeyValue) {\n      min = (T) aggregateKeyValueMin((KeyValue<?, ?>) min, (KeyValue<?, ?>) input);\n      return;\n    }\n\n    // Handle Comparable types\n    if (min instanceof Comparable && input instanceof Comparable) {\n      Comparable<Object> minComp = (Comparable<Object>) min;\n      if (minComp.compareTo(input) > 0) {\n        min = input;\n      }\n      return;\n    }\n\n    throw new UnsupportedAggregationException(\n        \"AGG_MIN policy requires Comparable types or KeyValue, but got: \" + min.getClass().getName()\n            + \" and \" + input.getClass().getName());\n  }\n\n  @Override\n  public T getResult() {\n    return min;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private KeyValue<?, ?> aggregateKeyValueMin(KeyValue<?, ?> kv1, KeyValue<?, ?> kv2) {\n    Object minKey = ((Comparable<Object>) kv1.getKey()).compareTo(kv2.getKey()) <= 0 ? kv1.getKey()\n        : kv2.getKey();\n    Object minValue = ((Comparable<Object>) kv1.getValue()).compareTo(kv2.getValue()) <= 0\n        ? kv1.getValue()\n        : kv2.getValue();\n    return new KeyValue<>(minKey, minValue);\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/MultiNodeResultAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.annots.Internal;\nimport redis.clients.jedis.exceptions.JedisBroadcastException;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\n\n/**\n * Internal use only. Aggregates results from multiple node/shard executions based on response\n * policy.\n * <p>\n * This class centralizes the logic for collecting results and errors from multi-node command\n * execution (broadcast commands, multi-shard commands) and determining the final result based on\n * the {@link CommandFlagsRegistry.ResponsePolicy}.\n * <p>\n * Key behavior differences by policy:\n * <ul>\n * <li>{@code ONE_SUCCEEDED}: Returns success if at least one node succeeds, even if others fail.\n * Only throws if ALL nodes fail.</li>\n * <li>All other policies: Throws {@link JedisBroadcastException} if ANY node fails.</li>\n * </ul>\n * @param <T> the type of the command result\n */\n@Internal\npublic final class MultiNodeResultAggregator<T> {\n\n  private static final HostAndPort UNKNOWN_NODE = HostAndPort.from(\"unknown:0\");\n\n  private final CommandFlagsRegistry.ResponsePolicy responsePolicy;\n  private final JedisBroadcastException bcastError;\n  private final Aggregator<T, T> replyAggregator;\n  private boolean hasError;\n  private boolean hasSuccess;\n\n  /**\n   * Creates a new aggregator with the specified response policy.\n   * @param responsePolicy the policy that determines how to aggregate results and handle errors\n   */\n  public MultiNodeResultAggregator(CommandFlagsRegistry.ResponsePolicy responsePolicy) {\n    this.responsePolicy = responsePolicy;\n    this.replyAggregator = new ClusterReplyAggregator<>(responsePolicy);\n    this.bcastError = new JedisBroadcastException();\n    this.hasError = false;\n    this.hasSuccess = false;\n  }\n\n  /**\n   * Records a successful result from a node.\n   * @param node the node that returned the result\n   * @param result the result from the node\n   */\n  public void addSuccess(HostAndPort node, T result) {\n    if (node != null) {\n      bcastError.addReply(node, result);\n    }\n    aggregateSuccess(result);\n  }\n\n  /**\n   * Records a successful result without node information.\n   * <p>\n   * Use this method when the node information is not readily available, such as in multi-shard\n   * commands where extracting the node would require additional computation.\n   * @param result the result from the operation\n   */\n  public void addSuccess(T result) {\n    aggregateSuccess(result);\n  }\n\n  /**\n   * Aggregates a successful result into the accumulated reply.\n   * @param result the result to aggregate\n   */\n  private void aggregateSuccess(T result) {\n    hasSuccess = true;\n\n    // Always aggregate successful results, even if we've seen errors\n    // This is important for ONE_SUCCEEDED policy where we need to return\n    // a successful result even if some nodes failed\n    replyAggregator.add(result);\n  }\n\n  /**\n   * Records an error from a node.\n   * @param node the node that returned the error\n   * @param error the exception from the node\n   */\n  public void addError(HostAndPort node, Exception error) {\n    bcastError.addReply(node, error);\n    hasError = true;\n  }\n\n  /**\n   * Records an error, extracting the node information from the exception if available.\n   * <p>\n   * This method extracts node information from {@link JedisClusterOperationException} if present,\n   * otherwise uses a placeholder \"unknown:0\" node.\n   * @param error the exception from the failed operation\n   */\n  public void addError(Exception error) {\n    HostAndPort node = extractNodeFromException(error);\n    addError(node, error);\n  }\n\n  /**\n   * Extracts the node information from an exception if available.\n   * @param error the exception to extract node info from\n   * @return the node that caused the error, or a placeholder if unknown\n   */\n  private static HostAndPort extractNodeFromException(Exception error) {\n    if (error instanceof JedisClusterOperationException) {\n      HostAndPort node = ((JedisClusterOperationException) error).getNode();\n      if (node != null) {\n        return node;\n      }\n    }\n    return UNKNOWN_NODE;\n  }\n\n  /**\n   * Returns the aggregated result based on the response policy.\n   * <p>\n   * For {@code ONE_SUCCEEDED} policy: returns the aggregated result if at least one node succeeded,\n   * throws {@link JedisBroadcastException} only if all nodes failed.\n   * <p>\n   * For all other policies: throws {@link JedisBroadcastException} if any node failed, otherwise\n   * returns the aggregated result.\n   * @return the aggregated result\n   * @throws JedisBroadcastException if the policy criteria are not met\n   */\n  public T getResult() {\n    if (responsePolicy == CommandFlagsRegistry.ResponsePolicy.ONE_SUCCEEDED) {\n      // ONE_SUCCEEDED: return success if at least one node succeeded\n      if (hasSuccess) {\n        return replyAggregator.getResult();\n      }\n      // All nodes failed\n      throw bcastError.prepareToThrow();\n    } else {\n      // All other policies: throw if any node failed\n      if (hasError) {\n        throw bcastError.prepareToThrow();\n      }\n      return replyAggregator.getResult();\n    }\n  }\n\n  /**\n   * Returns the response policy being used by this aggregator.\n   * @return the response policy\n   */\n  public CommandFlagsRegistry.ResponsePolicy getResponsePolicy() {\n    return responsePolicy;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/SetAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nclass SetAggregator<T> implements Aggregator<Set<T>, Set<T>> {\n\n  // Parts stores references to the maps added to the aggregator.\n  // Defines the initial capacity of the list holding the parts.\n  // Hard to come up with a reasonable default.\n  // Start with 3 as min redis cluster has 3 masters.\n  private static final int INITIAL_CAPACITY = 3;\n\n  private List<Set<T>> parts;\n  private int totalSize;\n\n  @Override\n  public void add(Set<T> set) {\n\n    if (set == null) {\n      return;\n    }\n\n    if (parts == null) {\n      parts = new ArrayList<>(INITIAL_CAPACITY);\n    }\n\n    parts.add(set);\n    totalSize += set.size();\n  }\n\n  @Override\n  public Set<T> getResult() {\n\n    if (parts == null) {\n      return null;\n    }\n\n    if (parts.size() == 1) {\n      return parts.get(0);\n    }\n\n    // Check if we're dealing with Set<byte[]> - need special handling for proper deduplication\n    for (Set<T> set : parts) {\n      if (!set.isEmpty()) {\n        Object firstElement = set.iterator().next();\n        if (firstElement instanceof byte[]) {\n          return mergeByteArraySets();\n        }\n        break;\n      }\n    }\n\n    Set<T> result = new HashSet<>(totalSize);\n\n    for (Set<T> part : parts) {\n      result.addAll(part);\n    }\n\n    return result;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private Set<T> mergeByteArraySets() {\n    // Wrap byte arrays for proper equals/hashCode, deduplicate, then unwrap\n    Set<ByteArrayWrapper> wrappedSet = new HashSet<>(totalSize);\n\n    for (Set<T> part : parts) {\n      for (T element : part) {\n        wrappedSet.add(new ByteArrayWrapper((byte[]) element));\n      }\n    }\n\n    // Unwrap back to byte[]\n    Set<Object> result = new HashSet<>(wrappedSet.size());\n    for (ByteArrayWrapper wrapper : wrappedSet) {\n      result.add(wrapper.unwrap());\n    }\n\n    return (Set<T>) result;\n  }\n\n  static final class ByteArrayWrapper {\n    private final byte[] data;\n\n    ByteArrayWrapper(byte[] data) {\n      this.data = data;\n    }\n\n    byte[] unwrap() {\n      return data;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (!(o instanceof ByteArrayWrapper)) {\n        return false;\n      }\n      return Arrays.equals(data, ((ByteArrayWrapper) o).data);\n    }\n\n    @Override\n    public int hashCode() {\n      return Arrays.hashCode(data);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/aggregators/SumAggregator.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\n\n/**\n * Aggregator that sums numeric values of the same type. Null inputs are ignored; if all inputs are\n * null, the result is null. Supports Integer, Long, and Double.\n */\nclass SumAggregator<T extends Number> implements Aggregator<T, T> {\n\n  private T sum;\n\n  @Override\n  public void add(T value) {\n    if (value == null) {\n      return;\n    }\n\n    if (sum == null) {\n      sum = value;\n      return;\n    }\n\n    if (sum instanceof Long && value instanceof Long) {\n      sum = (T) Long.valueOf(sum.longValue() + value.longValue());\n    } else if (sum instanceof Integer && value instanceof Integer) {\n      sum = (T) Integer.valueOf(sum.intValue() + value.intValue());\n    } else if (sum instanceof Double && value instanceof Double) {\n      sum = (T) Double.valueOf(sum.doubleValue() + value.doubleValue());\n    } else {\n      throw new UnsupportedAggregationException(\n          \"SumAggregator requires numeric types of the same kind (Integer, Long, Double), but got: \"\n              + sum.getClass().getSimpleName() + \" and \" + value.getClass().getSimpleName());\n    }\n  }\n\n  @Override\n  public T getResult() {\n    return sum;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/executors/package-info.java",
    "content": "/**\n * This package contains the implementations of CommandExecutor interface.\n */\npackage redis.clients.jedis.executors;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/DefaultGsonObjectMapper.java",
    "content": "package redis.clients.jedis.json;\n\nimport com.google.gson.Gson;\n\n/**\n * Use the default {@link Gson} configuration for serialization and deserialization JSON\n * operations.\n * <p>When none is explicitly set, this will be set.</p>\n * @see JsonObjectMapper Create a custom JSON serializer/deserializer\n */\npublic class DefaultGsonObjectMapper implements JsonObjectMapper {\n  /**\n   * Instance of Gson object with default gson configuration.\n   */\n  private final Gson gson = new Gson();\n\n  @Override\n  public <T> T fromJson(String value, Class<T> valueType) {\n    return gson.fromJson(value, valueType);\n  }\n\n  @Override\n  public String toJson(Object value) {\n    return gson.toJson(value);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/JsonBuilderFactory.java",
    "content": "package redis.clients.jedis.json;\n\nimport static redis.clients.jedis.BuilderFactory.STRING;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.exceptions.JedisException;\n\npublic final class JsonBuilderFactory {\n\n  public static final Builder<Class<?>> JSON_TYPE = new Builder<Class<?>>() {\n    @Override\n    public Class<?> build(Object data) {\n      if (data == null) return null;\n      String str = STRING.build(data);\n      switch (str) {\n        case \"null\":\n          return null;\n        case \"boolean\":\n          return boolean.class;\n        case \"integer\":\n          return int.class;\n        case \"number\":\n          return float.class;\n        case \"string\":\n          return String.class;\n        case \"object\":\n          return Object.class;\n        case \"array\":\n          return List.class;\n        default:\n          throw new JedisException(\"Unknown type: \" + str);\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"Class<?>\";\n    }\n  };\n\n  public static final Builder<List<Class<?>>> JSON_TYPE_LIST = new Builder<List<Class<?>>>() {\n    @Override\n    public List<Class<?>> build(Object data) {\n      List<Object> list = (List<Object>) data;\n      List<Class<?>> classes = new ArrayList<>(list.size());\n      for (Object elem : list) {\n        try {\n          classes.add(JSON_TYPE.build(elem));\n        } catch (JedisException je) {\n          classes.add(null);\n        }\n      }\n      return classes;\n    }\n  };\n\n  public static final Builder<List<List<Class<?>>>> JSON_TYPE_RESPONSE_RESP3 = new Builder<List<List<Class<?>>>>() {\n    @Override\n    public List<List<Class<?>>> build(Object data) {\n      return ((List<Object>) data).stream().map(JSON_TYPE_LIST::build).collect(Collectors.toList());\n    }\n  };\n\n  public static final Builder<List<Class<?>>> JSON_TYPE_RESPONSE_RESP3_COMPATIBLE = new Builder<List<Class<?>>>() {\n    @Override\n    public List<Class<?>> build(Object data) {\n      List<List<Class<?>>> fullReply = JSON_TYPE_RESPONSE_RESP3.build(data);\n      return fullReply == null ? null : fullReply.get(0);\n    }\n  };\n\n  public static final Builder<Object> JSON_OBJECT = new Builder<Object>() {\n    @Override\n    public Object build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      if (!(data instanceof byte[])) {\n        return data;\n      }\n      String str = STRING.build(data);\n      if (str.charAt(0) == '{') {\n        try {\n          return new JSONObject(str);\n        } catch (Exception ex) {\n        }\n      } else if (str.charAt(0) == '[') {\n        try {\n          return new JSONArray(str);\n        } catch (Exception ex) {\n        }\n      }\n      return str;\n    }\n  };\n\n  public static final Builder<JSONArray> JSON_ARRAY = new Builder<JSONArray>() {\n    @Override\n    public JSONArray build(Object data) {\n      if (data == null) {\n        return null;\n      }\n      String str = STRING.build(data);\n      try {\n        return new JSONArray(str);\n      } catch (JSONException ex) { // This is not necessary but we are doing this\n        // just to make it safe for com.vaadin.external.google:android-json library\n        throw new JedisException(ex);\n      }\n    }\n  };\n\n  public static final Builder<Object> JSON_ARRAY_OR_DOUBLE_LIST = new Builder<Object>() {\n    @Override\n    public Object build(Object data) {\n      if (data == null) return null;\n      if (data instanceof List) return BuilderFactory.DOUBLE_LIST.build(data);\n      return JSON_ARRAY.build(data);\n    }\n  };\n\n  public static final Builder<List<JSONArray>> JSON_ARRAY_LIST = new Builder<List<JSONArray>>() {\n    @Override\n    public List<JSONArray> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n      List<Object> list = (List<Object>) data;\n      return list.stream().map(o -> JSON_ARRAY.build(o)).collect(Collectors.toList());\n    }\n  };\n\n  private JsonBuilderFactory() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/JsonObjectMapper.java",
    "content": "package redis.clients.jedis.json;\n\n/**\n * Represents the ability of serialize an object to JSON format string and deserialize it to the\n * typed object.\n * @see DefaultGsonObjectMapper Default implementation for <em>JSON serializer/deserializer</em>\n *     engine with com.google.gson.Gson\n */\npublic interface JsonObjectMapper {\n  /**\n   * Perform deserialization from JSON format string to the given type object as argument.\n   * @param value     the JSON format\n   * @param valueType the object type to convert\n   * @param <T>       the type object to convert\n   * @return the instance of an object to the type given argument\n   */\n  <T> T fromJson(String value, Class<T> valueType);\n\n  /**\n   * Perform serialization from object to JSON format string.\n   * @param value the object to convert\n   * @return the JSON format string\n   */\n  String toJson(Object value);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/JsonProtocol.java",
    "content": "package redis.clients.jedis.json;\n\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class JsonProtocol {\n\n  public enum JsonCommand implements ProtocolCommand {\n    DEL(\"JSON.DEL\"),\n    GET(\"JSON.GET\"),\n    MGET(\"JSON.MGET\"),\n    MERGE(\"JSON.MERGE\"),\n    SET(\"JSON.SET\"),\n    TYPE(\"JSON.TYPE\"),\n    STRAPPEND(\"JSON.STRAPPEND\"),\n    STRLEN(\"JSON.STRLEN\"),\n    NUMINCRBY(\"JSON.NUMINCRBY\"),\n    ARRAPPEND(\"JSON.ARRAPPEND\"),\n    ARRINDEX(\"JSON.ARRINDEX\"),\n    ARRINSERT(\"JSON.ARRINSERT\"),\n    ARRLEN(\"JSON.ARRLEN\"),\n    ARRPOP(\"JSON.ARRPOP\"),\n    ARRTRIM(\"JSON.ARRTRIM\"),\n    CLEAR(\"JSON.CLEAR\"),\n    TOGGLE(\"JSON.TOGGLE\"),\n    OBJKEYS(\"JSON.OBJKEYS\"),\n    OBJLEN(\"JSON.OBJLEN\"),\n    DEBUG(\"JSON.DEBUG\"),\n    RESP(\"JSON.RESP\");\n\n    private final byte[] raw;\n\n    private JsonCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/JsonSetParams.java",
    "content": "package redis.clients.jedis.json;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\npublic class JsonSetParams implements IParams {\n\n  private boolean nx = false;\n  private boolean xx = false;\n\n  public JsonSetParams() {\n  }\n\n  public static JsonSetParams jsonSetParams() {\n    return new JsonSetParams();\n  }\n\n  public JsonSetParams nx() {\n    this.nx = true;\n    this.xx = false;\n    return this;\n  }\n\n  public JsonSetParams xx() {\n    this.nx = false;\n    this.xx = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (nx) {\n      args.add(\"NX\");\n    }\n    if (xx) {\n      args.add(\"XX\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/Path.java",
    "content": "package redis.clients.jedis.json;\n\n/**\n * Path is a RedisJSON (v1) path, representing a valid path into an object.\n * @deprecated RedisJSON (v1) support is deprecated.\n */\n@Deprecated\npublic class Path {\n\n  public static final Path ROOT_PATH = new Path(\".\");\n\n  private final String strPath;\n\n  public Path(final String strPath) {\n    this.strPath = strPath;\n  }\n\n  @Override\n  public String toString() {\n    return strPath;\n  }\n\n  public static Path of(final String strPath) {\n    return new Path(strPath);\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null) {\n      return false;\n    }\n    if (!(obj instanceof Path)) {\n      return false;\n    }\n    if (obj == this) {\n      return true;\n    }\n    return this.toString().equals(((Path) obj).toString());\n  }\n\n  @Override\n  public int hashCode() {\n    return strPath.hashCode();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/Path2.java",
    "content": "package redis.clients.jedis.json;\n\n/**\n * Path is a RedisJSON v2 path, representing a valid path or a multi-path into an object.\n */\npublic class Path2 {\n\n  public static final Path2 ROOT_PATH = new Path2(\"$\");\n\n  private final String str;\n\n  public Path2(final String str) {\n    if (str == null) {\n      throw new NullPointerException(\"Path cannot be null.\");\n    }\n    if (str.isEmpty()) {\n      throw new IllegalArgumentException(\"Path cannot be empty.\");\n    }\n    if (str.charAt(0) == '$') {\n      this.str = str;\n    } else if (str.charAt(0) == '.') {\n      this.str = '$' + str;\n    } else {\n      this.str = \"$.\" + str;\n    }\n  }\n\n  @Override\n  public String toString() {\n    return str;\n  }\n\n  public static Path2 of(final String path) {\n    return new Path2(path);\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null) {\n      return false;\n    }\n    if (!(obj instanceof Path2)) {\n      return false;\n    }\n    if (obj == this) {\n      return true;\n    }\n    return this.toString().equals(((Path2) obj).toString());\n  }\n\n  @Override\n  public int hashCode() {\n    return str.hashCode();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/commands/RedisJsonCommands.java",
    "content": "package redis.clients.jedis.json.commands;\n\npublic interface RedisJsonCommands extends RedisJsonV1Commands, RedisJsonV2Commands {\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/commands/RedisJsonPipelineCommands.java",
    "content": "package redis.clients.jedis.json.commands;\n\npublic interface RedisJsonPipelineCommands extends RedisJsonV1PipelineCommands, RedisJsonV2PipelineCommands {\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/commands/RedisJsonV1Commands.java",
    "content": "package redis.clients.jedis.json.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\n\n/**\n * @deprecated RedisJSON (v1) support is deprecated.\n */\n@Deprecated\npublic interface RedisJsonV1Commands {\n\n  @Deprecated\n  default String jsonSetLegacy(String key, Object pojo) {\n    return jsonSet(key, Path.ROOT_PATH, pojo);\n  }\n\n  @Deprecated\n  default String jsonSetLegacy(String key, Object pojo, JsonSetParams params) {\n    return jsonSet(key, Path.ROOT_PATH, pojo, params);\n  }\n\n  @Deprecated\n  String jsonSet(String key, Path path, Object pojo);\n\n  @Deprecated\n  String jsonSetWithPlainString(String key, Path path, String string);\n\n  @Deprecated\n  String jsonSet(String key, Path path, Object pojo, JsonSetParams params);\n\n  @Deprecated\n  String jsonMerge(String key, Path path, Object pojo);\n\n  Object jsonGet(String key); // both ver\n\n  @Deprecated\n  <T> T jsonGet(String key, Class<T> clazz);\n\n  @Deprecated\n  Object jsonGet(String key, Path... paths);\n\n  @Deprecated\n  String jsonGetAsPlainString(String key, Path path);\n\n  @Deprecated\n  <T> T jsonGet(String key, Class<T> clazz, Path... paths);\n\n  @Deprecated\n  default <T> List<T> jsonMGet(Class<T> clazz, String... keys) {\n    return jsonMGet(Path.ROOT_PATH, clazz, keys);\n  }\n\n  @Deprecated\n  <T> List<T> jsonMGet(Path path, Class<T> clazz, String... keys);\n\n  long jsonDel(String key); // both ver\n\n  @Deprecated\n  long jsonDel(String key, Path path);\n\n  long jsonClear(String key); // no test\n\n  @Deprecated\n  long jsonClear(String key, Path path);\n\n  @Deprecated\n  String jsonToggle(String key, Path path);\n\n  @Deprecated\n  Class<?> jsonType(String key);\n\n  @Deprecated\n  Class<?> jsonType(String key, Path path);\n\n  @Deprecated\n  long jsonStrAppend(String key, Object string);\n\n  @Deprecated\n  long jsonStrAppend(String key, Path path, Object string);\n\n  @Deprecated\n  Long jsonStrLen(String key);\n\n  @Deprecated\n  Long jsonStrLen(String key, Path path);\n\n  @Deprecated\n  double jsonNumIncrBy(String key, Path path, double value);\n\n  @Deprecated\n  Long jsonArrAppend(String key, Path path, Object... pojos);\n\n  @Deprecated\n  long jsonArrIndex(String key, Path path, Object scalar);\n\n  @Deprecated\n  long jsonArrInsert(String key, Path path, int index, Object... pojos);\n\n  @Deprecated\n  Object jsonArrPop(String key);\n\n  @Deprecated\n  <T> T jsonArrPop(String key, Class<T> clazz);\n\n  @Deprecated\n  Object jsonArrPop(String key, Path path);\n\n  @Deprecated\n  <T> T jsonArrPop(String key, Class<T> clazz, Path path);\n\n  @Deprecated\n  Object jsonArrPop(String key, Path path, int index);\n\n  @Deprecated\n  <T> T jsonArrPop(String key, Class<T> clazz, Path path, int index);\n\n  @Deprecated\n  Long jsonArrLen(String key);\n\n  @Deprecated\n  Long jsonArrLen(String key, Path path);\n\n  @Deprecated\n  Long jsonArrTrim(String key, Path path, int start, int stop);\n\n  @Deprecated\n  Long jsonObjLen(String key);\n\n  @Deprecated\n  Long jsonObjLen(String key, Path path);\n\n  @Deprecated\n  List<String> jsonObjKeys(String key);\n\n  @Deprecated\n  List<String> jsonObjKeys(String key, Path path);\n\n  @Deprecated\n  long jsonDebugMemory(String key);\n\n  @Deprecated\n  long jsonDebugMemory(String key, Path path);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/commands/RedisJsonV1PipelineCommands.java",
    "content": "package redis.clients.jedis.json.commands;\n\nimport java.util.List;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\n\n/**\n * @deprecated RedisJSON (v1) support is deprecated.\n */\n@Deprecated\npublic interface RedisJsonV1PipelineCommands {\n\n  @Deprecated\n  default Response<String> jsonSetLegacy(String key, Object pojo) {\n    return jsonSet(key, Path.ROOT_PATH, pojo);\n  }\n\n  @Deprecated\n  default Response<String> jsonSetLegacy(String key, Object pojo, JsonSetParams params) {\n    return jsonSet(key, Path.ROOT_PATH, pojo, params);\n  }\n\n  @Deprecated\n  Response<String> jsonSet(String key, Path path, Object pojo);\n\n  @Deprecated\n  Response<String> jsonSet(String key, Path path, Object pojo, JsonSetParams params);\n\n  @Deprecated\n  Response<String> jsonMerge(String key, Path path, Object pojo);\n\n  Response<Object> jsonGet(String key); // both ver\n\n  @Deprecated\n  <T> Response<T> jsonGet(String key, Class<T> clazz);\n\n  @Deprecated\n  Response<Object> jsonGet(String key, Path... paths);\n\n  @Deprecated\n  <T> Response<T> jsonGet(String key, Class<T> clazz, Path... paths);\n\n  @Deprecated\n  default <T> Response<List<T>> jsonMGet(Class<T> clazz, String... keys) {\n    return jsonMGet(Path.ROOT_PATH, clazz, keys);\n  }\n\n  @Deprecated\n  <T> Response<List<T>> jsonMGet(Path path, Class<T> clazz, String... keys);\n\n  Response<Long> jsonDel(String key); // both ver\n\n  @Deprecated\n  Response<Long> jsonDel(String key, Path path);\n\n  @Deprecated\n  Response<Long> jsonClear(String key); // no test\n\n  @Deprecated\n  Response<Long> jsonClear(String key, Path path);\n\n  @Deprecated\n  Response<String> jsonToggle(String key, Path path);\n\n  @Deprecated\n  Response<Class<?>> jsonType(String key);\n\n  @Deprecated\n  Response<Class<?>> jsonType(String key, Path path);\n\n  @Deprecated\n  Response<Long> jsonStrAppend(String key, Object string);\n\n  @Deprecated\n  Response<Long> jsonStrAppend(String key, Path path, Object string);\n\n  @Deprecated\n  Response<Long> jsonStrLen(String key);\n\n  @Deprecated\n  Response<Long> jsonStrLen(String key, Path path);\n\n  @Deprecated\n  Response<Double> jsonNumIncrBy(String key, Path path, double value);\n\n  @Deprecated\n  Response<Long> jsonArrAppend(String key, Path path, Object... pojos);\n\n  @Deprecated\n  Response<Long> jsonArrIndex(String key, Path path, Object scalar);\n\n  @Deprecated\n  Response<Long> jsonArrInsert(String key, Path path, int index, Object... pojos);\n\n  @Deprecated\n  Response<Object> jsonArrPop(String key);\n\n  @Deprecated\n  <T> Response<T> jsonArrPop(String key, Class<T> clazz);\n\n  @Deprecated\n  Response<Object> jsonArrPop(String key, Path path);\n\n  @Deprecated\n  <T> Response<T> jsonArrPop(String key, Class<T> clazz, Path path);\n\n  @Deprecated\n  Response<Object> jsonArrPop(String key, Path path, int index);\n\n  @Deprecated\n  <T> Response<T> jsonArrPop(String key, Class<T> clazz, Path path, int index);\n\n  @Deprecated\n  Response<Long> jsonArrLen(String key);\n\n  @Deprecated\n  Response<Long> jsonArrLen(String key, Path path);\n\n  @Deprecated\n  Response<Long> jsonArrTrim(String key, Path path, int start, int stop);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/commands/RedisJsonV2Commands.java",
    "content": "package redis.clients.jedis.json.commands;\n\nimport java.util.List;\nimport org.json.JSONArray;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path2;\n\npublic interface RedisJsonV2Commands {\n\n  default String jsonSet(String key, Object object) {\n    return jsonSet(key, Path2.ROOT_PATH, object);\n  }\n\n  default String jsonSetWithEscape(String key, Object object) {\n    return jsonSetWithEscape(key, Path2.ROOT_PATH, object);\n  }\n\n  default String jsonSet(String key, Object object, JsonSetParams params) {\n    return jsonSet(key, Path2.ROOT_PATH, object, params);\n  }\n\n  default String jsonSetWithEscape(String key, Object object, JsonSetParams params) {\n    return jsonSetWithEscape(key, Path2.ROOT_PATH, object, params);\n  }\n\n  String jsonSet(String key, Path2 path, Object object);\n\n  String jsonSetWithEscape(String key, Path2 path, Object object);\n\n  String jsonSet(String key, Path2 path, Object object, JsonSetParams params);\n\n  String jsonSetWithEscape(String key, Path2 path, Object object, JsonSetParams params);\n\n  String jsonMerge(String key, Path2 path, Object object);\n\n  Object jsonGet(String key); // both ver\n\n  Object jsonGet(String key, Path2... paths);\n\n  default List<JSONArray> jsonMGet(String... keys) {\n    return jsonMGet(Path2.ROOT_PATH, keys);\n  }\n\n  List<JSONArray> jsonMGet(Path2 path, String... keys);\n\n  long jsonDel(String key); // both ver\n\n  long jsonDel(String key, Path2 path);\n\n  long jsonClear(String key); // no test\n\n  long jsonClear(String key, Path2 path);\n\n  List<Boolean> jsonToggle(String key, Path2 path);\n\n  List<Class<?>> jsonType(String key, Path2 path);\n\n  List<Long> jsonStrAppend(String key, Path2 path, Object string);\n\n  List<Long> jsonStrLen(String key, Path2 path);\n\n  Object jsonNumIncrBy(String key, Path2 path, double value);\n\n  List<Long> jsonArrAppend(String key, Path2 path, Object... objects);\n\n  List<Long> jsonArrAppendWithEscape(String key, Path2 path, Object... objects);\n\n  List<Long> jsonArrIndex(String key, Path2 path, Object scalar);\n\n  List<Long> jsonArrIndexWithEscape(String key, Path2 path, Object scalar);\n\n  List<Long> jsonArrInsert(String key, Path2 path, int index, Object... objects);\n\n  List<Long> jsonArrInsertWithEscape(String key, Path2 path, int index, Object... objects);\n\n  List<Object> jsonArrPop(String key, Path2 path);\n\n  List<Object> jsonArrPop(String key, Path2 path, int index);\n\n  List<Long> jsonArrLen(String key, Path2 path);\n\n  List<Long> jsonArrTrim(String key, Path2 path, int start, int stop);\n\n  List<Long> jsonObjLen(String key, Path2 path);\n\n  List<List<String>> jsonObjKeys(String key, Path2 path);\n\n  List<Long> jsonDebugMemory(String key, Path2 path);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/commands/RedisJsonV2PipelineCommands.java",
    "content": "package redis.clients.jedis.json.commands;\n\nimport java.util.List;\nimport org.json.JSONArray;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path2;\n\npublic interface RedisJsonV2PipelineCommands {\n\n  default Response<String> jsonSet(String key, Object object) {\n    return jsonSet(key, Path2.ROOT_PATH, object);\n  }\n\n  default Response<String> jsonSetWithEscape(String key, Object object) {\n    return jsonSetWithEscape(key, Path2.ROOT_PATH, object);\n  }\n\n  default Response<String> jsonSet(String key, Object object, JsonSetParams params) {\n    return jsonSet(key, Path2.ROOT_PATH, object, params);\n  }\n\n  default Response<String> jsonSetWithEscape(String key, Object object, JsonSetParams params) {\n    return jsonSetWithEscape(key, Path2.ROOT_PATH, object, params);\n  }\n\n  Response<String> jsonSet(String key, Path2 path, Object object);\n\n  Response<String> jsonSetWithEscape(String key, Path2 path, Object object);\n\n  Response<String> jsonSet(String key, Path2 path, Object object, JsonSetParams params);\n\n  Response<String> jsonSetWithEscape(String key, Path2 path, Object object, JsonSetParams params);\n\n  Response<String> jsonMerge(String key, Path2 path, Object object);\n\n  Response<Object> jsonGet(String key); // both ver\n\n  Response<Object> jsonGet(String key, Path2... paths);\n\n  default Response<List<JSONArray>> jsonMGet(String... keys) {\n    return jsonMGet(Path2.ROOT_PATH, keys);\n  }\n\n  Response<List<JSONArray>> jsonMGet(Path2 path, String... keys);\n\n  Response<Long> jsonDel(String key); // both ver\n\n  Response<Long> jsonDel(String key, Path2 path);\n\n  Response<Long> jsonClear(String key); // no test\n\n  Response<Long> jsonClear(String key, Path2 path);\n\n  Response<List<Boolean>> jsonToggle(String key, Path2 path);\n\n  Response<List<Class<?>>> jsonType(String key, Path2 path);\n\n  Response<List<Long>> jsonStrAppend(String key, Path2 path, Object string);\n\n  Response<List<Long>> jsonStrLen(String key, Path2 path);\n\n  Response<Object> jsonNumIncrBy(String key, Path2 path, double value);\n\n  Response<List<Long>> jsonArrAppend(String key, Path2 path, Object... objects);\n\n  Response<List<Long>> jsonArrAppendWithEscape(String key, Path2 path, Object... objects);\n\n  Response<List<Long>> jsonArrIndex(String key, Path2 path, Object scalar);\n\n  Response<List<Long>> jsonArrIndexWithEscape(String key, Path2 path, Object scalar);\n\n  Response<List<Long>> jsonArrInsert(String key, Path2 path, int index, Object... objects);\n\n  Response<List<Long>> jsonArrInsertWithEscape(String key, Path2 path, int index, Object... objects);\n\n  Response<List<Object>> jsonArrPop(String key, Path2 path);\n\n  Response<List<Object>> jsonArrPop(String key, Path2 path, int index);\n\n  Response<List<Long>> jsonArrLen(String key, Path2 path);\n\n  Response<List<Long>> jsonArrTrim(String key, Path2 path, int start, int stop);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/json/package-info.java",
    "content": "/**\n * This package contains the classes and interfaces related to RedisJSON module.\n */\npackage redis.clients.jedis.json;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/CircuitBreakerThresholdsAdapter.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType;\nimport redis.clients.jedis.MultiDbConfig;\n\n/**\n * Adapter that disables Resilience4j's built-in circuit breaker evaluation and help delegate\n * threshold decisions to Jedis's custom dual-threshold logic.\n * <p>\n * This adapter sets maximum values for failure rate (100%) and minimum calls (Integer.MAX_VALUE) to\n * effectively disable Resilience4j's automatic circuit breaker transitions, allowing\n * {@link MultiDbConnectionProvider.Database#evaluateThresholds(boolean)} to control when the\n * circuit breaker opens based on both minimum failure count AND failure rate.\n * </p>\n * @see MultiDbConnectionProvider.Database#evaluateThresholds(boolean)\n */\nclass CircuitBreakerThresholdsAdapter {\n  /** Maximum failure rate threshold (100%) to disable Resilience4j evaluation */\n  private static final float FAILURE_RATE_THRESHOLD_MAX = 100.0f;\n\n  /** Always set to 100% to disable Resilience4j's rate-based evaluation */\n  private float failureRateThreshold;\n\n  /** Always set to Integer.MAX_VALUE to disable Resilience4j's call-count evaluation */\n  private int minimumNumberOfCalls;\n\n  /** Sliding window size from configuration for metrics collection */\n  private int slidingWindowSize;\n\n  /**\n   * Returns Integer.MAX_VALUE to disable Resilience4j's minimum call evaluation.\n   * @return Integer.MAX_VALUE to prevent automatic circuit breaker evaluation\n   */\n  int getMinimumNumberOfCalls() {\n    return minimumNumberOfCalls;\n  }\n\n  /**\n   * Returns 100% to disable Resilience4j's failure rate evaluation.\n   * @return 100.0f to prevent automatic circuit breaker evaluation\n   */\n  float getFailureRateThreshold() {\n    return failureRateThreshold;\n  }\n\n  /**\n   * Returns TIME_BASED sliding window type for metrics collection.\n   * @return SlidingWindowType.TIME_BASED\n   */\n  SlidingWindowType getSlidingWindowType() {\n    return SlidingWindowType.TIME_BASED;\n  }\n\n  /**\n   * Returns the sliding window size for metrics collection.\n   * @return sliding window size in seconds\n   */\n  int getSlidingWindowSize() {\n    return slidingWindowSize;\n  }\n\n  /**\n   * Creates an adapter that disables Resilience4j's circuit breaker evaluation.\n   * <p>\n   * Sets failure rate to 100% and minimum calls to Integer.MAX_VALUE to ensure Resilience4j never\n   * automatically opens the circuit breaker. Instead, Jedis's custom {@code evaluateThresholds()}\n   * method controls circuit breaker state based on the original configuration's dual-threshold\n   * logic.\n   * </p>\n   * @param multiDbConfig configuration containing sliding window size\n   */\n  CircuitBreakerThresholdsAdapter(MultiDbConfig multiDbConfig) {\n\n    // IMPORTANT: failureRateThreshold is set to max theoretically disable Resilience4j's evaluation\n    // and rely on our custom evaluateThresholds() logic.\n    failureRateThreshold = FAILURE_RATE_THRESHOLD_MAX;\n\n    // IMPORTANT: minimumNumberOfCalls is set to max theoretically disable Resilience4j's evaluation\n    // and rely on our custom evaluateThresholds() logic.\n    minimumNumberOfCalls = Integer.MAX_VALUE;\n\n    slidingWindowSize = multiDbConfig.getFailureDetector().getSlidingWindowSize();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/ConnectionFailoverException.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.exceptions.JedisException;\n\npublic class ConnectionFailoverException extends JedisException {\n  public ConnectionFailoverException(String s, Exception e) {\n    super(s, e);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/ConnectionInitializationContext.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.util.Map;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.mcf.InitializationPolicy.Decision;\n\n/**\n * Context for tracking connection initialization status across multiple database endpoints.\n * <p>\n * This class evaluates the current state of database connections and their health checks to\n * determine how many connections are available, failed, or still pending. It is used in conjunction\n * with {@link InitializationPolicy} to decide when the multi-database connection provider is ready\n * to be used.\n * </p>\n * @author Ali Takavci\n * @since 7.3\n */\nclass ConnectionInitializationContext implements InitializationPolicy.InitializationContext {\n\n  private int available = 0;\n\n  private int failed = 0;\n\n  private int pending = 0;\n\n  /**\n   * Creates a new ConnectionInitializationContext by evaluating the current state of database\n   * connections and their health statuses.\n   * @param databases map of database endpoints to their Database instances\n   * @param healthStatusManager manager for tracking health status of endpoints\n   */\n  public ConnectionInitializationContext(\n      Map<Endpoint, MultiDbConnectionProvider.Database> databases,\n      HealthStatusManager healthStatusManager) {\n\n    for (Map.Entry<Endpoint, MultiDbConnectionProvider.Database> entry : databases.entrySet()) {\n      Endpoint endpoint = entry.getKey();\n\n      // Check if health checks are enabled for this endpoint\n      if (healthStatusManager.hasHealthCheck(endpoint)) {\n        HealthStatus status = healthStatusManager.getHealthStatus(endpoint);\n\n        if (status == HealthStatus.HEALTHY) {\n          // Health check completed successfully\n          available++;\n        } else if (status == HealthStatus.UNHEALTHY) {\n          // Health check completed with failure\n          failed++;\n        } else {\n          // Health check not completed yet (UNKNOWN)\n          pending++;\n        }\n      } else {\n        // No health check configured - assume available\n        available++;\n      }\n    }\n  }\n\n  @Override\n  public int getAvailableConnections() {\n    return available;\n  }\n\n  @Override\n  public int getFailedConnections() {\n    return failed;\n  }\n\n  @Override\n  public int getPendingConnections() {\n    return pending;\n  }\n\n  /**\n   * Evaluates whether the current connection state conforms to the given initialization policy.\n   * @param policy the initialization policy to evaluate against\n   * @return the decision (CONTINUE, SUCCESS, or FAIL) based on the policy evaluation\n   */\n  public Decision conformsTo(InitializationPolicy policy) {\n    return policy.evaluate(this);\n  }\n\n  @Override\n  public String toString() {\n    return \"ConnectionInitializationContext{\" + \"available=\" + available + \", failed=\" + failed\n        + \", pending=\" + pending + '}';\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/DatabaseSwitchEvent.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\n\npublic class DatabaseSwitchEvent {\n\n  private final SwitchReason reason;\n  private final String databaseName;\n  private final Endpoint endpoint;\n\n  public DatabaseSwitchEvent(SwitchReason reason, Endpoint endpoint, Database database) {\n    this.reason = reason;\n    this.databaseName = database.getCircuitBreaker().getName();\n    this.endpoint = endpoint;\n  }\n\n  public SwitchReason getReason() {\n    return reason;\n  }\n\n  public String getDatabaseName() {\n    return databaseName;\n  }\n\n  public Endpoint getEndpoint() {\n    return endpoint;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthCheck.java",
    "content": "\npackage redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.Endpoint;\n\npublic interface HealthCheck {\n\n  Endpoint getEndpoint();\n\n  HealthStatus getStatus();\n\n  void stop();\n\n  void start();\n\n  /**\n   * Get the maximum wait duration (in milliseconds) to wait for health check HealthStatus reach\n   * stable state.\n   * <p>\n   * Transition to stable state means either HEALTHY or UNHEALTHY. UNKNOWN is not considered stable.\n   * This is calculated based on the health check strategy's timeout, retry delay and retry count.\n   * </p>\n   * @return the maximum wait duration in milliseconds\n   */\n  long getMaxWaitFor();\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthCheckCollection.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport redis.clients.jedis.Endpoint;\n\npublic class HealthCheckCollection {\n\n  private Map<Endpoint, HealthCheck> healthChecks = new ConcurrentHashMap<Endpoint, HealthCheck>();\n\n  public HealthCheck add(HealthCheck healthCheck) {\n    return healthChecks.put(healthCheck.getEndpoint(), healthCheck);\n  }\n\n  public HealthCheck[] addAll(HealthCheck[] healthChecks) {\n    HealthCheck[] old = new HealthCheck[healthChecks.length];\n    for (int i = 0; i < healthChecks.length; i++) {\n      old[i] = add(healthChecks[i]);\n    }\n    return old;\n  }\n\n  public HealthCheck remove(Endpoint endpoint) {\n    HealthCheck old = healthChecks.remove(endpoint);\n    if (old != null) {\n      old.stop();\n    }\n    return old;\n  }\n\n  public HealthCheck remove(HealthCheck healthCheck) {\n    HealthCheck[] temp = new HealthCheck[1];\n    healthChecks.computeIfPresent(healthCheck.getEndpoint(), (key, existing) -> {\n      if (existing == healthCheck) {\n        temp[0] = existing;\n        return null;\n      }\n      return existing;\n    });\n    return temp[0];\n  }\n\n  public HealthCheck get(Endpoint endpoint) {\n    return healthChecks.get(endpoint);\n  }\n\n  public void close() {\n    for (HealthCheck healthCheck : healthChecks.values()) {\n      healthCheck.stop();\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthCheckImpl.java",
    "content": "\npackage redis.clients.jedis.mcf;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.annots.VisibleForTesting;\nimport redis.clients.jedis.mcf.ProbingPolicy.ProbeContext;\nimport redis.clients.jedis.util.JedisAsserts;\n\npublic class HealthCheckImpl implements HealthCheck {\n\n  static class HealthProbeContext implements ProbeContext {\n    private final ProbingPolicy policy;\n    private int remainingProbes;\n    private int successes;\n    private int fails;\n    private boolean isCompleted;\n    private HealthStatus result;\n\n    HealthProbeContext(ProbingPolicy policy, int maxProbes) {\n      this.policy = policy;\n      this.remainingProbes = maxProbes;\n    }\n\n    void record(boolean success) {\n      if (success) {\n        this.successes++;\n      } else {\n        this.fails++;\n      }\n      remainingProbes--;\n      ProbingPolicy.Decision decision = policy.evaluate(this);\n      if (decision == ProbingPolicy.Decision.SUCCESS) {\n        setCompleted(HealthStatus.HEALTHY);\n      } else if (decision == ProbingPolicy.Decision.FAIL) {\n        setCompleted(HealthStatus.UNHEALTHY);\n      }\n    }\n\n    public int getRemainingProbes() {\n      return remainingProbes;\n    }\n\n    public int getSuccesses() {\n      return successes;\n    }\n\n    public int getFails() {\n      return fails;\n    }\n\n    void setCompleted(HealthStatus status) {\n      this.result = status;\n      this.isCompleted = true;\n    }\n\n    boolean isCompleted() {\n      return isCompleted;\n    }\n\n    HealthStatus getResult() {\n      return result;\n    }\n  }\n\n  private static class HealthCheckResult {\n    private final long timestamp;\n    private final HealthStatus status;\n\n    public HealthCheckResult(long timestamp, HealthStatus status) {\n      this.timestamp = timestamp;\n      this.status = status;\n    }\n\n    public long getTimestamp() {\n      return timestamp;\n    }\n\n    public HealthStatus getStatus() {\n      return status;\n    }\n  }\n\n  private static final Logger log = LoggerFactory.getLogger(HealthCheckImpl.class);\n\n  private static AtomicInteger workerCounter = new AtomicInteger(1);\n  private static ExecutorService workers = Executors.newCachedThreadPool(r -> {\n    Thread t = new Thread(r, \"jedis-healthcheck-worker-\" + workerCounter.getAndIncrement());\n    t.setDaemon(true);\n    return t;\n  });\n\n  private Endpoint endpoint;\n  private HealthCheckStrategy strategy;\n  private AtomicReference<HealthCheckResult> resultRef = new AtomicReference<HealthCheckResult>();\n  private Consumer<HealthStatusChangeEvent> statusChangeCallback;\n\n  private final ScheduledExecutorService scheduler;\n\n  HealthCheckImpl(Endpoint endpoint, HealthCheckStrategy strategy,\n      Consumer<HealthStatusChangeEvent> statusChangeCallback) {\n\n    JedisAsserts.isTrue(strategy.getNumProbes() > 0,\n      \"Number of HealthCheckStrategy probes must be greater than 0\");\n    this.endpoint = endpoint;\n    this.strategy = strategy;\n    this.statusChangeCallback = statusChangeCallback;\n    resultRef.set(new HealthCheckResult(0L, HealthStatus.UNKNOWN));\n\n    scheduler = Executors.newSingleThreadScheduledExecutor(r -> {\n      Thread t = new Thread(r, \"jedis-healthcheck-\" + this.endpoint);\n      t.setDaemon(true);\n      return t;\n    });\n  }\n\n  public Endpoint getEndpoint() {\n    return endpoint;\n  }\n\n  public HealthStatus getStatus() {\n    return resultRef.get().getStatus();\n  }\n\n  public void start() {\n    scheduler.scheduleAtFixedRate(this::healthCheck, 0, strategy.getInterval(),\n      TimeUnit.MILLISECONDS);\n  }\n\n  public void stop() {\n    strategy.close();\n    this.statusChangeCallback = null;\n    scheduler.shutdown();\n\n    try {\n      // Wait for graceful shutdown then force if required\n      if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {\n        scheduler.shutdownNow();\n      }\n    } catch (InterruptedException e) {\n      // Force shutdown immediately\n      scheduler.shutdownNow();\n      Thread.currentThread().interrupt();\n    }\n  }\n\n  private HealthStatus doHealthCheck() {\n    HealthStatus newStatus = strategy.doHealthCheck(endpoint);\n    log.trace(\"Health check completed for {} with status {}\", endpoint, newStatus);\n    return newStatus;\n  }\n\n  private void healthCheck() {\n    long me = System.currentTimeMillis();\n    HealthStatus update = null;\n    HealthProbeContext probeContext = new HealthProbeContext(strategy.getPolicy(),\n        strategy.getNumProbes());\n\n    while (!probeContext.isCompleted()) {\n      Future<HealthStatus> future = workers.submit(this::doHealthCheck);\n      try {\n        update = future.get(strategy.getTimeout(), TimeUnit.MILLISECONDS);\n        probeContext.record(update == HealthStatus.HEALTHY);\n      } catch (TimeoutException | ExecutionException e) {\n        future.cancel(true);\n        if (log.isWarnEnabled()) {\n          log.warn(String.format(\"Health check timed out or failed for %s.\", endpoint), e);\n        }\n        probeContext.record(false);\n      } catch (InterruptedException e) {// Health check thread was interrupted\n        future.cancel(true);\n        Thread.currentThread().interrupt(); // Restore interrupted status\n        log.warn(String.format(\"Health check interrupted for %s.\", endpoint), e);\n        // thread interrupted, stop health check process\n        return;\n      }\n      if (!probeContext.isCompleted()) {\n        try {\n          Thread.sleep(strategy.getDelayInBetweenProbes());\n        } catch (InterruptedException e) {\n          Thread.currentThread().interrupt(); // Restore interrupted status\n          log.warn(String.format(\"Health check interrupted while sleeping for %s.\", endpoint), e);\n          // thread interrupted, stop health check process\n          return;\n        }\n      }\n    }\n\n    safeUpdate(me, probeContext.getResult());\n  }\n\n  /**\n   * just to avoid to replace status with an outdated result from another healthCheck\n   *\n   * <pre>\n   * Health Check Race Condition Prevention\n   *\n   * Problem: Async health checks can complete out of order, causing stale results\n   * to overwrite newer ones.\n   *\n   * Timeline Example:\n   * ─────────────────────────────────────────────────────────────────\n   * T0: Start Check #1 ────────────────────┐\n   * T1: Start Check #2 ──────────┐         │\n   * T2:                          │         │\n   * T3: Check #2 completes ──────┘         │  → status = \"Healthy\"\n   * T4: Check #1 completes ────────────────┘  → status = \"Unhealthy\" (STALE!)\n   *\n   *\n   * Result: Final status shows \"Unhealthy\" even though the most recent\n   * check (#2) returned \"Healthy\"\n   *\n   * How Parallel Health Checks Can Occur:\n   * 1. Timeout scenario: A scheduled health check times out and future.cancel(true)\n   *    is called, but the actual health check operation continues running in the\n   *    background thread and may complete any time later\n   * 2. Scheduler overlap: If a health check takes longer than the configured\n   *    interval, the next scheduled check can start before the previous one finishes\n   * 3. Interruption handling: When a health check thread is interrupted, it may\n   *    still complete its operation before recognizing the interruption\n   *\n   * Solution: Track execution order/timestamp to ignore outdated results\n   * </pre>\n   *\n   * @param owner the timestamp of the health check that is updating the status\n   * @param status the new status to set\n   */\n  @VisibleForTesting\n  void safeUpdate(long owner, HealthStatus status) {\n    HealthCheckResult newResult = new HealthCheckResult(owner, status);\n    AtomicBoolean wasUpdated = new AtomicBoolean(false);\n\n    HealthCheckResult oldResult = resultRef.getAndUpdate(current -> {\n      if (current.getTimestamp() < owner) {\n        wasUpdated.set(true);\n        return newResult;\n      }\n      wasUpdated.set(false);\n      return current;\n    });\n\n    if (wasUpdated.get() && oldResult.getStatus() != status) {\n      log.info(\"Health status changed for {} from {} to {}\", endpoint, oldResult.getStatus(),\n        status);\n      // notify listeners\n      notifyListeners(oldResult.getStatus(), status);\n    }\n  }\n\n  private void notifyListeners(HealthStatus oldStatus, HealthStatus newStatus) {\n    if (statusChangeCallback != null) {\n      statusChangeCallback.accept(new HealthStatusChangeEvent(endpoint, oldStatus, newStatus));\n    }\n  }\n\n  @Override\n  public long getMaxWaitFor() {\n    return (strategy.getTimeout() + strategy.getDelayInBetweenProbes()) * strategy.getNumProbes();\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthCheckStrategy.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.io.Closeable;\nimport redis.clients.jedis.Endpoint;\n\npublic interface HealthCheckStrategy extends Closeable {\n\n  /**\n   * Get the interval (in milliseconds) between health checks.\n   * @return the interval in milliseconds\n   */\n  int getInterval();\n\n  /**\n   * Get the timeout (in milliseconds) for a health check.\n   * @return the timeout in milliseconds\n   */\n  int getTimeout();\n\n  /**\n   * Perform the health check for the given endpoint.\n   * @param endpoint the endpoint to check\n   * @return the health status\n   */\n  HealthStatus doHealthCheck(Endpoint endpoint);\n\n  /**\n   * Close any resources used by the health check strategy.\n   */\n  default void close() {\n  }\n\n  /**\n   * Get the number of probes for health checks to repeat.\n   * @return the number of probes\n   */\n  int getNumProbes();\n\n  /**\n   * Get the policy for health checks.\n   * @return the policy\n   */\n  ProbingPolicy getPolicy();\n\n  /**\n   * Get the delay (in milliseconds) between retries for failed health checks.\n   * @return the delay in milliseconds\n   */\n  int getDelayInBetweenProbes();\n\n  public static class Config {\n    private static final int INTERVAL_DEFAULT = 5000;\n    private static final int TIMEOUT_DEFAULT = 1000;\n    private static final int NUM_PROBES_DEFAULT = 3;\n    private static final int DELAY_IN_BETWEEN_PROBES_DEFAULT = 500;\n\n    protected final int interval;\n    protected final int timeout;\n    protected final int numProbes;\n    protected final int delayInBetweenProbes;\n    protected final ProbingPolicy policy;\n\n    public Config(int interval, int timeout, int numProbes, int delayInBetweenProbes,\n        ProbingPolicy policy) {\n      this.interval = interval;\n      this.timeout = timeout;\n      this.numProbes = numProbes;\n      this.delayInBetweenProbes = delayInBetweenProbes;\n      this.policy = policy;\n    }\n\n    Config(Builder<?, ?> builder) {\n      this.interval = builder.interval;\n      this.timeout = builder.timeout;\n      this.numProbes = builder.numProbes;\n      this.delayInBetweenProbes = builder.delayInBetweenProbes;\n      this.policy = builder.policy;\n    }\n\n    public int getInterval() {\n      return interval;\n    }\n\n    public int getTimeout() {\n      return timeout;\n    }\n\n    public int getNumProbes() {\n      return numProbes;\n    }\n\n    public int getDelayInBetweenProbes() {\n      return delayInBetweenProbes;\n    }\n\n    public ProbingPolicy getPolicy() {\n      return policy;\n    }\n\n    /**\n     * Create a new Config instance with default values.\n     * @return a new Config instance\n     */\n    public static Config create() {\n      return builder().build();\n    }\n\n    /**\n     * Create a new builder for HealthCheckStrategy.Config.\n     * @return a new Builder instance\n     */\n    public static Builder<?, ? extends Config> builder() {\n      return new Builder<>();\n    }\n\n    /**\n     * Base builder for HealthCheckStrategy.Config and its subclasses.\n     * @param <T> the builder type (for fluent API)\n     * @param <C> the config type being built\n     */\n    public static class Builder<T extends Builder<T, C>, C extends Config> {\n      protected int interval = INTERVAL_DEFAULT;\n      protected int timeout = TIMEOUT_DEFAULT;\n      protected int numProbes = NUM_PROBES_DEFAULT;\n      protected ProbingPolicy policy = ProbingPolicy.BuiltIn.ALL_SUCCESS;\n      protected int delayInBetweenProbes = DELAY_IN_BETWEEN_PROBES_DEFAULT;\n\n      /**\n       * Set the interval between health checks in milliseconds.\n       * @param interval the interval in milliseconds (default: 1000)\n       * @return this builder\n       */\n      @SuppressWarnings(\"unchecked\")\n      public T interval(int interval) {\n        this.interval = interval;\n        return (T) this;\n      }\n\n      /**\n       * Set the timeout for health checks in milliseconds.\n       * @param timeout the timeout in milliseconds (default: 1000)\n       * @return this builder\n       */\n      @SuppressWarnings(\"unchecked\")\n      public T timeout(int timeout) {\n        this.timeout = timeout;\n        return (T) this;\n      }\n\n      /**\n       * Set the number of probes for health check.\n       * @param numProbes the number of repeats (default: 3)\n       * @return this builder\n       */\n      @SuppressWarnings(\"unchecked\")\n      public T numProbes(int numProbes) {\n        this.numProbes = numProbes;\n        return (T) this;\n      }\n\n      /**\n       * Set the policy for health checks.\n       * @param policy the policy (default: ProbingPolicy.BuiltIn.ALL_SUCCESS)\n       * @return this builder\n       */\n      @SuppressWarnings(\"unchecked\")\n      public T policy(ProbingPolicy policy) {\n        this.policy = policy;\n        return (T) this;\n      }\n\n      /**\n       * Set the delay between retries for failed health checks in milliseconds.\n       * @param delayInBetweenProbes the delay in milliseconds (default: 100)\n       * @return this builder\n       */\n      @SuppressWarnings(\"unchecked\")\n      public T delayInBetweenProbes(int delayInBetweenProbes) {\n        this.delayInBetweenProbes = delayInBetweenProbes;\n        return (T) this;\n      }\n\n      /**\n       * Build the Config instance.\n       * @return a new Config instance\n       */\n      @SuppressWarnings(\"unchecked\")\n      public C build() {\n        return (C) new Config(this);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthStatus.java",
    "content": "package redis.clients.jedis.mcf;\n\npublic enum HealthStatus {\n  UNKNOWN(0x00), HEALTHY(0x01), UNHEALTHY(0x02);\n\n  private final int value;\n\n  HealthStatus(int val) {\n    this.value = val;\n  }\n\n  public boolean isHealthy() {\n    return (this.value & HEALTHY.value) != 0;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthStatusChangeEvent.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.Endpoint;\n\npublic class HealthStatusChangeEvent {\n\n  private final Endpoint endpoint;\n  private final HealthStatus oldStatus;\n  private final HealthStatus newStatus;\n\n  public HealthStatusChangeEvent(Endpoint endpoint, HealthStatus oldStatus,\n      HealthStatus newStatus) {\n    this.endpoint = endpoint;\n    this.oldStatus = oldStatus;\n    this.newStatus = newStatus;\n  }\n\n  public Endpoint getEndpoint() {\n    return endpoint;\n  }\n\n  public HealthStatus getOldStatus() {\n    return oldStatus;\n  }\n\n  public HealthStatus getNewStatus() {\n    return newStatus;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthStatusListener.java",
    "content": "package redis.clients.jedis.mcf;\n\npublic interface HealthStatusListener {\n\n  void onStatusChange(HealthStatusChangeEvent event);\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/HealthStatusManager.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport redis.clients.jedis.Endpoint;\n\npublic class HealthStatusManager {\n\n  private final HealthCheckCollection healthChecks = new HealthCheckCollection();\n  private final List<HealthStatusListener> listeners = new CopyOnWriteArrayList<>();\n  private final Map<Endpoint, List<HealthStatusListener>> endpointListeners = new ConcurrentHashMap<Endpoint, List<HealthStatusListener>>();\n\n  public void registerListener(HealthStatusListener listener) {\n    listeners.add(listener);\n  }\n\n  public void unregisterListener(HealthStatusListener listener) {\n    listeners.remove(listener);\n  }\n\n  public void registerListener(Endpoint endpoint, HealthStatusListener listener) {\n    endpointListeners.computeIfAbsent(endpoint, k -> new CopyOnWriteArrayList<>()).add(listener);\n  }\n\n  public void unregisterListener(Endpoint endpoint, HealthStatusListener listener) {\n    endpointListeners.computeIfPresent(endpoint, (k, v) -> {\n      v.remove(listener);\n      return v;\n    });\n  }\n\n  public void notifyListeners(HealthStatusChangeEvent eventArgs) {\n    endpointListeners.computeIfPresent(eventArgs.getEndpoint(), (k, v) -> {\n      for (HealthStatusListener listener : v) {\n        listener.onStatusChange(eventArgs);\n      }\n      return v;\n    });\n    for (HealthStatusListener listener : listeners) {\n      listener.onStatusChange(eventArgs);\n    }\n  }\n\n  public HealthCheck add(Endpoint endpoint, HealthCheckStrategy strategy) {\n    HealthCheck hc = new HealthCheckImpl(endpoint, strategy, this::notifyListeners);\n    HealthCheck old = healthChecks.add(hc);\n    hc.start();\n    if (old != null) {\n      old.stop();\n    }\n    return hc;\n  }\n\n  public void addAll(Endpoint[] endpoints, HealthCheckStrategy strategy) {\n    for (Endpoint endpoint : endpoints) {\n      add(endpoint, strategy);\n    }\n  }\n\n  public void remove(Endpoint endpoint) {\n    HealthCheck old = healthChecks.remove(endpoint);\n    if (old != null) {\n      old.stop();\n    }\n  }\n\n  public void removeAll(Endpoint[] endpoints) {\n    for (Endpoint endpoint : endpoints) {\n      remove(endpoint);\n    }\n  }\n\n  public HealthStatus getHealthStatus(Endpoint endpoint) {\n    HealthCheck healthCheck = healthChecks.get(endpoint);\n    return healthCheck != null ? healthCheck.getStatus() : HealthStatus.UNKNOWN;\n  }\n\n  public boolean hasHealthCheck(Endpoint endpoint) {\n    return healthChecks.get(endpoint) != null;\n  }\n\n  public long getMaxWaitFor(Endpoint endpoint) {\n    HealthCheck healthCheck = healthChecks.get(endpoint);\n    return healthCheck != null ? healthCheck.getMaxWaitFor() : 0;\n  }\n\n  public void close() {\n    healthChecks.close();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/InitializationPolicy.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * Interface for initialization policies.\n * <p>\n * An initialization policy determines when a multi-database connection is ready to be returned\n * based on the availability of individual database connections.\n * </p>\n * <p>\n * The policy is evaluated based on the completion status of database connection health checks, and\n * the decision to continue waiting, succeed, or fail is based on the number of available, pending,\n * and failed connections.\n * </p>\n * @author Ali Takavci\n * @since 7.3\n */\n@Experimental\npublic interface InitializationPolicy {\n\n  enum Decision {\n    CONTINUE, SUCCESS, FAIL\n  }\n\n  Decision evaluate(InitializationContext context);\n\n  interface InitializationContext {\n\n    int getAvailableConnections();\n\n    int getFailedConnections();\n\n    int getPendingConnections();\n\n  }\n\n  /**\n   * Built-in initialization policies.\n   * <p>\n   * The policy is evaluated based on the completion status of database health checks, and the\n   * decision to continue waiting, succeed, or fail is based on the number of available, pending,\n   * and failed connections.\n   * </p>\n   * Built-in policies are:\n   * <ul>\n   * <li>{@link BuiltIn#ALL_AVAILABLE} - All databases need to be available</li>\n   * <li>{@link BuiltIn#MAJORITY_AVAILABLE} - Majority of databases need to be available</li>\n   * <li>{@link BuiltIn#ONE_AVAILABLE} - At least one database needs to be available</li>\n   * </ul>\n   */\n  class BuiltIn {\n\n    /**\n     * Policy that requires all databases to be available before the connection is ready.\n     */\n    public static final InitializationPolicy ALL_AVAILABLE = new AllAvailablePolicy();\n\n    /**\n     * Policy that requires a majority of databases to be available before the connection is ready.\n     */\n    public static final InitializationPolicy MAJORITY_AVAILABLE = new MajorityAvailablePolicy();\n\n    /**\n     * Policy that requires at least one database to be available before the connection is ready.\n     */\n    public static final InitializationPolicy ONE_AVAILABLE = new OneAvailablePolicy();\n\n    /*\n     * All databases need to be available. The connection is ready only when all database health\n     * checks have completed successfully. If any connection fails, the initialization fails. If all\n     * connections are available, initialization succeeds. Otherwise, continue waiting.\n     */\n    private static class AllAvailablePolicy implements InitializationPolicy {\n\n      @Override\n      public Decision evaluate(InitializationContext ctx) {\n        // Any failure means overall failure\n        if (ctx.getFailedConnections() > 0) {\n          return Decision.FAIL;\n        }\n\n        // All connections completed successfully\n        if (ctx.getPendingConnections() == 0) {\n          // No connections configured at all - fail\n          if (ctx.getAvailableConnections() == 0) {\n            return Decision.FAIL;\n          }\n          return Decision.SUCCESS;\n        }\n\n        return Decision.CONTINUE;\n      }\n\n    }\n\n    /*\n     * A majority of databases need to be available. The connection is ready when more than half of\n     * the database connections are available. This means initialization can succeed early once\n     * majority is reached, or fail early if majority becomes impossible.\n     */\n    private static class MajorityAvailablePolicy implements InitializationPolicy {\n\n      @Override\n      public Decision evaluate(InitializationContext ctx) {\n        int total = ctx.getPendingConnections() + ctx.getAvailableConnections()\n            + ctx.getFailedConnections();\n        int required = (total / 2) + 1;\n\n        // Early success - majority reached\n        if (ctx.getAvailableConnections() >= required) {\n          return Decision.SUCCESS;\n        }\n\n        // Early failure - impossible to reach majority\n        int maxPossibleAvailable = ctx.getAvailableConnections() + ctx.getPendingConnections();\n        if (maxPossibleAvailable < required) {\n          return Decision.FAIL;\n        }\n\n        // Final evaluation - no more pending\n        if (ctx.getPendingConnections() == 0) {\n          return ctx.getAvailableConnections() >= required ? Decision.SUCCESS : Decision.FAIL;\n        }\n\n        return Decision.CONTINUE;\n      }\n\n    }\n\n    /*\n     * At least one database needs to be available. The connection is ready as soon as any database\n     * connection is available. Initialization fails only if all connections fail.\n     */\n    private static class OneAvailablePolicy implements InitializationPolicy {\n\n      @Override\n      public Decision evaluate(InitializationContext ctx) {\n        // Any success means overall success\n        if (ctx.getAvailableConnections() > 0) {\n          return Decision.SUCCESS;\n        }\n\n        // All connections completed with failures\n        if (ctx.getPendingConnections() == 0) {\n          return Decision.FAIL;\n        }\n\n        return Decision.CONTINUE;\n      }\n\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/JedisFailoverException.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\n/**\n * Exception thrown when a failover attempt fails due to lack of available/healthy databases.\n * <p>\n * This exception itself is not thrown, see the child exceptions for more details.\n * </p>\n * @see JedisFailoverException.JedisPermanentlyNotAvailableException\n * @see JedisFailoverException.JedisTemporarilyNotAvailableException\n */\npublic class JedisFailoverException extends JedisConnectionException {\n  private static final String MESSAGE = \"Database endpoint could not failover since the MultiDbConfig was not \"\n      + \"provided with an additional database endpoint according to its prioritized sequence. \"\n      + \"If applicable, consider falling back OR restarting with an available database endpoint\";\n\n  public JedisFailoverException(String s) {\n    super(s);\n  }\n\n  public JedisFailoverException() {\n    super(MESSAGE);\n  }\n\n  /**\n   * Exception thrown when a failover attempt fails due to lack of available/healthy databases, and\n   * the max number of failover attempts has been exceeded. And there is still no healthy databases.\n   * <p>\n   * See the configuration properties\n   * {@link redis.clients.jedis.MultiDbConfig#maxNumFailoverAttempts} and\n   * {@link redis.clients.jedis.MultiDbConfig#delayInBetweenFailoverAttempts} for more details.\n   */\n  public static class JedisPermanentlyNotAvailableException extends JedisFailoverException {\n    public JedisPermanentlyNotAvailableException(String s) {\n      super(s);\n    }\n\n    public JedisPermanentlyNotAvailableException() {\n      super();\n    }\n  }\n\n  /**\n   * Exception thrown when a failover attempt fails due to lack of available/healthy databases, but\n   * the max number of failover attempts has not been exceeded yet. Though there is no healthy\n   * database including the selected/current one, given configuration suggests that it should be a\n   * temporary condition and it is possible that there will be a healthy database available.\n   * <p>\n   * See the configuration properties\n   * {@link redis.clients.jedis.MultiDbConfig#maxNumFailoverAttempts} and\n   * {@link redis.clients.jedis.MultiDbConfig#delayInBetweenFailoverAttempts} for more details.\n   */\n  public static class JedisTemporarilyNotAvailableException extends JedisFailoverException {\n\n    public JedisTemporarilyNotAvailableException(String s) {\n      super(s);\n    }\n\n    public JedisTemporarilyNotAvailableException() {\n      super();\n    }\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/LagAwareStrategy.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.function.Supplier;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.RedisCredentials;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.Endpoint;\n\npublic class LagAwareStrategy implements HealthCheckStrategy {\n\n  private static final Logger log = LoggerFactory.getLogger(LagAwareStrategy.class);\n\n  private final Config config;\n  private final RedisRestAPI redisRestAPI;\n  private String bdbId;\n\n  public LagAwareStrategy(Config config) {\n    this.config = config;\n    this.redisRestAPI = new RedisRestAPI(config.getRestEndpoint(), config.getCredentialsSupplier(),\n        config.getTimeout(), config.getSslOptions());\n  }\n\n  @Override\n  public int getInterval() {\n    return config.interval;\n  }\n\n  @Override\n  public int getTimeout() {\n    return config.timeout;\n  }\n\n  @Override\n  public int getNumProbes() {\n    return config.getNumProbes();\n  }\n\n  @Override\n  public ProbingPolicy getPolicy() {\n    return config.getPolicy();\n  }\n\n  @Override\n  public int getDelayInBetweenProbes() {\n    return config.getDelayInBetweenProbes();\n  }\n\n  @Override\n  public HealthStatus doHealthCheck(Endpoint endpoint) {\n    try {\n      String bdb = bdbId;\n      if (bdb == null) {\n        // Try to find BDB that matches the database host\n        String dbHost = endpoint.getHost();\n        List<RedisRestAPI.BdbInfo> bdbs = redisRestAPI.getBdbs();\n        RedisRestAPI.BdbInfo matchingBdb = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, dbHost);\n\n        if (matchingBdb == null) {\n          String msg = String.format(\"No BDB found matching host '%s' for health check\", dbHost);\n          log.warn(msg);\n          throw new JedisException(msg);\n        } else {\n          bdb = matchingBdb.getUid();\n          log.debug(\"Found matching BDB '{}' for host '{}'\", bdb, dbHost);\n          bdbId = bdb;\n        }\n      }\n      if (this.config.isExtendedCheckEnabled()) {\n        // Use extended check with lag validation\n        if (redisRestAPI.checkBdbAvailability(bdb, true,\n          this.config.getAvailabilityLagTolerance().toMillis())) {\n          return HealthStatus.HEALTHY;\n        }\n      } else {\n        // Use standard datapath validation only\n        if (redisRestAPI.checkBdbAvailability(bdb, false)) {\n          return HealthStatus.HEALTHY;\n        }\n      }\n    } catch (Exception e) {\n      log.error(\"Error while checking database availability\", e);\n      bdbId = null;\n      throw new JedisException(\"Error while checking availability\", e);\n    }\n    return HealthStatus.UNHEALTHY;\n  }\n\n  public static class Config extends HealthCheckStrategy.Config {\n\n    public static final boolean EXTENDED_CHECK_DEFAULT = true;\n    public static final Duration AVAILABILITY_LAG_TOLERANCE_DEFAULT = Duration.ofMillis(5000);\n\n    private final Endpoint restEndpoint;\n    private final Supplier<RedisCredentials> credentialsSupplier;\n\n    // SSL configuration for HTTPS connections to Redis Enterprise REST API\n    private final SslOptions sslOptions;\n\n    // Maximum acceptable lag in milliseconds (default: 5000);\n    private final Duration availability_lag_tolerance;\n\n    // Enable extended lag checking (default: true - performs lag validation in addition to standard\n    // datapath\n    // validation )\n    private final boolean extendedCheckEnabled;\n\n    public Config(Endpoint restEndpoint, Supplier<RedisCredentials> credentialsSupplier) {\n      this(builder(restEndpoint, credentialsSupplier)\n          .availabilityLagTolerance(AVAILABILITY_LAG_TOLERANCE_DEFAULT)\n          .extendedCheckEnabled(EXTENDED_CHECK_DEFAULT));\n    }\n\n    private Config(ConfigBuilder builder) {\n      super(builder);\n\n      this.restEndpoint = builder.restEndpoint;\n      this.credentialsSupplier = builder.credentialsSupplier;\n      this.sslOptions = builder.sslOptions;\n      this.availability_lag_tolerance = builder.availabilityLagTolerance;\n      this.extendedCheckEnabled = builder.extendedCheckEnabled;\n    }\n\n    public Endpoint getRestEndpoint() {\n      return restEndpoint;\n    }\n\n    public Supplier<RedisCredentials> getCredentialsSupplier() {\n      return credentialsSupplier;\n    }\n\n    public SslOptions getSslOptions() {\n      return sslOptions;\n    }\n\n    public Duration getAvailabilityLagTolerance() {\n      return availability_lag_tolerance;\n    }\n\n    public boolean isExtendedCheckEnabled() {\n      return extendedCheckEnabled;\n    }\n\n    /**\n     * Create a new builder for LagAwareStrategy.Config.\n     * @param restEndpoint the Redis Enterprise REST API endpoint\n     * @param credentialsSupplier the credentials supplier\n     * @return a new ConfigBuilder instance\n     */\n    public static ConfigBuilder builder(Endpoint restEndpoint,\n        Supplier<RedisCredentials> credentialsSupplier) {\n      return new ConfigBuilder(restEndpoint, credentialsSupplier);\n    }\n\n    /**\n     * Use {@link LagAwareStrategy.Config#builder(Endpoint, Supplier)} instead.\n     * @return a new Builder instance\n     */\n    public static ConfigBuilder builder() {\n      throw new UnsupportedOperationException(\n          \"Endpoint and credentials are required to build LagAwareStrategy.Config.\");\n    }\n\n    /**\n     * Create a new Config instance with default values.\n     * <p>\n     * Extended checks like lag validation is enabled by default. With a default lag tolerance of\n     * 100ms. To perform only standard datapath validation, use\n     * {@link #databaseAvailability(Endpoint, Supplier)}. To configure a custom lag tolerance, use\n     * {@link #lagAwareWithTolerance(Endpoint, Supplier, Duration)}\n     * </p>\n     */\n    public static Config create(Endpoint restEndpoint,\n        Supplier<RedisCredentials> credentialsSupplier) {\n      return new ConfigBuilder(restEndpoint, credentialsSupplier).build();\n    }\n\n    /**\n     * Perform standard datapath validation only.\n     * <p>\n     * Extended checks like lag validation is disabled by default. To enable extended checks, use\n     * {@link #lagAware(Endpoint, Supplier)} or\n     * {@link #lagAwareWithTolerance(Endpoint, Supplier, Duration)}\n     * </p>\n     */\n    public static Config databaseAvailability(Endpoint restEndpoint,\n        Supplier<RedisCredentials> credentialsSupplier) {\n      return new ConfigBuilder(restEndpoint, credentialsSupplier).extendedCheckEnabled(false)\n          .build();\n    }\n\n    /**\n     * Perform standard datapath validation and lag validation using the default lag tolerance.\n     * <p>\n     * To configure a custom lag tolerance, use\n     * {@link #lagAwareWithTolerance(Endpoint, Supplier, Duration)}\n     * </p>\n     */\n    public static Config lagAware(Endpoint restEndpoint,\n        Supplier<RedisCredentials> credentialsSupplier) {\n      return new ConfigBuilder(restEndpoint, credentialsSupplier).extendedCheckEnabled(true)\n          .build();\n    }\n\n    /**\n     * Perform standard datapath validation and lag validation using the specified lag tolerance.\n     */\n    public static Config lagAwareWithTolerance(Endpoint restEndpoint,\n        Supplier<RedisCredentials> credentialsSupplier, Duration availabilityLagTolerance) {\n      return new ConfigBuilder(restEndpoint, credentialsSupplier).extendedCheckEnabled(true)\n          .availabilityLagTolerance(availabilityLagTolerance).build();\n    }\n\n    /**\n     * Builder for LagAwareStrategy.Config.\n     */\n    public static class ConfigBuilder\n        extends HealthCheckStrategy.Config.Builder<ConfigBuilder, Config> {\n      private final Endpoint restEndpoint;\n      private final Supplier<RedisCredentials> credentialsSupplier;\n\n      // SSL configuration for HTTPS connections\n      private SslOptions sslOptions;\n\n      // Maximum acceptable lag in milliseconds (default: 100);\n      private Duration availabilityLagTolerance = AVAILABILITY_LAG_TOLERANCE_DEFAULT;\n\n      // Enable extended lag checking\n      private boolean extendedCheckEnabled = EXTENDED_CHECK_DEFAULT;\n\n      private ConfigBuilder(Endpoint restEndpoint, Supplier<RedisCredentials> credentialsSupplier) {\n        this.restEndpoint = restEndpoint;\n        this.credentialsSupplier = credentialsSupplier;\n      }\n\n      /**\n       * Set SSL options for HTTPS connections to Redis Enterprise REST API. This allows\n       * configuration of custom truststore, keystore, and SSL parameters for secure connections.\n       * @param sslOptions the SSL configuration options\n       * @return this builder\n       */\n      public ConfigBuilder sslOptions(SslOptions sslOptions) {\n        this.sslOptions = sslOptions;\n        return this;\n      }\n\n      /**\n       * Set the maximum acceptable lag in milliseconds.\n       * @param availabilityLagTolerance the lag tolerance in milliseconds (default: 100)\n       * @return this builder\n       */\n      public ConfigBuilder availabilityLagTolerance(Duration availabilityLagTolerance) {\n        this.availabilityLagTolerance = availabilityLagTolerance;\n        return this;\n      }\n\n      /**\n       * Enable extended lag checking. When enabled, performs lag validation in addition to standard\n       * datapath validation. When disabled performs only standard datapath validation - all slots\n       * are available.\n       * @param extendedCheckEnabled true to enable extended lag checking (default: false)\n       * @return this builder\n       */\n      public ConfigBuilder extendedCheckEnabled(boolean extendedCheckEnabled) {\n        this.extendedCheckEnabled = extendedCheckEnabled;\n        return this;\n      }\n\n      /**\n       * Build the Config instance.\n       * @return a new Config instance\n       */\n      @Override\n      public Config build() {\n        return new Config(this);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/MultiDbCommandExecutor.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker.State;\nimport io.github.resilience4j.decorators.Decorators;\nimport io.github.resilience4j.decorators.Decorators.DecorateSupplier;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\n\n/**\n * @author Allen Terleto (aterleto)\n *         <p>\n *         CommandExecutor with built-in retry, circuit-breaker, and failover to another database\n *         endpoint. With this executor users can seamlessly failover to Disaster Recovery (DR),\n *         Backup, and Active-Active cluster(s) by using simple configuration which is passed\n *         through from Resilience4j - https://resilience4j.readme.io/docs\n *         <p>\n */\n@Experimental\npublic class MultiDbCommandExecutor extends MultiDbFailoverBase implements CommandExecutor {\n\n  public MultiDbCommandExecutor(MultiDbConnectionProvider provider) {\n    super(provider);\n  }\n\n  @Override\n  public <T> T executeCommand(CommandObject<T> commandObject) {\n    Database database = provider.getDatabase(); // Pass this by reference for thread safety\n\n    DecorateSupplier<T> supplier = Decorators\n        .ofSupplier(() -> this.handleExecuteCommand(commandObject, database));\n\n    supplier.withCircuitBreaker(database.getCircuitBreaker());\n    supplier.withRetry(database.getRetry());\n    supplier.withFallback(provider.getFallbackExceptionList(),\n      e -> this.handleDatabaseFailover(commandObject, database));\n    try {\n      return supplier.decorate().get();\n    } catch (Exception e) {\n      if (database.getCircuitBreaker().getState() == State.OPEN && isActiveDatabase(database)) {\n        databaseFailover(database);\n      }\n      throw e;\n    }\n  }\n\n  /**\n   * Functional interface wrapped in retry and circuit breaker logic to handle happy path scenarios\n   */\n  private <T> T handleExecuteCommand(CommandObject<T> commandObject, Database database) {\n    Connection connection;\n    try {\n      connection = database.getConnection();\n    } catch (JedisConnectionException e) {\n      provider.assertOperability();\n      throw e;\n    }\n    try {\n      return connection.executeCommand(commandObject);\n    } catch (Exception e) {\n      if (database.retryOnFailover() && !isActiveDatabase(database)\n          && isCircuitBreakerTrackedException(e, database)) {\n        throw new ConnectionFailoverException(\n            \"Command failed during failover: \" + database.getCircuitBreaker().getName(), e);\n      }\n      throw e;\n    } finally {\n      connection.close();\n    }\n  }\n\n  /**\n   * Functional interface wrapped in retry and circuit breaker logic to handle open circuit breaker\n   * failure scenarios\n   */\n  private <T> T handleDatabaseFailover(CommandObject<T> commandObject, Database database) {\n\n    databaseFailover(database);\n\n    // Recursive call to the initiating method so the operation can be retried on the next database\n    // connection\n    return executeCommand(commandObject);\n  }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/MultiDbConnectionProvider.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker;\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker.Metrics;\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker.State;\nimport io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;\nimport io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;\nimport io.github.resilience4j.core.IntervalFunction;\nimport io.github.resilience4j.retry.Retry;\nimport io.github.resilience4j.retry.RetryConfig;\nimport io.github.resilience4j.retry.RetryRegistry;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.stream.Collectors;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.annots.VisibleForTesting;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.exceptions.JedisValidationException;\nimport redis.clients.jedis.mcf.InitializationPolicy.Decision;\nimport redis.clients.jedis.mcf.JedisFailoverException.*;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.MultiDbConfig.StrategySupplier;\nimport redis.clients.jedis.util.JedisAsserts;\nimport redis.clients.jedis.util.Pool;\n\n/**\n * @author Allen Terleto (aterleto)\n *         <p>\n *         ConnectionProvider which supports multiple database endpoints each with their own\n *         isolated connection pool. With this ConnectionProvider users can seamlessly failover to\n *         Disaster Recovery (DR), Backup, and Active-Active database(s) by using simple\n *         configuration which is passed through from Resilience4j -\n *         <a href=\"https://resilience4j.readme.io/docs\">docs</a>\n *         <p>\n *         Support for manual failback is provided by way of {@link #setActiveDatabase(Endpoint)}\n *         <p>\n */\n@Experimental\npublic class MultiDbConnectionProvider implements ConnectionProvider {\n\n  private final Logger log = LoggerFactory.getLogger(getClass());\n\n  /**\n   * Ordered map of database. Users can move down (failover) or (up) failback the map depending on\n   * their availability and order.\n   */\n  private final Map<Endpoint, Database> databaseMap = new ConcurrentHashMap<>();\n\n  /**\n   * Indicates the actively used database endpoint (connection pool) amongst the pre-configured list\n   * which were provided at startup via the MultiDbConfig. All traffic will be routed with this\n   * database\n   */\n  private volatile Database activeDatabase;\n\n  private final Lock activeDatabaseChangeLock = new ReentrantLock(true);\n\n  /**\n   * Functional interface for listening to database switch events. The event args contain the reason\n   * for the switch, the endpoint, and the database.\n   */\n  private Consumer<DatabaseSwitchEvent> databaseSwitchListener;\n\n  private final List<Class<? extends Throwable>> fallbackExceptionList;\n\n  private final HealthStatusManager healthStatusManager = new HealthStatusManager();\n\n  // Flag to control when handleHealthStatusChange should process events (only after initialization)\n  private volatile boolean initializationComplete = false;\n\n  // Failback mechanism fields\n  private static final AtomicInteger failbackThreadCounter = new AtomicInteger(1);\n  private final ScheduledExecutorService failbackScheduler = Executors\n      .newSingleThreadScheduledExecutor(r -> {\n        Thread t = new Thread(r, \"jedis-failback-\" + failbackThreadCounter.getAndIncrement());\n        t.setDaemon(true);\n        return t;\n      });\n\n  // Store retry and circuit breaker configs for dynamic database addition/removal\n  private final RetryConfig retryConfig;\n  private final CircuitBreakerConfig circuitBreakerConfig;\n  private final MultiDbConfig multiDbConfig;\n\n  private final AtomicLong failoverFreezeUntil = new AtomicLong(0);\n  private final AtomicInteger failoverAttemptCount = new AtomicInteger(0);\n\n  public MultiDbConnectionProvider(MultiDbConfig multiDbConfig) {\n\n    if (multiDbConfig == null) throw new JedisValidationException(\n        \"MultiDbConfig must not be NULL for MultiDbConnectionProvider\");\n\n    this.multiDbConfig = multiDbConfig;\n\n    ////////////// Configure Retry ////////////////////\n    MultiDbConfig.RetryConfig commandRetry = multiDbConfig.getCommandRetry();\n    this.retryConfig = buildRetryConfig(commandRetry);\n\n    ////////////// Configure Circuit Breaker ////////////////////\n    MultiDbConfig.CircuitBreakerConfig failureDetector = multiDbConfig.getFailureDetector();\n    this.circuitBreakerConfig = buildCircuitBreakerConfig(failureDetector, multiDbConfig);\n\n    ////////////// Configure Database Map ////////////////////\n    DatabaseConfig[] databaseConfigs = multiDbConfig.getDatabaseConfigs();\n\n    // Now add databases - health checks will start but events will be queued\n    for (DatabaseConfig config : databaseConfigs) {\n      addDatabaseInternal(multiDbConfig, config);\n    }\n\n    // Initialize StatusTracker for waiting on health check results\n    StatusTracker statusTracker = new StatusTracker(healthStatusManager);\n\n    // Wait for initial health check results and select active database based on weights\n    activeDatabase = waitForInitializationPolicy(statusTracker);\n\n    // Mark initialization as complete - handleHealthStatusChange can now process events\n    initializationComplete = true;\n    Database temp = activeDatabase;\n    if (!temp.isHealthy()) {\n      // Race condition: Direct assignment to 'activeDatabase' is not thread safe because\n      // 'onHealthStatusChange' may execute concurrently once 'initializationComplete'\n      // is set to true.\n      // Simple rule is to never assign value of 'activeDatabase' outside of\n      // 'activeDatabaseChangeLock' once the 'initializationComplete' is done.\n      waitForInitializationPolicy(statusTracker);\n      switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, temp);\n    }\n    this.fallbackExceptionList = multiDbConfig.getFallbackExceptionList();\n\n    // Start periodic failback checker\n    if (multiDbConfig.isFailbackSupported()) {\n      long failbackInterval = multiDbConfig.getFailbackCheckInterval();\n      failbackScheduler.scheduleAtFixedRate(this::periodicFailbackCheck, failbackInterval,\n        failbackInterval, TimeUnit.MILLISECONDS);\n    }\n  }\n\n  /**\n   * Builds a Resilience4j RetryConfig from Jedis MultiDbConfig.RetryConfig.\n   * @param commandRetry the Jedis retry configuration\n   * @return configured Resilience4j RetryConfig\n   */\n  private RetryConfig buildRetryConfig(redis.clients.jedis.MultiDbConfig.RetryConfig commandRetry) {\n    RetryConfig.Builder builder = RetryConfig.custom();\n\n    builder.maxAttempts(commandRetry.getMaxAttempts());\n    builder.intervalFunction(IntervalFunction.ofExponentialBackoff(commandRetry.getWaitDuration(),\n      commandRetry.getExponentialBackoffMultiplier()));\n    builder.failAfterMaxAttempts(false); // JedisConnectionException will be thrown\n    builder.retryExceptions(commandRetry.getIncludedExceptionList().stream().toArray(Class[]::new));\n\n    List<Class> ignoreExceptions = commandRetry.getIgnoreExceptionList();\n    if (ignoreExceptions != null) {\n      builder.ignoreExceptions(ignoreExceptions.stream().toArray(Class[]::new));\n    }\n\n    return builder.build();\n  }\n\n  /**\n   * Builds Resilience4j CircuitBreakerConfig from Jedis CircuitBreakerConfig.\n   * @param failureDetector the Jedis circuit breaker configuration\n   * @param multiDbConfig the multi-database configuration (for adapter)\n   * @return configured Resilience4j CircuitBreakerConfig\n   */\n  private CircuitBreakerConfig buildCircuitBreakerConfig(\n      MultiDbConfig.CircuitBreakerConfig failureDetector, MultiDbConfig multiDbConfig) {\n    CircuitBreakerConfig.Builder builder = CircuitBreakerConfig.custom();\n\n    CircuitBreakerThresholdsAdapter adapter = new CircuitBreakerThresholdsAdapter(multiDbConfig);\n    builder.minimumNumberOfCalls(adapter.getMinimumNumberOfCalls());\n    builder.failureRateThreshold(adapter.getFailureRateThreshold());\n    builder.slidingWindowSize(adapter.getSlidingWindowSize());\n    builder.slidingWindowType(adapter.getSlidingWindowType());\n\n    builder.recordExceptions(\n      failureDetector.getIncludedExceptionList().stream().toArray(Class[]::new));\n    builder.automaticTransitionFromOpenToHalfOpenEnabled(false); // State transitions are forced.\n                                                                 // No half open states are used\n\n    List<Class> ignoreExceptions = failureDetector.getIgnoreExceptionList();\n    if (ignoreExceptions != null) {\n      builder.ignoreExceptions(ignoreExceptions.stream().toArray(Class[]::new));\n    }\n\n    return builder.build();\n  }\n\n  /**\n   * Adds a new database endpoint to the provider.\n   * @param databaseConfig the configuration for the new database\n   * @throws JedisValidationException if the endpoint already exists\n   */\n  public void add(DatabaseConfig databaseConfig) {\n    if (databaseConfig == null) {\n      throw new JedisValidationException(\"DatabaseConfig must not be null\");\n    }\n\n    Endpoint endpoint = databaseConfig.getEndpoint();\n    if (databaseMap.containsKey(endpoint)) {\n      throw new JedisValidationException(\n          \"Endpoint \" + endpoint + \" already exists in the provider\");\n    }\n\n    activeDatabaseChangeLock.lock();\n    try {\n      addDatabaseInternal(multiDbConfig, databaseConfig);\n    } finally {\n      activeDatabaseChangeLock.unlock();\n    }\n  }\n\n  /**\n   * Removes a database endpoint from the provider.\n   * @param endpoint the endpoint to remove\n   * @throws JedisValidationException if the endpoint doesn't exist or is the last remaining\n   *           endpoint\n   */\n  public void remove(Endpoint endpoint) {\n    if (endpoint == null) {\n      throw new JedisValidationException(\"Endpoint must not be null\");\n    }\n\n    if (!databaseMap.containsKey(endpoint)) {\n      throw new JedisValidationException(\n          \"Endpoint \" + endpoint + \" does not exist in the provider\");\n    }\n\n    if (databaseMap.size() < 2) {\n      throw new JedisValidationException(\"Cannot remove the last remaining endpoint\");\n    }\n    log.debug(\"Removing endpoint {}\", endpoint);\n\n    Map.Entry<Endpoint, Database> notificationData = null;\n    activeDatabaseChangeLock.lock();\n    try {\n      Database databaseToRemove = databaseMap.get(endpoint);\n      boolean isActiveDatabase = (activeDatabase == databaseToRemove);\n\n      if (isActiveDatabase) {\n        log.info(\"Active database is being removed. Finding a new active database...\");\n        Map.Entry<Endpoint, Database> candidate = findWeightedHealthyDatabaseToIterate(\n          databaseToRemove);\n        if (candidate != null) {\n          Database selectedDatabase = candidate.getValue();\n          if (setActiveDatabase(selectedDatabase, true)) {\n            log.info(\"New active database set to {}\", candidate.getKey());\n            notificationData = candidate;\n          }\n        } else {\n          throw new JedisException(\n              \"Database can not be removed due to no healthy database available to switch!\");\n        }\n      }\n\n      // Remove from health status manager first\n      healthStatusManager.unregisterListener(endpoint, this::onHealthStatusChange);\n      healthStatusManager.remove(endpoint);\n\n      // Remove from database map\n      databaseMap.remove(endpoint);\n\n      // Close the database resources\n      if (databaseToRemove != null) {\n        databaseToRemove.setDisabled(true);\n        databaseToRemove.close();\n      }\n    } finally {\n      activeDatabaseChangeLock.unlock();\n    }\n    if (notificationData != null) {\n      onDatabaseSwitch(SwitchReason.FORCED, notificationData.getKey(), notificationData.getValue());\n    }\n  }\n\n  /**\n   * Internal method to add a database configuration. This method is not thread-safe and should be\n   * called within appropriate locks.\n   */\n  private void addDatabaseInternal(MultiDbConfig multiDbConfig, DatabaseConfig config) {\n    if (databaseMap.containsKey(config.getEndpoint())) {\n      throw new JedisValidationException(\n          \"Endpoint \" + config.getEndpoint() + \" already exists in the provider\");\n    }\n\n    String databaseId = \"database:\" + config.getEndpoint();\n\n    Retry retry = RetryRegistry.of(retryConfig).retry(databaseId);\n\n    Retry.EventPublisher retryPublisher = retry.getEventPublisher();\n    retryPublisher.onRetry(event -> log.warn(String.valueOf(event)));\n    retryPublisher.onError(event -> log.error(String.valueOf(event)));\n\n    CircuitBreaker circuitBreaker = CircuitBreakerRegistry.of(circuitBreakerConfig)\n        .circuitBreaker(databaseId);\n\n    CircuitBreaker.EventPublisher circuitBreakerEventPublisher = circuitBreaker.getEventPublisher();\n    circuitBreakerEventPublisher.onCallNotPermitted(event -> log.error(String.valueOf(event)));\n    circuitBreakerEventPublisher.onError(event -> log.error(String.valueOf(event)));\n    circuitBreakerEventPublisher.onFailureRateExceeded(event -> log.error(String.valueOf(event)));\n    circuitBreakerEventPublisher.onSlowCallRateExceeded(event -> log.error(String.valueOf(event)));\n\n    TrackingConnectionPool pool = TrackingConnectionPool.builder()\n        .hostAndPort(hostPort(config.getEndpoint())).clientConfig(config.getJedisClientConfig())\n        .poolConfig(config.getConnectionPoolConfig()).build();\n\n    Database database;\n    StrategySupplier strategySupplier = config.getHealthCheckStrategySupplier();\n    if (strategySupplier != null) {\n      HealthCheckStrategy hcs = strategySupplier.get(hostPort(config.getEndpoint()),\n        config.getJedisClientConfig());\n      // Register listeners BEFORE adding databases to avoid missing events\n      healthStatusManager.registerListener(config.getEndpoint(), this::onHealthStatusChange);\n      HealthCheck hc = healthStatusManager.add(config.getEndpoint(), hcs);\n      database = new Database(config.getEndpoint(), pool, retry, hc, circuitBreaker,\n          config.getWeight(), multiDbConfig);\n    } else {\n      database = new Database(config.getEndpoint(), pool, retry, circuitBreaker, config.getWeight(),\n          multiDbConfig);\n    }\n\n    databaseMap.put(config.getEndpoint(), database);\n\n    // this is the place where we listen tracked errors and check if\n    // thresholds are exceeded for the database\n    circuitBreakerEventPublisher.onError(event -> {\n      database.evaluateThresholds(false);\n    });\n  }\n\n  private HostAndPort hostPort(Endpoint endpoint) {\n    return new HostAndPort(endpoint.getHost(), endpoint.getPort());\n  }\n\n  /**\n   * Handles health status changes for databases. This method is called by the health status manager\n   * when the health status of a database changes.\n   */\n  @VisibleForTesting\n  void onHealthStatusChange(HealthStatusChangeEvent eventArgs) {\n    Endpoint endpoint = eventArgs.getEndpoint();\n    HealthStatus newStatus = eventArgs.getNewStatus();\n    log.debug(\"Health status changed for {} from {} to {}\", endpoint, eventArgs.getOldStatus(),\n      newStatus);\n    Database databaseWithHealthChange = databaseMap.get(endpoint);\n\n    if (databaseWithHealthChange == null) return;\n\n    if (initializationComplete) {\n      if (!newStatus.isHealthy() && databaseWithHealthChange == activeDatabase) {\n        databaseWithHealthChange.setGracePeriod();\n        switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, databaseWithHealthChange);\n      }\n    }\n  }\n\n  /**\n   * Waits for initial health check results and selects the first healthy database based on weight\n   * priority. Uses the configured initialization policy to determine when enough databases are\n   * available.\n   * @param statusTracker the status tracker to use for waiting on health check results\n   * @return the first healthy database found, ordered by weight (highest first)\n   * @throws JedisConnectionException (or JedisValidationException in unlikely cases) if\n   *           initialization fails according to the policy\n   */\n  @VisibleForTesting\n  Database waitForInitializationPolicy(StatusTracker statusTracker) {\n    InitializationPolicy policy = multiDbConfig.getInitializationPolicy();\n    log.debug(\"Waiting for initialization policy {} to complete for {} configured databases\",\n      policy.getClass().getSimpleName(), databaseMap.size());\n\n    // Evaluate immediately with the current statuses\n    ConnectionInitializationContext ctx = new ConnectionInitializationContext(databaseMap,\n        healthStatusManager);\n    Decision decision = ctx.conformsTo(policy);\n    log.debug(\"Initial policy evaluation: {} with context: {}\", decision, ctx);\n\n    if (decision == Decision.FAIL) {\n      throw new JedisConnectionException(\n          \"Initialization failed due to initialization policy: \" + ctx);\n    }\n\n    // Sort databases by weight in descending order\n    List<Map.Entry<Endpoint, Database>> sortedDatabases = databaseMap.entrySet().stream()\n        .sorted(Map.Entry.<Endpoint, Database> comparingByValue(\n          Comparator.comparing(Database::getWeight).reversed()))\n        .collect(Collectors.toList());\n\n    // Check databases in weight order\n    for (Map.Entry<Endpoint, Database> entry : sortedDatabases) {\n      Endpoint endpoint = entry.getKey();\n      Database database = entry.getValue();\n\n      log.info(\"Evaluating database {} (weight: {})\", endpoint, database.getWeight());\n\n      // Check if health checks are enabled for this endpoint\n      if (healthStatusManager.hasHealthCheck(endpoint)) {\n        log.info(\"Health checks enabled for {}, waiting for result\", endpoint);\n        // Wait for this database's health status to be determined\n        statusTracker.waitForHealthStatus(endpoint);\n      } else {\n        // No health check configured - assume healthy\n        log.debug(\"No health check configured for database {}, defaulting to HEALTHY\", endpoint);\n      }\n\n      ConnectionInitializationContext evalCtx = new ConnectionInitializationContext(databaseMap,\n          healthStatusManager);\n      Decision d = evalCtx.conformsTo(policy);\n      log.debug(\"Policy evaluation after {}: {}\", endpoint, d);\n      if (d == Decision.SUCCESS) {\n        return selectBestAvailableDatabase(sortedDatabases);\n      }\n      if (d == Decision.FAIL) {\n        throw new JedisConnectionException(\n            \"Initialization failed due to initialization policy: \" + evalCtx);\n      }\n      // else CONTINUE -> move to the next pending endpoint\n    }\n\n    // All databases are unhealthy\n    throw new JedisConnectionException(\n        \"All configured databases are unhealthy. Cannot initialize MultiDbConnectionProvider.\");\n  }\n\n  /**\n   * Selects the best available (healthy) database based on weight priority.\n   * @param sortedDatabases the list of databases sorted by weight in descending order\n   * @return the highest-weighted healthy database\n   * @throws JedisConnectionException if no healthy database is available\n   */\n  private Database selectBestAvailableDatabase(\n      List<Map.Entry<Endpoint, Database>> sortedDatabases) {\n    log.info(\"Selecting initial database from {} configured databases\", sortedDatabases.size());\n\n    // Select first healthy database in weight order\n    for (Map.Entry<Endpoint, Database> entry : sortedDatabases) {\n      Endpoint endpoint = entry.getKey();\n      Database database = entry.getValue();\n\n      HealthStatus status;\n\n      // Check if health checks are enabled for this endpoint\n      if (healthStatusManager.hasHealthCheck(endpoint)) {\n        status = healthStatusManager.getHealthStatus(endpoint);\n      } else {\n        // No health check configured - assume healthy\n        log.info(\"No health check configured for database {}, defaulting to HEALTHY\", endpoint);\n        status = HealthStatus.HEALTHY;\n      }\n\n      if (status.isHealthy()) {\n        log.info(\"Found healthy database: {} (weight: {})\", endpoint, database.getWeight());\n        return database;\n      } else {\n        log.info(\"Database {} is unhealthy, trying next database\", endpoint);\n      }\n    }\n\n    // No healthy database found (should not happen if policy succeeded)\n    throw new JedisConnectionException(\n        \"No healthy database available after initialization policy succeeded.\");\n  }\n\n  /**\n   * Periodic failback checker - runs at configured intervals to check for failback opportunities\n   */\n  @VisibleForTesting\n  void periodicFailbackCheck() {\n    try {\n      // Find the best candidate database for failback\n      Map.Entry<Endpoint, Database> bestCandidate = null;\n      float bestWeight = activeDatabase.getWeight();\n\n      for (Map.Entry<Endpoint, Database> entry : databaseMap.entrySet()) {\n        Database database = entry.getValue();\n\n        // Skip if this is already the active database\n        if (database == activeDatabase) {\n          continue;\n        }\n\n        // Skip if database is not healthy\n        if (!database.isHealthy()) {\n          continue;\n        }\n\n        // This database is a valid candidate\n        if (database.getWeight() > bestWeight) {\n          bestCandidate = entry;\n          bestWeight = database.getWeight();\n        }\n      }\n\n      // Perform failback if we found a better candidate\n      if (bestCandidate != null) {\n        Database selectedDatabase = bestCandidate.getValue();\n        log.info(\"Performing failback from {} to {} (higher weight database available)\",\n          activeDatabase.getCircuitBreaker().getName(),\n          selectedDatabase.getCircuitBreaker().getName());\n        if (setActiveDatabase(selectedDatabase, true)) {\n          onDatabaseSwitch(SwitchReason.FAILBACK, bestCandidate.getKey(), selectedDatabase);\n        }\n      }\n    } catch (Exception e) {\n      log.error(\"Error during periodic failback check\", e);\n    }\n  }\n\n  Endpoint switchToHealthyDatabase(SwitchReason reason, Database iterateFrom) {\n    Map.Entry<Endpoint, Database> databaseToIterate = findWeightedHealthyDatabaseToIterate(\n      iterateFrom);\n    if (databaseToIterate == null) {\n      // throws exception anyway since not able to iterate\n      handleNoHealthyDatabase();\n    }\n\n    Database database = databaseToIterate.getValue();\n    boolean changed = setActiveDatabase(database, false);\n    if (!changed) return null;\n    failoverAttemptCount.set(0);\n    onDatabaseSwitch(reason, databaseToIterate.getKey(), database);\n    return databaseToIterate.getKey();\n  }\n\n  private void handleNoHealthyDatabase() {\n    int max = multiDbConfig.getMaxNumFailoverAttempts();\n    log.error(\"No healthy database available to switch to\");\n    if (failoverAttemptCount.get() > max) {\n      throw new JedisPermanentlyNotAvailableException();\n    }\n\n    int currentAttemptCount = markAsFreeze() ? failoverAttemptCount.incrementAndGet()\n        : failoverAttemptCount.get();\n\n    if (currentAttemptCount > max) {\n      throw new JedisPermanentlyNotAvailableException();\n    }\n    throw new JedisTemporarilyNotAvailableException();\n  }\n\n  private boolean markAsFreeze() {\n    long until = failoverFreezeUntil.get();\n    long now = System.currentTimeMillis();\n    if (until <= now) {\n      long nextUntil = now + multiDbConfig.getDelayInBetweenFailoverAttempts();\n      if (failoverFreezeUntil.compareAndSet(until, nextUntil)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * Asserts that the active database is operable. If not, throws an exception.\n   * <p>\n   * This method is called by the circuit breaker command executor before executing a command.\n   * @throws JedisPermanentlyNotAvailableException if the there is no operable database and the max\n   *           number of failover attempts has been exceeded.\n   * @throws JedisTemporarilyNotAvailableException if the there is no operable database and the max\n   *           number of failover attempts has not been exceeded.\n   */\n  @VisibleForTesting\n  public void assertOperability() {\n    Database current = activeDatabase;\n    if (!current.isHealthy() && !this.canIterateFrom(current)) {\n      handleNoHealthyDatabase();\n    }\n  }\n\n  private static Comparator<Map.Entry<Endpoint, Database>> maxByWeight = Map.Entry\n      .<Endpoint, Database> comparingByValue(Comparator.comparing(Database::getWeight));\n\n  private static Predicate<Map.Entry<Endpoint, Database>> filterByHealth = c -> c.getValue()\n      .isHealthy();\n\n  private Map.Entry<Endpoint, Database> findWeightedHealthyDatabaseToIterate(Database iterateFrom) {\n    return databaseMap.entrySet().stream().filter(filterByHealth)\n        .filter(entry -> entry.getValue() != iterateFrom).max(maxByWeight).orElse(null);\n  }\n\n  /**\n   * Design decision was made to defer responsibility for cross-replication validation to the user.\n   * Alternatively there was discussion to handle cross-database replication validation by setting a\n   * key/value pair per hashslot in the active connection (with a TTL) and subsequently reading it\n   * from the target connection.\n   */\n  public void validateTargetConnection(Endpoint endpoint) {\n    Database database = databaseMap.get(endpoint);\n    validateTargetConnection(database);\n  }\n\n  private void validateTargetConnection(Database database) {\n    CircuitBreaker circuitBreaker = database.getCircuitBreaker();\n\n    State originalState = circuitBreaker.getState();\n    try {\n      // Transitions the state machine to a CLOSED state, allowing state transition, metrics and\n      // event publishing. Safe since the activeDatabase has not yet been changed and therefore no\n      // traffic will be routed yet\n      circuitBreaker.transitionToClosedState();\n\n      try (Connection targetConnection = database.getConnection()) {\n        targetConnection.ping();\n      }\n    } catch (Exception e) {\n\n      // If the original state was FORCED_OPEN, then transition it back which stops state\n      // transition, metrics and\n      // event publishing\n      if (State.FORCED_OPEN.equals(originalState)) circuitBreaker.transitionToForcedOpenState();\n\n      throw new JedisValidationException(circuitBreaker.getName()\n          + \" failed to connect. Please check configuration and try again.\", e);\n    }\n  }\n\n  /**\n   * Returns the set of all configured endpoints.\n   * @return the set of all configured endpoints\n   */\n  public Set<Endpoint> getEndpoints() {\n    return new HashSet<>(databaseMap.keySet());\n  }\n\n  public void setActiveDatabase(Endpoint endpoint) {\n    if (endpoint == null) {\n      throw new JedisValidationException(\n          \"Provided endpoint is null. Please use one from the configuration\");\n    }\n    Database database = databaseMap.get(endpoint);\n    if (database == null) {\n      throw new JedisValidationException(\"Provided endpoint: \" + endpoint + \" is not within \"\n          + \"the configured endpoints. Please use one from the configuration\");\n    }\n    if (setActiveDatabase(database, true)) {\n      onDatabaseSwitch(SwitchReason.FORCED, endpoint, database);\n    }\n  }\n\n  public void forceActiveDatabase(Endpoint endpoint, long forcedActiveDuration) {\n    Database database = databaseMap.get(endpoint);\n\n    if (database == null) {\n      throw new JedisValidationException(\"Provided endpoint: \" + endpoint + \" is not within \"\n          + \"the configured endpoints. Please use one from the configuration\");\n    }\n\n    database.clearGracePeriod();\n    if (!database.isHealthy()) {\n      throw new JedisValidationException(\"Provided endpoint: \" + endpoint\n          + \" is not healthy. Please consider a healthy endpoint from the configuration\");\n    }\n    databaseMap.entrySet().stream().forEach(entry -> {\n      if (entry.getKey() != endpoint) {\n        entry.getValue().setGracePeriod(forcedActiveDuration);\n      }\n    });\n    setActiveDatabase(endpoint);\n  }\n\n  private boolean setActiveDatabase(Database database, boolean validateConnection) {\n    // Database database = databaseEntry.getValue();\n    // Field-level synchronization is used to avoid the edge case in which\n    // setActiveDatabase() is called at the same time\n    activeDatabaseChangeLock.lock();\n    Database oldDatabase;\n    try {\n\n      // Allows an attempt to reset the current database from a FORCED_OPEN to CLOSED state in the\n      // event that no failover is possible\n      if (activeDatabase == database && !database.isCBForcedOpen()) return false;\n\n      if (validateConnection) validateTargetConnection(database);\n\n      String originalDatabaseName = getDatabaseCircuitBreaker().getName();\n\n      if (activeDatabase == database)\n        log.warn(\"Database/database endpoint '{}' successfully closed its circuit breaker\",\n          originalDatabaseName);\n      else log.warn(\"Database/database endpoint successfully updated from '{}' to '{}'\",\n        originalDatabaseName, database.circuitBreaker.getName());\n      oldDatabase = activeDatabase;\n      activeDatabase = database;\n    } finally {\n      activeDatabaseChangeLock.unlock();\n    }\n    boolean switched = oldDatabase != database;\n    if (switched && this.multiDbConfig.isFastFailover()) {\n      log.info(\"Forcing disconnect of all active connections in old database: {}\",\n        oldDatabase.circuitBreaker.getName());\n      oldDatabase.forceDisconnect();\n      log.info(\"Disconnected all active connections in old database: {}\",\n        oldDatabase.circuitBreaker.getName());\n\n    }\n    return switched;\n\n  }\n\n  @Override\n  public void close() {\n    if (healthStatusManager != null) {\n      healthStatusManager.close();\n    }\n\n    // Shutdown the failback scheduler\n    failbackScheduler.shutdown();\n    try {\n      if (!failbackScheduler.awaitTermination(1, TimeUnit.SECONDS)) {\n        failbackScheduler.shutdownNow();\n      }\n    } catch (InterruptedException e) {\n      failbackScheduler.shutdownNow();\n      Thread.currentThread().interrupt();\n    }\n\n    // Close all database connection pools\n    for (Database database : databaseMap.values()) {\n      database.close();\n    }\n  }\n\n  @Override\n  public Connection getConnection() {\n    return activeDatabase.getConnection();\n  }\n\n  public Connection getConnection(Endpoint endpoint) {\n    return databaseMap.get(endpoint).getConnection();\n  }\n\n  @Override\n  public Connection getConnection(CommandArguments args) {\n    return activeDatabase.getConnection();\n  }\n\n  @Override\n  public Map<?, Pool<Connection>> getConnectionMap() {\n    ConnectionPool connectionPool = activeDatabase.connectionPool;\n    return Collections.singletonMap(connectionPool.getFactory(), connectionPool);\n  }\n\n  public Database getDatabase() {\n    return activeDatabase;\n  }\n\n  @VisibleForTesting\n  public Database getDatabase(Endpoint endpoint) {\n    return databaseMap.get(endpoint);\n  }\n\n  /**\n   * Returns the active endpoint\n   * <p>\n   * Active endpoint is the one which is currently being used for all operations. It can change at\n   * any time due to health checks, failover, failback, etc.\n   * @return the active database endpoint\n   */\n  public Endpoint getActiveEndpoint() {\n    return activeDatabase.getEndpoint();\n  }\n\n  /**\n   * Returns the health state of the given endpoint\n   * @param endpoint the endpoint to check\n   * @return the health status of the endpoint\n   */\n  public boolean isHealthy(Endpoint endpoint) {\n    Database database = getDatabase(endpoint);\n    if (database == null) {\n      throw new JedisValidationException(\n          \"Endpoint \" + endpoint + \" does not exist in the provider\");\n    }\n    return database.isHealthy();\n  }\n\n  public CircuitBreaker getDatabaseCircuitBreaker() {\n    return activeDatabase.getCircuitBreaker();\n  }\n\n  /**\n   * Indicates the final database endpoint (connection pool), according to the pre-configured list\n   * provided at startup via the MultiDbConfig, is unavailable and therefore no further failover is\n   * possible. Users can manually failback to an available database\n   */\n  public boolean canIterateFrom(Database iterateFrom) {\n    Map.Entry<Endpoint, Database> e = findWeightedHealthyDatabaseToIterate(iterateFrom);\n    return e != null;\n  }\n\n  public void onDatabaseSwitch(SwitchReason reason, Endpoint endpoint, Database database) {\n    if (databaseSwitchListener != null) {\n      DatabaseSwitchEvent eventArgs = new DatabaseSwitchEvent(reason, endpoint, database);\n      databaseSwitchListener.accept(eventArgs);\n    }\n  }\n\n  public void setDatabaseSwitchListener(Consumer<DatabaseSwitchEvent> databaseSwitchListener) {\n    this.databaseSwitchListener = databaseSwitchListener;\n  }\n\n  public List<Class<? extends Throwable>> getFallbackExceptionList() {\n    return fallbackExceptionList;\n  }\n\n  public static class Database {\n\n    private TrackingConnectionPool connectionPool;\n    private final Retry retry;\n    private final CircuitBreaker circuitBreaker;\n    private float weight;\n    private final HealthCheck healthCheck;\n    private final MultiDbConfig multiDbConfig;\n    private boolean disabled = false;\n    private final Endpoint endpoint;\n\n    // Grace period tracking\n    private volatile long gracePeriodEndsAt = 0;\n    private final Logger log = LoggerFactory.getLogger(getClass());\n\n    private Database(Endpoint endpoint, TrackingConnectionPool connectionPool, Retry retry,\n        CircuitBreaker circuitBreaker, float weight, MultiDbConfig multiDbConfig) {\n\n      this.endpoint = endpoint;\n      this.connectionPool = connectionPool;\n      this.retry = retry;\n      this.circuitBreaker = circuitBreaker;\n      this.weight = weight;\n      this.multiDbConfig = multiDbConfig;\n      this.healthCheck = null;\n    }\n\n    private Database(Endpoint endpoint, TrackingConnectionPool connectionPool, Retry retry,\n        HealthCheck hc, CircuitBreaker circuitBreaker, float weight, MultiDbConfig multiDbConfig) {\n\n      this.endpoint = endpoint;\n      this.connectionPool = connectionPool;\n      this.retry = retry;\n      this.circuitBreaker = circuitBreaker;\n      this.weight = weight;\n      this.multiDbConfig = multiDbConfig;\n      this.healthCheck = hc;\n    }\n\n    public Endpoint getEndpoint() {\n      return endpoint;\n    }\n\n    public Connection getConnection() {\n      if (!isHealthy()) throw new JedisConnectionException(\"Database is not healthy\");\n      if (connectionPool.isClosed()) {\n        connectionPool = TrackingConnectionPool.from(connectionPool);\n      }\n      return connectionPool.getResource();\n    }\n\n    @VisibleForTesting\n    public ConnectionPool getConnectionPool() {\n      return connectionPool;\n    }\n\n    public Retry getRetry() {\n      return retry;\n    }\n\n    public CircuitBreaker getCircuitBreaker() {\n      return circuitBreaker;\n    }\n\n    public HealthStatus getHealthStatus() {\n      return healthCheck == null ? HealthStatus.HEALTHY : healthCheck.getStatus();\n    }\n\n    /**\n     * Assigned weight for this database\n     */\n    public float getWeight() {\n      return weight;\n    }\n\n    public void setWeight(float weight) {\n      JedisAsserts.isTrue(weight > 0, \"Database weight must be greater than 0\");\n      this.weight = weight;\n    }\n\n    public boolean isCBForcedOpen() {\n      if (circuitBreaker.getState() == State.FORCED_OPEN && !isInGracePeriod()) {\n        log.info(\n          \"Transitioning circuit breaker from FORCED_OPEN to CLOSED state due to end of grace period!\");\n        circuitBreaker.transitionToClosedState();\n      }\n      return circuitBreaker.getState() == CircuitBreaker.State.FORCED_OPEN;\n    }\n\n    public boolean isHealthy() {\n      return getHealthStatus().isHealthy() && !isCBForcedOpen() && !disabled && !isInGracePeriod();\n    }\n\n    public boolean retryOnFailover() {\n      return multiDbConfig.isRetryOnFailover();\n    }\n\n    public int getCircuitBreakerMinNumOfFailures() {\n      return multiDbConfig.getFailureDetector().getMinNumOfFailures();\n    }\n\n    public float getCircuitBreakerFailureRateThreshold() {\n      return multiDbConfig.getFailureDetector().getFailureRateThreshold();\n    }\n\n    public boolean isDisabled() {\n      return disabled;\n    }\n\n    public void setDisabled(boolean disabled) {\n      this.disabled = disabled;\n    }\n\n    /**\n     * Checks if the da is currently in grace period\n     */\n    public boolean isInGracePeriod() {\n      return System.currentTimeMillis() < gracePeriodEndsAt;\n    }\n\n    /**\n     * Sets the grace period for this database\n     */\n    public void setGracePeriod() {\n      setGracePeriod(multiDbConfig.getGracePeriod());\n    }\n\n    public void setGracePeriod(long gracePeriod) {\n      long endTime = System.currentTimeMillis() + gracePeriod;\n      if (endTime < gracePeriodEndsAt) return;\n      gracePeriodEndsAt = endTime;\n    }\n\n    public void clearGracePeriod() {\n      gracePeriodEndsAt = 0;\n    }\n\n    /**\n     * Whether failback is supported by client\n     */\n    public boolean isFailbackSupported() {\n      return multiDbConfig.isFailbackSupported();\n    }\n\n    public void forceDisconnect() {\n      connectionPool.forceDisconnect();\n    }\n\n    public void close() {\n      connectionPool.close();\n    }\n\n    void evaluateThresholds(boolean lastFailRecorded) {\n      if (getCircuitBreaker().getState() == State.CLOSED\n          && isThresholdsExceeded(this, lastFailRecorded)) {\n        getCircuitBreaker().transitionToOpenState();\n      }\n    }\n\n    private static boolean isThresholdsExceeded(Database database, boolean lastFailRecorded) {\n      Metrics metrics = database.getCircuitBreaker().getMetrics();\n      // ATTENTION: this is to increment fails in regard to the current call that is failing,\n      // DO NOT remove the increment, it will change the behaviour in case of initial requests to\n      // database fail\n      int fails = metrics.getNumberOfFailedCalls() + (lastFailRecorded ? 0 : 1);\n      int succ = metrics.getNumberOfSuccessfulCalls();\n      if (fails >= database.getCircuitBreakerMinNumOfFailures()) {\n        float ratePercentThreshold = database.getCircuitBreakerFailureRateThreshold();// 0..100\n        int total = fails + succ;\n        if (total == 0) return false;\n        float failureRatePercent = (fails * 100.0f) / total;\n        return failureRatePercent >= ratePercentThreshold;\n      }\n      return false;\n    }\n\n    @Override\n    public String toString() {\n      return circuitBreaker.getName() + \"{\" + \"connectionPool=\" + connectionPool + \", retry=\"\n          + retry + \", circuitBreaker=\" + circuitBreaker + \", weight=\" + weight + \", healthStatus=\"\n          + getHealthStatus() + \", multiDbConfig=\" + multiDbConfig + '}';\n    }\n\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/MultiDbConnectionSupplier.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker.State;\nimport io.github.resilience4j.decorators.Decorators;\nimport io.github.resilience4j.decorators.Decorators.DecorateSupplier;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\n\n/**\n * ConnectionProvider with built-in retry, circuit-breaker, and failover to another /database\n * endpoint. With this executor users can seamlessly failover to Disaster Recovery (DR), Backup, and\n * Active-Active cluster(s) by using simple configuration\n */\n@Experimental\npublic class MultiDbConnectionSupplier extends MultiDbFailoverBase {\n\n  public MultiDbConnectionSupplier(MultiDbConnectionProvider provider) {\n    super(provider);\n  }\n\n  public Connection getConnection() {\n    Database database = provider.getDatabase(); // Pass this by reference for thread safety\n\n    DecorateSupplier<Connection> supplier = Decorators\n        .ofSupplier(() -> this.handleGetConnection(database));\n\n    supplier.withRetry(database.getRetry());\n    supplier.withCircuitBreaker(database.getCircuitBreaker());\n    supplier.withFallback(provider.getFallbackExceptionList(),\n      e -> this.handleDatabaseFailover(database));\n\n    try {\n      return supplier.decorate().get();\n    } catch (Exception e) {\n      if (database.getCircuitBreaker().getState() == State.OPEN && isActiveDatabase(database)) {\n        databaseFailover(database);\n      }\n      throw e;\n    }\n  }\n\n  /**\n   * Functional interface wrapped in retry and circuit breaker logic to handle happy path scenarios\n   */\n  private Connection handleGetConnection(Database database) {\n    Connection connection = database.getConnection();\n    connection.ping();\n    return connection;\n  }\n\n  /**\n   * Functional interface wrapped in retry and circuit breaker logic to handle open circuit breaker\n   * failure scenarios\n   */\n  private Connection handleDatabaseFailover(Database database) {\n\n    databaseFailover(database);\n\n    // Recursive call to the initiating method so the operation can be retried on the next database\n    // connection\n    return getConnection();\n  }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/MultiDbFailoverBase.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker;\n\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\nimport redis.clients.jedis.util.IOUtils;\n\n/**\n * @author Allen Terleto (aterleto)\n *         <p>\n *         Base class for CommandExecutor with built-in retry, circuit-breaker, and failover to\n *         another database endpoint. With this executor users can seamlessly failover to Disaster\n *         Recovery (DR), Backup, and Active-Active cluster(s) by using simple configuration\n *         <p>\n */\n@Experimental\npublic class MultiDbFailoverBase implements AutoCloseable {\n  private final Lock lock = new ReentrantLock(true);\n\n  protected final MultiDbConnectionProvider provider;\n\n  public MultiDbFailoverBase(MultiDbConnectionProvider provider) {\n    this.provider = provider;\n  }\n\n  @Override\n  public void close() {\n    IOUtils.closeQuietly(this.provider);\n  }\n\n  /**\n   * Functional interface wrapped in retry and circuit breaker logic to handle open circuit breaker\n   * failure scenarios\n   */\n  protected void databaseFailover(Database database) {\n    lock.lock();\n\n    CircuitBreaker circuitBreaker = database.getCircuitBreaker();\n    try {\n      // Check state to handle race conditions since () is\n      // non-idempotent\n      if (!CircuitBreaker.State.FORCED_OPEN.equals(circuitBreaker.getState())) {\n\n        // Transitions state machine to a FORCED_OPEN state, stopping state transition, metrics and\n        // event publishing.\n        // To recover/transition from this forced state the user will need to manually failback\n\n        Database activeDatabase = provider.getDatabase();\n        // This should be possible only if active database is switched from by other reasons than\n        // circuit breaker, just before circuit breaker triggers\n        if (activeDatabase != database) {\n          return;\n        }\n\n        database.setGracePeriod();\n        circuitBreaker.transitionToForcedOpenState();\n\n        // Iterating the active database will allow subsequent calls to the executeCommand() to use\n        // the next\n        // database's connection pool - according to the configuration's prioritization/order/weight\n        provider.switchToHealthyDatabase(SwitchReason.CIRCUIT_BREAKER, database);\n      }\n      // this check relies on the fact that many failover attempts can hit with the same CB,\n      // only the first one will trigger a failover, and make the CB FORCED_OPEN.\n      // when the rest reaches here, the active database is already the next one, and should be\n      // different than\n      // active CB. If its the same one and there are no more databases to failover to, then throw\n      // an\n      // exception\n      else if (database == provider.getDatabase()) {\n        provider.switchToHealthyDatabase(SwitchReason.CIRCUIT_BREAKER, database);\n      }\n      // Ignore exceptions since we are already in a failure state\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  boolean isActiveDatabase(Database database) {\n    Database activeDatabase = provider.getDatabase();\n    return activeDatabase != null && activeDatabase.equals(database);\n  }\n\n  static boolean isCircuitBreakerTrackedException(Exception e, Database database) {\n    return database.getCircuitBreaker().getCircuitBreakerConfig().getRecordExceptionPredicate()\n        .test(e);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/MultiDbPipeline.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.io.Closeable;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * This is high memory dependent solution as all the appending commands will be hold in memory until\n * {@link MultiDbPipeline#sync() SYNC} (or {@link MultiDbPipeline#close() CLOSE}) gets called.\n */\n@Experimental\npublic class MultiDbPipeline extends AbstractPipeline implements Closeable {\n\n  private final MultiDbConnectionSupplier failoverProvider;\n  private final Queue<KeyValue<CommandArguments, Response<?>>> commands = new LinkedList<>();\n\n  @Deprecated\n  public MultiDbPipeline(MultiDbConnectionProvider pooledProvider) {\n    super(new CommandObjects());\n\n    this.failoverProvider = new MultiDbConnectionSupplier(pooledProvider);\n\n    try (Connection connection = failoverProvider.getConnection()) {\n      RedisProtocol proto = connection.getRedisProtocol();\n      if (proto != null) this.commandObjects.setProtocol(proto);\n    }\n  }\n\n  public MultiDbPipeline(MultiDbConnectionProvider pooledProvider, CommandObjects commandObjects) {\n    super(commandObjects);\n    this.failoverProvider = new MultiDbConnectionSupplier(pooledProvider);\n  }\n\n  @Override\n  protected final <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n    CommandArguments args = commandObject.getArguments();\n    Response<T> response = new Response<>(commandObject.getBuilder());\n    commands.add(KeyValue.of(args, response));\n    return response;\n  }\n\n  @Override\n  public void close() {\n    sync();\n    // connection prepared and closed (in try-with-resources) in sync()\n  }\n\n  /**\n   * Synchronize pipeline by reading all responses. This operation close the pipeline. In order to\n   * get return values from pipelined commands, capture the different Response&lt;?&gt; of the\n   * commands you execute.\n   */\n  @Override\n  public void sync() {\n    if (commands.isEmpty()) return;\n\n    try (Connection connection = failoverProvider.getConnection()) {\n\n      commands.forEach((command) -> connection.sendCommand(command.getKey()));\n      // following connection.getMany(int) flushes anyway, so no flush here.\n\n      List<Object> unformatted = connection.getMany(commands.size());\n      unformatted.forEach((rawReply) -> commands.poll().getValue().set(rawReply));\n    }\n  }\n\n  public Response<Long> waitReplicas(int replicas, long timeout) {\n    return appendCommand(commandObjects.waitReplicas(replicas, timeout));\n  }\n\n  public Response<KeyValue<Long, Long>> waitAOF(long numLocal, long numReplicas, long timeout) {\n    return appendCommand(commandObjects.waitAOF(numLocal, numReplicas, timeout));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static redis.clients.jedis.Protocol.Command.DISCARD;\nimport static redis.clients.jedis.Protocol.Command.EXEC;\nimport static redis.clients.jedis.Protocol.Command.MULTI;\nimport static redis.clients.jedis.Protocol.Command.UNWATCH;\n\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * This is high memory dependent solution as all the appending commands will be hold in memory.\n */\n@Experimental\npublic class MultiDbTransaction extends AbstractTransaction {\n\n  private static final Builder<?> NO_OP_BUILDER = BuilderFactory.RAW_OBJECT;\n\n  private final MultiDbConnectionSupplier failoverProvider;\n  private final AtomicInteger extraCommandCount = new AtomicInteger();\n  private final Queue<KeyValue<CommandArguments, Response<?>>> commands = new LinkedList<>();\n\n  private boolean inWatch = false;\n  private boolean inMulti = false;\n\n  /**\n   * A MULTI command will be added to be sent to server. WATCH/UNWATCH/MULTI commands must not be\n   * called with this object.\n   * @param provider\n   */\n  @Deprecated\n  public MultiDbTransaction(MultiDbConnectionProvider provider) {\n    this(provider, true);\n  }\n\n  /**\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   * @param provider\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   */\n  @Deprecated\n  public MultiDbTransaction(MultiDbConnectionProvider provider, boolean doMulti) {\n    this.failoverProvider = new MultiDbConnectionSupplier(provider);\n\n    try (Connection connection = failoverProvider.getConnection()) {\n      RedisProtocol proto = connection.getRedisProtocol();\n      if (proto != null) this.commandObjects.setProtocol(proto);\n    }\n\n    if (doMulti) multi();\n  }\n\n  /**\n   * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should\n   * be {@code doMulti=false}.\n   * @param provider\n   * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI\n   * @param commandObjects command objects\n   */\n  public MultiDbTransaction(MultiDbConnectionProvider provider, boolean doMulti,\n      CommandObjects commandObjects) {\n    super(commandObjects);\n    this.failoverProvider = new MultiDbConnectionSupplier(provider);\n\n    if (doMulti) multi();\n  }\n\n  @Override\n  public final void multi() {\n    appendCommand(new CommandObject<>(new CommandArguments(MULTI), NO_OP_BUILDER));\n    extraCommandCount.incrementAndGet();\n    inMulti = true;\n  }\n\n  /**\n   * @param keys\n   * @return {@code null}\n   */\n  @Override\n  public final String watch(String... keys) {\n    appendCommand(commandObjects.watch(keys));\n    extraCommandCount.incrementAndGet();\n    inWatch = true;\n    return null;\n  }\n\n  /**\n   * @param keys\n   * @return {@code null}\n   */\n  @Override\n  public final String watch(byte[]... keys) {\n    appendCommand(commandObjects.watch(keys));\n    extraCommandCount.incrementAndGet();\n    inWatch = true;\n    return null;\n  }\n\n  /**\n   * @return {@code null}\n   */\n  @Override\n  public final String unwatch() {\n    appendCommand(new CommandObject<>(new CommandArguments(UNWATCH), NO_OP_BUILDER));\n    extraCommandCount.incrementAndGet();\n    inWatch = false;\n    return null;\n  }\n\n  @Override\n  protected final <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n    CommandArguments args = commandObject.getArguments();\n    Response<T> response = new Response<>(commandObject.getBuilder());\n    commands.add(KeyValue.of(args, response));\n    return response;\n  }\n\n  @Override\n  public void close() {\n    clear();\n  }\n\n  private void clear() {\n    if (inMulti) {\n      discard();\n    } else if (inWatch) {\n      unwatch();\n    }\n  }\n\n  @Override\n  public final List<Object> exec() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"EXEC without MULTI\");\n    }\n\n    try (Connection connection = failoverProvider.getConnection()) {\n\n      commands.forEach((command) -> connection.sendCommand(command.getKey()));\n      // following connection.getMany(int) flushes anyway, so no flush here.\n\n      // ignore QUEUED (or ERROR)\n      connection.getMany(commands.size());\n\n      // remove extra response builders\n      for (int idx = 0; idx < extraCommandCount.get(); ++idx) {\n        commands.poll();\n      }\n\n      connection.sendCommand(EXEC);\n\n      List<Object> unformatted = connection.getObjectMultiBulkReply();\n      if (unformatted == null) {\n        commands.clear();\n        return null;\n      }\n\n      List<Object> formatted = new ArrayList<>(unformatted.size() - extraCommandCount.get());\n      for (Object rawReply : unformatted) {\n        try {\n          Response<?> response = commands.poll().getValue();\n          response.set(rawReply);\n          formatted.add(response.get());\n        } catch (JedisDataException e) {\n          formatted.add(e);\n        }\n      }\n      return formatted;\n\n    } finally {\n      inMulti = false;\n      inWatch = false;\n    }\n  }\n\n  @Override\n  public final String discard() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"DISCARD without MULTI\");\n    }\n\n    try (Connection connection = failoverProvider.getConnection()) {\n\n      commands.forEach((command) -> connection.sendCommand(command.getKey()));\n      // following connection.getMany(int) flushes anyway, so no flush here.\n\n      // ignore QUEUED (or ERROR)\n      connection.getMany(commands.size());\n\n      connection.sendCommand(DISCARD);\n\n      return connection.getStatusCodeReply();\n    } finally {\n      inMulti = false;\n      inWatch = false;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/PingStrategy.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.MultiDbConfig.StrategySupplier;\n\npublic class PingStrategy implements HealthCheckStrategy {\n  private static final int MAX_HEALTH_CHECK_POOL_SIZE = 2;\n\n  private final RedisClient jedis;\n  private final HealthCheckStrategy.Config config;\n\n  public PingStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig) {\n    this(hostAndPort, jedisClientConfig, HealthCheckStrategy.Config.create());\n  }\n\n  public PingStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig,\n      HealthCheckStrategy.Config config) {\n    GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();\n    poolConfig.setMaxTotal(MAX_HEALTH_CHECK_POOL_SIZE);\n    this.jedis = RedisClient.builder().hostAndPort(hostAndPort).clientConfig(jedisClientConfig)\n        .poolConfig(poolConfig).build();\n    this.config = config;\n  }\n\n  @Override\n  public int getInterval() {\n    return config.getInterval();\n  }\n\n  @Override\n  public int getTimeout() {\n    return config.getTimeout();\n  }\n\n  @Override\n  public int getNumProbes() {\n    return config.getNumProbes();\n  }\n\n  @Override\n  public ProbingPolicy getPolicy() {\n    return config.getPolicy();\n  }\n\n  @Override\n  public int getDelayInBetweenProbes() {\n    return config.getDelayInBetweenProbes();\n  }\n\n  @Override\n  public HealthStatus doHealthCheck(Endpoint endpoint) {\n    return \"PONG\".equals(jedis.ping()) ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;\n  }\n\n  @Override\n  public void close() {\n    jedis.close();\n  }\n\n  public static final StrategySupplier DEFAULT = PingStrategy::new;\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/ProbingPolicy.java",
    "content": "package redis.clients.jedis.mcf;\n\npublic interface ProbingPolicy {\n\n  public enum Decision {\n    CONTINUE, SUCCESS, FAIL\n  }\n\n  Decision evaluate(ProbeContext data);\n\n  public static interface ProbeContext {\n\n    public int getRemainingProbes();\n\n    public int getSuccesses();\n\n    public int getFails();\n\n  }\n\n  public static class BuiltIn {\n    public static final ProbingPolicy ALL_SUCCESS = new AllSuccessPolicy();\n    public static final ProbingPolicy ANY_SUCCESS = new AnySuccessPolicy();\n    public static final ProbingPolicy MAJORITY_SUCCESS = new MajoritySuccessPolicy();\n\n    /*\n     * All probes need to be healthy. If a database doesn’t pass the health check for numProbes\n     * times, then the check wasn’t successful. This means you can stop probing after you got the\n     * first failed health check (e.g., timeout or unhealthy status)\n     */\n    private static class AllSuccessPolicy implements ProbingPolicy {\n      @Override\n      public Decision evaluate(ProbeContext ctx) {\n        // Any failure means overall failure\n        if (ctx.getFails() > 0) {\n          return Decision.FAIL;\n        }\n\n        // All probes completed successfully\n        if (ctx.getRemainingProbes() == 0) {\n          return Decision.SUCCESS;\n        }\n\n        return Decision.CONTINUE;\n      }\n    }\n\n    /*\n     * A database is healthy if at least one probe returned a healthy status. You can stop probing\n     * as soon as you got the first healthy status.\n     */\n    private static class AnySuccessPolicy implements ProbingPolicy {\n      @Override\n      public Decision evaluate(ProbeContext ctx) {\n        // Any success means overall success\n        if (ctx.getSuccesses() > 0) {\n          return Decision.SUCCESS;\n        }\n\n        // All probes completed with failures\n        if (ctx.getRemainingProbes() == 0) {\n          return Decision.FAIL;\n        }\n\n        return Decision.CONTINUE;\n      }\n    }\n\n    /*\n     * A database is healthy if the majority of probes returned ‘healthy’. This means you can stop\n     * probing as soon as the majority can’t be guaranteed any more (e.g., you have 4 probes and 2\n     * of them failed), or as soon as the majority is reached (e.g., 3 out of 4 were healthy)\n     */\n    private static class MajoritySuccessPolicy implements ProbingPolicy {\n      @Override\n      public Decision evaluate(ProbeContext ctx) {\n        int total = ctx.getRemainingProbes() + ctx.getSuccesses() + ctx.getFails();\n        int required = (total / 2) + 1;\n\n        // Early success\n        if (ctx.getSuccesses() >= required) {\n          return Decision.SUCCESS;\n        }\n\n        // Early failure - impossible to reach majority\n        int maxPossibleSuccesses = ctx.getSuccesses() + ctx.getRemainingProbes();\n        if (maxPossibleSuccesses < required) {\n          return Decision.FAIL;\n        }\n\n        // Final evaluation\n        if (ctx.getRemainingProbes() == 0) {\n          return ctx.getSuccesses() >= required ? Decision.SUCCESS : Decision.FAIL;\n        }\n\n        return Decision.CONTINUE;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/RedisRestAPI.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.security.GeneralSecurityException;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.function.Supplier;\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.RedisCredentials;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.SslVerifyMode;\nimport redis.clients.jedis.annots.Internal;\n\n/**\n * Helper class to check the availability of a Redis database\n */\n@Internal\nclass RedisRestAPI {\n\n  private static final Logger log = LoggerFactory.getLogger(RedisRestAPI.class);\n  private static final String BDBS_URL = \"https://%s:%s/v1/bdbs?fields=uid,endpoints\";\n  private static final String AVAILABILITY_URL = \"https://%s:%s/v1/bdbs/%s/availability\";\n  private static final String LAGAWARE_AVAILABILITY_URL = \"https://%s:%s/v1/bdbs/%s/availability?extend_check=lag\";\n\n  private static final int DEFAULT_TIMEOUT_MS = 1000;\n\n  private final Endpoint endpoint;\n  private final Supplier<RedisCredentials> credentialsSupplier;\n  private final int timeoutMs;\n  private final SslOptions sslOptions;\n  private String bdbsUri;\n\n  public RedisRestAPI(Endpoint endpoint, Supplier<RedisCredentials> credentialsSupplier) {\n    this(endpoint, credentialsSupplier, DEFAULT_TIMEOUT_MS);\n  }\n\n  public RedisRestAPI(Endpoint endpoint, Supplier<RedisCredentials> credentialsSupplier,\n      int timeoutMs) {\n    this(endpoint, credentialsSupplier, timeoutMs, null);\n  }\n\n  public RedisRestAPI(Endpoint endpoint, Supplier<RedisCredentials> credentialsSupplier,\n      int timeoutMs, SslOptions sslOptions) {\n    this.endpoint = endpoint;\n    this.credentialsSupplier = credentialsSupplier;\n    this.timeoutMs = timeoutMs;\n    this.sslOptions = sslOptions;\n  }\n\n  public List<RedisRestAPI.BdbInfo> getBdbs() throws IOException {\n    if (bdbsUri == null) {\n      bdbsUri = String.format(BDBS_URL, endpoint.getHost(), endpoint.getPort());\n    }\n\n    HttpURLConnection conn = null;\n    try {\n      conn = createConnection(bdbsUri, \"GET\", credentialsSupplier.get());\n      conn.setRequestProperty(\"Accept\", \"application/json\");\n      int code = conn.getResponseCode();\n      String responseBody = readResponse(conn);\n      if (code != 200) {\n        throw new IOException(\"Unexpected response code '\" + code + \"' for getBdbs: '\"\n            + responseBody + \"' from '\" + bdbsUri + \"'\");\n      }\n      return parseBdbInfoFromResponse(responseBody);\n    } finally {\n      if (conn != null) conn.disconnect();\n    }\n  }\n\n  public boolean checkBdbAvailability(String uid, boolean lagAware) throws IOException {\n    return checkBdbAvailability(uid, lagAware, null);\n  }\n\n  public boolean checkBdbAvailability(String uid, boolean extendedCheckEnabled,\n      Long availabilityLagToleranceMs) throws IOException {\n    String availabilityUri;\n    if (extendedCheckEnabled) {\n      // Use extended check with lag validation\n      availabilityUri = String.format(LAGAWARE_AVAILABILITY_URL, endpoint.getHost(),\n        endpoint.getPort(), uid);\n      if (availabilityLagToleranceMs != null) {\n        availabilityUri = availabilityUri + \"&availability_lag_tolerance_ms=\"\n            + availabilityLagToleranceMs;\n      }\n    } else {\n      // Use standard datapath validation only\n      availabilityUri = String.format(AVAILABILITY_URL, endpoint.getHost(), endpoint.getPort(),\n        uid);\n    }\n\n    HttpURLConnection conn = null;\n    try {\n      conn = createConnection(availabilityUri, \"GET\", credentialsSupplier.get());\n      conn.setRequestProperty(\"Accept\", \"application/json\");\n      int code = conn.getResponseCode();\n      if (code == 200) {\n        return true;\n      }\n      String body = readResponse(conn);\n      log.warn(\"Availability check for {} returned body='{}' from '{}'\", uid, body,\n        availabilityUri);\n    } finally {\n      if (conn != null) conn.disconnect();\n    }\n    return false;\n  }\n\n  HttpURLConnection createConnection(String urlString, String method, RedisCredentials credentials)\n      throws IOException {\n    URL url = new URL(urlString);\n    HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n\n    // Configure SSL if this is an HTTPS connection and SSL options are provided\n    if (connection instanceof HttpsURLConnection && sslOptions != null) {\n      HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;\n      try {\n        SSLContext sslContext = sslOptions.createSslContext();\n        httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory());\n\n        if (sslOptions.getSslVerifyMode() == SslVerifyMode.CA\n            || sslOptions.getSslVerifyMode() == SslVerifyMode.INSECURE) {\n          httpsConnection.setHostnameVerifier((h, s) -> true); // skip hostname check\n        }\n      } catch (GeneralSecurityException e) {\n        throw new IOException(\"SSL configuration failed\", e);\n      }\n    }\n\n    connection.setRequestMethod(method);\n    connection.setConnectTimeout(timeoutMs);\n    connection.setReadTimeout(timeoutMs);\n    connection.setRequestProperty(\"Authorization\", getAuthenticationHeader(credentials));\n\n    return connection;\n  }\n\n  // This is just to avoid putting password chars directly into a string\n  private static String getAuthenticationHeader(RedisCredentials credentials) throws IOException {\n    // Build Basic auth without creating a password String\n    final char[] pass = credentials.getPassword() != null ? credentials.getPassword() : new char[0];\n    final String user = credentials.getUser() != null ? credentials.getUser() : \"\";\n    final byte[] userBytes = user.getBytes(StandardCharsets.UTF_8);\n\n    // Encode char[] directly to UTF-8 bytes\n    java.nio.ByteBuffer bb = StandardCharsets.UTF_8.encode(java.nio.CharBuffer.wrap(pass));\n    byte[] passBytes = new byte[bb.remaining()];\n    bb.get(passBytes);\n\n    // user \":\" password\n    byte[] combined = new byte[userBytes.length + 1 + passBytes.length];\n    System.arraycopy(userBytes, 0, combined, 0, userBytes.length);\n    combined[userBytes.length] = (byte) ':';\n    System.arraycopy(passBytes, 0, combined, userBytes.length + 1, passBytes.length);\n\n    String encodedAuth = Base64.getEncoder().encodeToString(combined);\n\n    // Clear sensitive buffers\n    java.util.Arrays.fill(passBytes, (byte) 0);\n    java.util.Arrays.fill(combined, (byte) 0);\n    return \"Basic \" + encodedAuth;\n  }\n\n  /**\n   * Parses the response body and extracts BDB information including endpoints.\n   * @param responseBody the JSON response containing BDBs with endpoints\n   * @return list of BDB information objects\n   */\n  static List<RedisRestAPI.BdbInfo> parseBdbInfoFromResponse(String responseBody) {\n    JsonArray bdbs = JsonParser.parseString(responseBody).getAsJsonArray();\n    List<RedisRestAPI.BdbInfo> bdbInfoList = new ArrayList<>();\n\n    for (JsonElement bdbElement : bdbs) {\n      if (!bdbElement.isJsonObject()) {\n        continue;\n      }\n\n      JsonObject bdb = bdbElement.getAsJsonObject();\n      if (!bdb.has(\"uid\")) {\n        continue;\n      }\n\n      String bdbId = bdb.get(\"uid\").getAsString();\n      List<RedisRestAPI.EndpointInfo> endpoints = new ArrayList<>();\n\n      if (bdb.has(\"endpoints\") && bdb.get(\"endpoints\").isJsonArray()) {\n        JsonArray endpointsArray = bdb.getAsJsonArray(\"endpoints\");\n\n        for (JsonElement endpointElement : endpointsArray) {\n          if (!endpointElement.isJsonObject()) {\n            continue;\n          }\n\n          JsonObject endpoint = endpointElement.getAsJsonObject();\n\n          // Extract addr array\n          List<String> addrList = new ArrayList<>();\n          if (endpoint.has(\"addr\") && endpoint.get(\"addr\").isJsonArray()) {\n            JsonArray addresses = endpoint.getAsJsonArray(\"addr\");\n            for (JsonElement addrElement : addresses) {\n              if (addrElement.isJsonPrimitive()) {\n                addrList.add(addrElement.getAsString());\n              }\n            }\n          }\n\n          // Extract other fields\n          String dnsName = endpoint.has(\"dns_name\") ? endpoint.get(\"dns_name\").getAsString() : null;\n          Integer port = endpoint.has(\"port\") ? endpoint.get(\"port\").getAsInt() : null;\n          String endpointUid = endpoint.has(\"uid\") ? endpoint.get(\"uid\").getAsString() : null;\n\n          endpoints.add(new RedisRestAPI.EndpointInfo(addrList, dnsName, port, endpointUid));\n        }\n      }\n\n      bdbInfoList.add(new RedisRestAPI.BdbInfo(bdbId, endpoints));\n    }\n\n    return bdbInfoList;\n  }\n\n  static String readResponse(HttpURLConnection connection) throws IOException {\n    InputStream inputStream = null;\n    try {\n      inputStream = connection.getInputStream();\n      if (inputStream == null) {\n        inputStream = connection.getErrorStream();\n      }\n    } catch (IOException e) {\n      // If there's an error, try to read from error stream\n      inputStream = connection.getErrorStream();\n    }\n    if (inputStream == null) {\n      throw new IOException(\n          \"No response stream available from server (code=\" + connection.getResponseCode() + \")\");\n    }\n\n    StringBuilder response = new StringBuilder();\n    byte[] buffer = new byte[1024];\n    int bytesRead;\n\n    while ((bytesRead = inputStream.read(buffer)) != -1) {\n      response.append(new String(buffer, 0, bytesRead, StandardCharsets.UTF_8));\n    }\n\n    inputStream.close();\n    return response.toString();\n  }\n\n  /**\n   * Information about a Redis Enterprise BDB (database) including its endpoints.\n   */\n  static class BdbInfo {\n    private final String uid;\n    private final List<EndpointInfo> endpoints;\n\n    BdbInfo(String uid, List<EndpointInfo> endpoints) {\n      this.uid = uid;\n      this.endpoints = endpoints;\n    }\n\n    String getUid() {\n      return uid;\n    }\n\n    List<EndpointInfo> getEndpoints() {\n      return endpoints;\n    }\n\n    /**\n     * Find the BDB that matches the given database host by comparing endpoints.\n     * @param bdbs list of BDB information\n     * @param dbHost the database host to match\n     * @return the matching BDB, or null if no match is found\n     */\n    static BdbInfo findMatchingBdb(List<BdbInfo> bdbs, String dbHost) {\n      for (BdbInfo bdb : bdbs) {\n        for (EndpointInfo endpoint : bdb.getEndpoints()) {\n          // First check dns_name\n          if (dbHost.equals(endpoint.getDnsName())) {\n            return bdb;\n          }\n\n          // Then check addr array for IP addresses\n          if (endpoint.getAddr() != null) {\n            for (String addr : endpoint.getAddr()) {\n              if (dbHost.equals(addr)) {\n                return bdb;\n              }\n            }\n          }\n        }\n      }\n      return null; // No matching BDB found\n    }\n\n    @Override\n    public String toString() {\n      return \"BdbInfo{\" + \"uid='\" + uid + '\\'' + \", endpoints=\" + endpoints + '}';\n    }\n  }\n\n  /**\n   * Information about a Redis Enterprise BDB endpoint.\n   */\n  static class EndpointInfo {\n    private final List<String> addr;\n    private final String dnsName;\n    private final Integer port;\n    private final String uid;\n\n    EndpointInfo(List<String> addr, String dnsName, Integer port, String uid) {\n      this.addr = addr;\n      this.dnsName = dnsName;\n      this.port = port;\n      this.uid = uid;\n    }\n\n    List<String> getAddr() {\n      return addr;\n    }\n\n    String getDnsName() {\n      return dnsName;\n    }\n\n    Integer getPort() {\n      return port;\n    }\n\n    String getUid() {\n      return uid;\n    }\n\n    @Override\n    public String toString() {\n      return \"EndpointInfo{\" + \"addr=\" + addr + \", dnsName='\" + dnsName + '\\'' + \", port=\" + port\n          + \", uid='\" + uid + '\\'' + '}';\n    }\n  }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/StatusTracker.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisValidationException;\n\n/**\n * StatusTracker is responsible for tracking and waiting for health status changes for specific\n * endpoints. It provides an event-driven approach to wait for health status transitions from\n * UNKNOWN to either HEALTHY or UNHEALTHY.\n */\npublic class StatusTracker {\n\n  private final HealthStatusManager healthStatusManager;\n\n  public StatusTracker(HealthStatusManager healthStatusManager) {\n    this.healthStatusManager = healthStatusManager;\n  }\n\n  /**\n   * Waits for a specific endpoint's health status to be determined (not UNKNOWN). Uses event-driven\n   * approach with CountDownLatch to avoid polling.\n   * @param endpoint the endpoint to wait for\n   * @return the determined health status (HEALTHY or UNHEALTHY)\n   * @throws JedisConnectionException if interrupted while waiting\n   */\n  public HealthStatus waitForHealthStatus(Endpoint endpoint) {\n    // First check if status is already determined\n    HealthStatus currentStatus = healthStatusManager.getHealthStatus(endpoint);\n    if (currentStatus != HealthStatus.UNKNOWN) {\n      return currentStatus;\n    }\n\n    // Set up event-driven waiting\n    final CountDownLatch latch = new CountDownLatch(1);\n    final AtomicReference<HealthStatus> resultStatus = new AtomicReference<>();\n\n    // Create a temporary listener for this specific endpoint\n    HealthStatusListener tempListener = new HealthStatusListener() {\n      @Override\n      public void onStatusChange(HealthStatusChangeEvent event) {\n        if (event.getEndpoint().equals(endpoint) && event.getNewStatus() != HealthStatus.UNKNOWN) {\n          resultStatus.set(event.getNewStatus());\n          latch.countDown();\n        }\n      }\n    };\n\n    // Register the temporary listener\n    healthStatusManager.registerListener(endpoint, tempListener);\n\n    try {\n      // Double-check status after registering listener (race condition protection)\n      currentStatus = healthStatusManager.getHealthStatus(endpoint);\n      if (currentStatus != HealthStatus.UNKNOWN) {\n        return currentStatus;\n      }\n\n      // Wait for the health status change event\n      // just for safety to not block indefinitely\n      boolean completed = latch.await(healthStatusManager.getMaxWaitFor(endpoint),\n        TimeUnit.MILLISECONDS);\n      if (!completed) {\n        throw new JedisValidationException(\"Timeout while waiting for health check result\");\n      }\n      return resultStatus.get();\n\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      throw new JedisConnectionException(\"Interrupted while waiting for health check result\", e);\n    } finally {\n      // Clean up: unregister the temporary listener\n      healthStatusManager.unregisterListener(endpoint, tempListener);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/SwitchReason.java",
    "content": "package redis.clients.jedis.mcf;\n\npublic enum SwitchReason {\n  HEALTH_CHECK, CIRCUIT_BREAKER, FAILBACK, FORCED\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/TrackingConnectionPool.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.apache.commons.pool2.PooledObject;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionFactory;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.DefaultJedisSocketFactory;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.csc.CacheConnection;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\npublic class TrackingConnectionPool extends ConnectionPool {\n\n  private static class FailFastConnectionFactory extends ConnectionFactory {\n    private volatile boolean failFast = false;\n    private final Set<Connection> factoryTrackedObjects = ConcurrentHashMap.newKeySet();\n\n    public FailFastConnectionFactory(ConnectionFactory.Builder factoryBuilder,\n        JedisClientConfig clientConfig) {\n      super(factoryBuilder\n          .connectionBuilder(createCustomConnectionBuilder(factoryBuilder, clientConfig)));\n    }\n\n    private static Connection.Builder createCustomConnectionBuilder(\n        ConnectionFactory.Builder factoryBuilder, JedisClientConfig clientConfig) {\n      Connection.Builder connBuilder = factoryBuilder.getCache() == null ? Connection.builder()\n          : CacheConnection.builder(factoryBuilder.getCache());\n\n      return connBuilder.socketFactory(factoryBuilder.getJedisSocketFactory())\n          .clientConfig(clientConfig);\n    }\n\n    @Override\n    public PooledObject<Connection> makeObject() throws Exception {\n      if (failFast) {\n        throw new JedisConnectionException(\"Failed to create connection!\");\n      }\n      try {\n        PooledObject<Connection> object = super.makeObject();\n        factoryTrackedObjects.add(object.getObject());\n        try {\n          object.getObject().initializeFromClientConfig();\n        } finally {\n          factoryTrackedObjects.remove(object.getObject());\n        }\n        // this can make a marginal improvement on fast failover duration!\n        if (failFast) {\n          object.getObject().close();\n          throw new JedisConnectionException(\"Failed to create connection!\");\n        }\n        return object;\n      } catch (JedisConnectionException e) {\n        throw e;\n      } catch (Exception e) {\n        throw new JedisConnectionException(e);\n      }\n    }\n\n    public void forceDisconnect() {\n      for (Connection connection : factoryTrackedObjects) {\n        try {\n          connection.forceDisconnect();\n        } catch (Exception e) {\n          log.warn(\"Error while force disconnecting connection: \" + connection.toIdentityString(),\n            e);\n        }\n      }\n    }\n\n  }\n\n  public static class Builder {\n    private HostAndPort hostAndPort;\n    private JedisClientConfig clientConfig;\n    private GenericObjectPoolConfig<Connection> poolConfig;\n\n    public Builder hostAndPort(HostAndPort hostAndPort) {\n      this.hostAndPort = hostAndPort;\n      return this;\n    }\n\n    public Builder clientConfig(JedisClientConfig clientConfig) {\n      this.clientConfig = clientConfig;\n      return this;\n    }\n\n    public Builder poolConfig(GenericObjectPoolConfig<Connection> poolConfig) {\n      this.poolConfig = poolConfig;\n      return this;\n    }\n\n    public TrackingConnectionPool build() {\n      applyDefaults();\n      return new TrackingConnectionPool(this);\n    }\n\n    private void applyDefaults() {\n      if (clientConfig == null) {\n        clientConfig = DefaultJedisClientConfig.builder().build();\n      }\n      if (poolConfig == null) {\n        poolConfig = new GenericObjectPoolConfig<>();\n      }\n    }\n  }\n\n  private static final Logger log = LoggerFactory.getLogger(TrackingConnectionPool.class);\n\n  private final HostAndPort hostAndPort;\n  private final JedisClientConfig clientConfig;\n  private final GenericObjectPoolConfig<Connection> poolConfig;\n  private final AtomicInteger numWaiters = new AtomicInteger();\n  private final Set<Connection> poolTrackedObjects = ConcurrentHashMap.newKeySet();\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private TrackingConnectionPool(Builder builder) {\n    super(createfailFastFactory(builder),\n        builder.poolConfig != null ? builder.poolConfig : new GenericObjectPoolConfig<>());\n\n    this.hostAndPort = builder.hostAndPort;\n    this.clientConfig = builder.clientConfig;\n    this.poolConfig = builder.poolConfig;\n    this.attachAuthenticationListener(builder.clientConfig.getAuthXManager());\n  }\n\n  private static FailFastConnectionFactory createfailFastFactory(Builder poolBuilder) {\n    ConnectionFactory.Builder factoryBuilder = ConnectionFactory.builder()\n        .clientConfig(poolBuilder.clientConfig).socketFactory(\n          new DefaultJedisSocketFactory(poolBuilder.hostAndPort, poolBuilder.clientConfig));\n    return new FailFastConnectionFactory(factoryBuilder, poolBuilder.clientConfig);\n  }\n\n  public static TrackingConnectionPool from(TrackingConnectionPool existing) {\n    return builder().hostAndPort(existing.hostAndPort).clientConfig(existing.clientConfig)\n        .poolConfig(existing.poolConfig).build();\n  }\n\n  @Override\n  public Connection getResource() {\n    try {\n      numWaiters.incrementAndGet();\n      Connection conn = super.getResource();\n      poolTrackedObjects.add(conn);\n      return conn;\n    } catch (Exception e) {\n      if (this.isClosed()) {\n        throw new JedisConnectionException(\"Pool is closed!\", e);\n      }\n      throw e;\n    } finally {\n      numWaiters.decrementAndGet();\n    }\n  }\n\n  @Override\n  public void returnResource(final Connection resource) {\n    super.returnResource(resource);\n    poolTrackedObjects.remove(resource);\n  }\n\n  @Override\n  public void returnBrokenResource(final Connection resource) {\n    super.returnBrokenResource(resource);\n    poolTrackedObjects.remove(resource);\n  }\n\n  public void forceDisconnect() {\n    this.close();\n    ((FailFastConnectionFactory) this.getFactory()).failFast = true;\n    int numOfConnected = poolTrackedObjects.size();\n    // we need to wait for all waiters to leave before we are done with disconnecting the\n    // connections, since a user app thread might be either;\n    // - in the middle of a factory call(create|init) and not yet show up in poolTrackedObjects\n    // - blocked on an exhausted pool, waiting for resources to return back pool\n    while (numWaiters.get() > 0 || numOfConnected > 0) {\n      this.clear();\n      ((FailFastConnectionFactory) this.getFactory()).forceDisconnect();\n      numOfConnected = 0;\n      for (Connection connection : poolTrackedObjects) {\n        try {\n          if (connection.isConnected()) {\n            numOfConnected++;\n          }\n          connection.forceDisconnect();\n        } catch (Exception e) {\n          log.warn(\"Error while force disconnecting connection: \" + connection.toIdentityString(),\n            e);\n        }\n      }\n      try {\n        // this is just to yield the thread for a fair share of CPU\n        Thread.sleep(1);\n      } catch (InterruptedException e) {\n      }\n    }\n    ((FailFastConnectionFactory) this.getFactory()).failFast = false;\n  }\n\n  @Override\n  public void close() {\n    this.destroy();\n    this.detachAuthenticationListener();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/mcf/package-info.java",
    "content": "/**\n * This package contains the classes that are related to Active-Active cluster(s) and Multi-Cluster\n * failover.\n */\n@Experimental\npackage redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.annots.Experimental;"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/BaseGetExParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\n/**\n * Abstract base class for setting expiration parameters for Redis GET commands.\n * This class provides methods to set various expiration options such as EX, PX, EXAT, PXAT, and PERSIST.\n *\n * <ul>\n *   <li>{@link #ex(long)} - Set the specified expire time, in seconds.</li>\n *   <li>{@link #px(long)} - Set the specified expire time, in milliseconds.</li>\n *   <li>{@link #exAt(long)} - Set the specified Unix time at which the key will expire, in seconds.</li>\n *   <li>{@link #pxAt(long)} - Set the specified Unix time at which the key will expire, in milliseconds.</li>\n *   <li>{@link #persist()} - Remove the time to live associated with the key.</li>\n * </ul>\n * \n * @param <T> the type of the subclass extending this base class\n */\nabstract class BaseGetExParams<T extends BaseGetExParams> implements IParams {\n\n  private Keyword expiration;\n  private Long expirationValue;\n\n  private T expiration(Keyword type, Long value) {\n    this.expiration = type;\n    this.expirationValue = value;\n    return (T) this;\n  }\n\n  /**\n   * Set the specified expire time, in seconds.\n   * @return parameter object\n   */\n  public T ex(long secondsToExpire) {\n    return expiration(Keyword.EX, secondsToExpire);\n  }\n\n  /**\n   * Set the specified expire time, in milliseconds.\n   * @return parameter object\n   */\n  public T px(long millisecondsToExpire) {\n    return expiration(Keyword.PX, millisecondsToExpire);\n  }\n\n  /**\n   * Set the specified Unix time at which the key will expire, in seconds.\n   * @param seconds\n   * @return parameter object\n   */\n  public T exAt(long seconds) {\n    return expiration(Keyword.EXAT, seconds);\n  }\n\n  /**\n   * Set the specified Unix time at which the key will expire, in milliseconds.\n   * @param milliseconds\n   * @return parameter object\n   */\n  public T pxAt(long milliseconds) {\n    return expiration(Keyword.PXAT, milliseconds);\n  }\n\n  /**\n   * Remove the time to live associated with the key.\n   * @return parameter object\n   */\n  public T persist() {\n    return expiration(Keyword.PERSIST, null);\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (expiration != null) {\n      args.add(expiration);\n      if (expirationValue != null) {\n        args.add(expirationValue);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    BaseGetExParams that = (BaseGetExParams) o;\n    return expiration == that.expiration && Objects.equals(expirationValue, that.expirationValue);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(expiration, expirationValue);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/BaseSetExParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\n/**\n * BaseSetExParams is a base class for setting expiration parameters for Redis keys.\n * It provides methods to set expiration times in seconds or milliseconds, \n * as well as Unix timestamps for expiration.\n * \n * <ul>\n *   <li>{@link #ex(long)} - Set the specified expire time, in seconds.</li>\n *   <li>{@link #px(long)} - Set the specified expire time, in milliseconds.</li>\n *   <li>{@link #exAt(long)} - Set the specified Unix time at which the key will expire, in seconds.</li>\n *   <li>{@link #pxAt(long)} - Set the specified Unix time at which the key will expire, in milliseconds.</li>\n *   <li>{@link #keepTtl()} - Retain the time to live associated with the key.</li>\n * </ul>\n * \n * @param <T> the type of the subclass extending this base class\n */\nclass BaseSetExParams<T extends BaseSetExParams<T>> implements IParams {\n\n  private Keyword expiration;\n  private Long expirationValue;\n\n  private T expiration(Keyword type, Long value) {\n    this.expiration = type;\n    this.expirationValue = value;\n    return (T) this;\n  }\n\n  /**\n   * Set the specified expire time, in seconds.\n   * @param remainingSeconds\n   * @return params object\n   */\n  public T ex(long remainingSeconds) {\n    return expiration(Keyword.EX, remainingSeconds);\n  }\n\n  /**\n   * Set the specified expire time, in milliseconds.\n   * @param remainingMilliseconds\n   * @return params object\n   */\n  public T px(long remainingMilliseconds) {\n    return expiration(Keyword.PX, remainingMilliseconds);\n  }\n\n  /**\n   * Set the specified Unix time at which the key will expire, in seconds.\n   * @param timestampSeconds\n   * @return params object\n   */\n  public T exAt(long timestampSeconds) {\n    return expiration(Keyword.EXAT, timestampSeconds);\n  }\n\n  /**\n   * Set the specified Unix time at which the key will expire, in milliseconds.\n   * @param timestampMilliseconds\n   * @return params object\n   */\n  public T pxAt(long timestampMilliseconds) {\n    return expiration(Keyword.PXAT, timestampMilliseconds);\n  }\n\n  /**\n   * @deprecated Use {@link BaseSetExParams#keepTtl()}.\n   * @return params object\n   */\n  @Deprecated\n  public T keepttl() {\n    return keepTtl();\n  }\n\n  /**\n   * Retain the time to live associated with the key.\n   * @return params object\n   */\n  public T keepTtl() {\n    return expiration(Keyword.KEEPTTL, null);\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (expiration != null) {\n      args.add(expiration);\n      if (expirationValue != null) {\n        args.add(expirationValue);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    BaseSetExParams setParams = (BaseSetExParams) o;\n    return Objects.equals(expiration, setParams.expiration) \n      && Objects.equals(expirationValue, setParams.expirationValue);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(expiration, expirationValue);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/BitPosParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.args.BitCountOption;\n\nimport java.util.Objects;\n\npublic class BitPosParams implements IParams {\n\n  private Long start;\n  private Long end;\n  private BitCountOption modifier;\n\n  public BitPosParams() {\n  }\n\n  // TODO: deprecate ??\n  public BitPosParams(long start) {\n    this.start = start;\n  }\n\n  // TODO: deprecate ??\n  public BitPosParams(long start, long end) {\n    this(start);\n\n    this.end = end;\n  }\n\n  public static BitPosParams bitPosParams() {\n    return new BitPosParams();\n  }\n\n  public BitPosParams start(long start) {\n    this.start = start;\n    return this;\n  }\n\n  /**\n   * {@link BitPosParams#start(long) START} must be set for END option.\n   */\n  public BitPosParams end(long end) {\n    this.end = end;\n    return this;\n  }\n\n  /**\n   * Both {@link BitPosParams#start(long) START} and {@link BitPosParams#end(long) END} both must be\n   * set for MODIFIER option.\n   */\n  public BitPosParams modifier(BitCountOption modifier) {\n    this.modifier = modifier;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (start != null) {\n      args.add(start);\n      if (end != null) {\n        args.add(end);\n        if (modifier != null) {\n          args.add(modifier);\n        }\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    BitPosParams that = (BitPosParams) o;\n    return Objects.equals(start, that.start) && Objects.equals(end, that.end) && Objects.equals(modifier, that.modifier);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(start, end, modifier);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ClientKillParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.ArrayList;\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.ClientType;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class ClientKillParams implements IParams {\n\n  public static enum SkipMe {\n    YES, NO;\n  }\n\n  private final ArrayList<KeyValue<Keyword, Object>> params = new ArrayList<>();\n\n  public ClientKillParams() {\n  }\n\n  public static ClientKillParams clientKillParams() {\n    return new ClientKillParams();\n  }\n\n  private ClientKillParams addParam(Keyword key, Object value) {\n    params.add(KeyValue.of(key, value));\n    return this;\n  }\n\n  public ClientKillParams id(String clientId) {\n    return addParam(Keyword.ID, clientId);\n  }\n\n  public ClientKillParams id(byte[] clientId) {\n    return addParam(Keyword.ID, clientId);\n  }\n\n  public ClientKillParams type(ClientType type) {\n    return addParam(Keyword.TYPE, type);\n  }\n\n  public ClientKillParams addr(String ipPort) {\n    return addParam(Keyword.ADDR, ipPort);\n  }\n\n  public ClientKillParams addr(byte[] ipPort) {\n    return addParam(Keyword.ADDR, ipPort);\n  }\n\n  public ClientKillParams addr(String ip, int port) {\n    return addParam(Keyword.ADDR, ip + ':' + port);\n  }\n\n  public ClientKillParams skipMe(SkipMe skipMe) {\n    return addParam(Keyword.SKIPME, skipMe);\n  }\n\n  public ClientKillParams user(String username) {\n    return addParam(Keyword.USER, username);\n  }\n\n  public ClientKillParams laddr(String ipPort) {\n    return addParam(Keyword.LADDR, ipPort);\n  }\n\n  public ClientKillParams laddr(String ip, int port) {\n    return addParam(Keyword.LADDR, ip + ':' + port);\n  }\n\n  /**\n   * Kill clients older than {@code maxAge} seconds.\n   *\n   * @param maxAge Clients older than this number of seconds will be killed.\n   * @return The {@code ClientKillParams} instance, for call chaining.\n   */\n  public ClientKillParams maxAge(long maxAge) {\n    return addParam(Keyword.MAXAGE, maxAge);\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    params.forEach(kv -> args.add(kv.getKey()).add(kv.getValue()));\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ClientKillParams that = (ClientKillParams) o;\n    return Objects.equals(params, that.params);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(params);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/CommandListFilterByParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class CommandListFilterByParams implements IParams {\n\n  private String moduleName;\n  private String category;\n  private String pattern;\n\n  public static CommandListFilterByParams commandListFilterByParams() {\n    return new CommandListFilterByParams();\n  }\n\n  public CommandListFilterByParams filterByModule(String moduleName) {\n    this.moduleName = moduleName;\n    return this;\n  }\n\n  public CommandListFilterByParams filterByAclCat(String category) {\n    this.category = category;\n    return this;\n  }\n\n  public CommandListFilterByParams filterByPattern(String pattern) {\n    this.pattern = pattern;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(Keyword.FILTERBY);\n\n    if (moduleName != null && category == null && pattern == null) {\n      args.add(Keyword.MODULE);\n      args.add(moduleName);\n    } else if (moduleName == null && category != null && pattern == null) {\n      args.add(Keyword.ACLCAT);\n      args.add(category);\n    } else if (moduleName == null && category == null && pattern != null) {\n      args.add(Keyword.PATTERN);\n      args.add(pattern);\n    } else {\n      throw new IllegalArgumentException(\"Must choose exactly one filter in \"\n          + getClass().getSimpleName());\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    CommandListFilterByParams that = (CommandListFilterByParams) o;\n    return Objects.equals(moduleName, that.moduleName) && Objects.equals(category, that.category) && Objects.equals(pattern, that.pattern);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(moduleName, category, pattern);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/FailoverParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class FailoverParams implements IParams {\n\n  private HostAndPort to;\n\n  private boolean force;\n\n  private Long timeout;\n\n  public static FailoverParams failoverParams() {\n    return new FailoverParams();\n  }\n\n  public FailoverParams to(String host, int port) {\n    return to(new HostAndPort(host, port));\n  }\n\n  public FailoverParams to(HostAndPort to) {\n    this.to = to;\n    return this;\n  }\n\n  /**\n   * Both TO ({@link FailoverParams#to(redis.clients.jedis.HostAndPort)} or\n   * {@link FailoverParams#to(java.lang.String, int)}) and\n   * {@link FailoverParams#timeout(long) TIMEOUT} must be set in order for FORCE option.\n   */\n  public FailoverParams force() {\n    this.force = true;\n    return this;\n  }\n\n  public FailoverParams timeout(long timeout) {\n    this.timeout = timeout;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (to != null) {\n      args.add(Keyword.TO).add(to.getHost()).add(to.getPort());\n    }\n\n    if (force) {\n      if (to == null || timeout == null) {\n        throw new IllegalArgumentException(\"FAILOVER with force option requires both a timeout and target HOST and IP.\");\n      }\n      args.add(Keyword.FORCE);\n    }\n\n    if (timeout != null) {\n      args.add(Keyword.TIMEOUT).add(timeout);\n    }\n\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    FailoverParams that = (FailoverParams) o;\n    return force == that.force && Objects.equals(to, that.to) && Objects.equals(timeout, that.timeout);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(to, force, timeout);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/GeoAddParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class GeoAddParams implements IParams {\n\n  private boolean nx = false;\n  private boolean xx = false;\n  private boolean ch = false;\n\n  public GeoAddParams() {\n  }\n\n  public static GeoAddParams geoAddParams() {\n    return new GeoAddParams();\n  }\n\n  /**\n   * Don't update already existing elements. Always add new elements.\n   * @return GetExParams\n   */\n  public GeoAddParams nx() {\n    this.nx = true;\n    return this;\n  }\n\n  /**\n   * Only update elements that already exist. Never add elements.\n   * @return GetExParams\n   */\n  public GeoAddParams xx() {\n    this.xx = true;\n    return this;\n  }\n\n  /**\n   * Modify the return value from the number of new elements added, to the total number of elements\n   * changed\n   * @return GetExParams\n   */\n  public GeoAddParams ch() {\n    this.ch = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (nx) {\n      args.add(Keyword.NX);\n    } else if (xx) {\n      args.add(Keyword.XX);\n    }\n\n    if (ch) {\n      args.add(Keyword.CH);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    GeoAddParams that = (GeoAddParams) o;\n    return nx == that.nx && xx == that.xx && ch == that.ch;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(nx, xx, ch);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/GeoRadiusParam.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.SortingOrder;\n\npublic class GeoRadiusParam implements IParams {\n\n  private boolean withCoord = false;\n  private boolean withDist = false;\n  private boolean withHash = false;\n\n  private Integer count = null;\n  private boolean any = false;\n  private SortingOrder sortingOrder = null;\n\n  public GeoRadiusParam() {\n  }\n\n  public static GeoRadiusParam geoRadiusParam() {\n    return new GeoRadiusParam();\n  }\n\n  public GeoRadiusParam withCoord() {\n    withCoord = true;\n    return this;\n  }\n\n  public GeoRadiusParam withDist() {\n    withDist = true;\n    return this;\n  }\n\n  public GeoRadiusParam withHash() {\n    withHash = true;\n    return this;\n  }\n\n  public GeoRadiusParam sortAscending() {\n    return sortingOrder(SortingOrder.ASC);\n  }\n\n  public GeoRadiusParam sortDescending() {\n    return sortingOrder(SortingOrder.DESC);\n  }\n\n  public GeoRadiusParam sortingOrder(SortingOrder order) {\n    this.sortingOrder = order;\n    return this;\n  }\n\n  public GeoRadiusParam count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  public GeoRadiusParam count(int count, boolean any) {\n    this.count = count;\n    this.any = any;\n    return this;\n  }\n\n  public GeoRadiusParam any() {\n    if (this.count == null) {\n      throw new IllegalArgumentException(\"COUNT must be set before ANY to be set\");\n    }\n    this.any = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (withCoord) {\n      args.add(Keyword.WITHCOORD);\n    }\n    if (withDist) {\n      args.add(Keyword.WITHDIST);\n    }\n    if (withHash) {\n      args.add(Keyword.WITHHASH);\n    }\n\n    if (count != null) {\n      args.add(Keyword.COUNT).add(count);\n      if (any) {\n        args.add(Keyword.ANY);\n      }\n    }\n\n    if (sortingOrder != null) {\n      args.add(sortingOrder);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/GeoRadiusStoreParam.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\npublic class GeoRadiusStoreParam implements IParams {\n\n  private boolean store = false;\n  private boolean storeDist = false;\n  private String key;\n\n  public GeoRadiusStoreParam() {\n  }\n\n  public static GeoRadiusStoreParam geoRadiusStoreParam() {\n    return new GeoRadiusStoreParam();\n  }\n\n  /**\n   * WARNING: In Redis, if STOREDIST exists, store will be ignored.\n   * <p>\n   * Refer: https://github.com/antirez/redis/blob/6.0/src/geo.c#L649\n   */\n  public GeoRadiusStoreParam store(String key) {\n    if (key != null) {\n      this.store = true;\n      this.key = key;\n    }\n    return this;\n  }\n\n  public GeoRadiusStoreParam storeDist(String key) {\n    if (key != null) {\n      this.storeDist = true;\n      this.key = key;\n    }\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (storeDist) {\n      args.add(Keyword.STOREDIST).key(key);\n    } else if (store) {\n      args.add(Keyword.STORE).key(key);\n    } else {\n      throw new IllegalArgumentException(this.getClass().getSimpleName()\n          + \" must has store or storedist option\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/GeoSearchParam.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.args.SortingOrder;\n\npublic class GeoSearchParam implements IParams {\n\n  private boolean fromMember = false;\n  private boolean fromLonLat = false;\n  private String member;\n  private GeoCoordinate coord;\n\n  private boolean byRadius = false;\n  private boolean byBox = false;\n  private double radius;\n  private double width;\n  private double height;\n  private GeoUnit unit;\n\n  private boolean withCoord = false;\n  private boolean withDist = false;\n  private boolean withHash = false;\n\n  private Integer count = null;\n  private boolean any = false;\n  private SortingOrder sortingOrder = null;\n\n  public GeoSearchParam() { }\n\n  public static GeoSearchParam geoSearchParam() { return new GeoSearchParam(); }\n\n  public GeoSearchParam fromMember(String member) {\n    this.fromMember = true;\n    this.member = member;\n    return this;\n  }\n\n  public GeoSearchParam fromLonLat(double longitude, double latitude) {\n    this.fromLonLat = true;\n    this.coord = new GeoCoordinate(longitude, latitude);\n    return this;\n  }\n\n  public GeoSearchParam fromLonLat(GeoCoordinate coord) {\n    this.fromLonLat = true;\n    this.coord = coord;\n    return this;\n  }\n\n\n  public GeoSearchParam byRadius(double radius, GeoUnit unit){\n    this.byRadius = true;\n    this.radius = radius;\n    this.unit = unit;\n    return this;\n  }\n\n  public GeoSearchParam byBox(double width, double height, GeoUnit unit){\n    this.byBox = true;\n    this.width = width;\n    this.height = height;\n    this.unit = unit;\n    return this;\n  }\n\n  public GeoSearchParam withCoord() {\n    withCoord = true;\n    return this;\n  }\n\n  public GeoSearchParam withDist() {\n    withDist = true;\n    return this;\n  }\n\n  public GeoSearchParam withHash() {\n    withHash = true;\n    return this;\n  }\n\n  public GeoSearchParam asc() {\n    return sortingOrder(SortingOrder.ASC);\n  }\n\n  public GeoSearchParam desc() {\n    return sortingOrder(SortingOrder.DESC);\n  }\n\n  public GeoSearchParam sortingOrder(SortingOrder order) {\n    sortingOrder = order;\n    return this;\n  }\n\n  public GeoSearchParam count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  public GeoSearchParam count(int count, boolean any) {\n    this.count = count;\n    this.any = true;\n    return this;\n  }\n\n  public GeoSearchParam any() {\n    if (this.count == null) {\n      throw new IllegalArgumentException(\"COUNT must be set before ANY to be set\");\n    }\n    this.any = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (fromMember && fromLonLat) {\n      throw new IllegalArgumentException(\"Both FROMMEMBER and FROMLONLAT cannot be used.\");\n    } else if (fromMember) {\n      args.add(Keyword.FROMMEMBER).add(member);\n    } else if (fromLonLat) {\n      args.add(Keyword.FROMLONLAT).add(coord.getLongitude()).add(coord.getLatitude());\n    } else {\n      throw new IllegalArgumentException(\"Either FROMMEMBER or FROMLONLAT must be used.\");\n    }\n\n    if (byRadius && byBox) {\n      throw new IllegalArgumentException(\"Both BYRADIUS and BYBOX cannot be used.\");\n    } else if (byRadius) {\n      args.add(Keyword.BYRADIUS).add(radius).add(unit);\n    } else if (byBox) {\n      args.add(Keyword.BYBOX).add(width).add(height).add(unit);\n    } else {\n      throw new IllegalArgumentException(\"Either BYRADIUS or BYBOX must be used.\");\n    }\n\n    if (withCoord) {\n      args.add(Keyword.WITHCOORD);\n    }\n    if (withDist) {\n      args.add(Keyword.WITHDIST);\n    }\n    if (withHash) {\n      args.add(Keyword.WITHHASH);\n    }\n\n    if (count != null) {\n      args.add(Keyword.COUNT).add(count);\n      if (any) {\n        args.add(Keyword.ANY);\n      }\n    }\n\n    if (sortingOrder != null) {\n      args.add(sortingOrder);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/GetExParams.java",
    "content": "package redis.clients.jedis.params;\n\npublic class GetExParams extends BaseGetExParams<GetExParams> {\n  \n  public static GetExParams getExParams() { \n    return new GetExParams();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/HGetExParams.java",
    "content": "package redis.clients.jedis.params;\n\n/**\n * HGetExParams is a parameter class used when getting values of given keys in hash data with \n * optionally setting/changing expiry time in Redis.\n * It can be used to set the expiry time of the key in seconds or milliseconds.\n * \n * <p>This class includes the following methods:</p>\n * <ul>\n *   <li>{@link #hGetExParams()} - Static factory method to create a new instance of HGetExParams.</li>\n *   <li>{@link #ex(long)} - Set the specified expire time, in seconds.</li>\n *   <li>{@link #px(long)} - Set the specified expire time, in milliseconds.</li>\n *   <li>{@link #exAt(long)} - Set the specified Unix(epoch) time at which the key will expire, in seconds.</li>\n *   <li>{@link #pxAt(long)} - Set the specified Unix(epoch) time at which the key will expire, in milliseconds.</li>\n *   <li>{@link #persist()} - Remove the time-to-live associated with the key.</li>\n * </ul>\n * \n * <p>Example usage:</p>\n * <pre>\n * {@code\n * HGetExParams params = HGetExParams.hGetExParams().persist();\n * }\n * </pre>\n * \n * @see BaseGetExParams\n */\npublic class HGetExParams extends BaseGetExParams<HGetExParams> {\n\n  public static HGetExParams hGetExParams() {\n    return new HGetExParams();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/HSetExParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\n/**\n * HSetExParams is a parameter class used when setting key-value pairs of hash data with \n * optional expiry and existance conditions in Redis.\n * It provides methods to specify whether the key should be set\n * only if it already exists or only if it does not already exist.\n * It can also be used to set the expiry time of the key in seconds or milliseconds.\n * \n * <p>This class includes the following methods:</p>\n * <ul>\n *   <li>{@link #hSetExParams()} - Static factory method to create a new instance of HSetExParams.</li>\n *   <li>{@link #fnx()} - Sets the condition to only set the key if it does not already exist.</li>\n *   <li>{@link #fxx()} - Sets the condition to only set the key if it already exists.</li>\n *   <li>{@link #ex(long)} - Set the specified expire time, in seconds.</li>\n *   <li>{@link #px(long)} - Set the specified expire time, in milliseconds.</li>\n *   <li>{@link #exAt(long)} - Set the specified Unix(epoch) time at which the key will expire, in seconds.</li>\n *   <li>{@link #pxAt(long)} - Set the specified Unix(epoch) time at which the key will expire, in milliseconds.</li>\n *   <li>{@link #keepTtl()} - Retain the time to live associated with the key.</li>\n * </ul>\n * \n * <p>Example usage:</p>\n * <pre>\n * {@code\n * HSetExParams params = HSetExParams.hSetExParams().fnx();\n * }\n * </pre>\n * \n * @see BaseSetExParams\n */\npublic class HSetExParams extends BaseSetExParams<HSetExParams> {\n\n    private Keyword existance;\n\n    public static HSetExParams hSetExParams() {\n        return new HSetExParams();\n    }\n\n    /**\n     * Only set the key if it does not already exist.\n     * @return HSetExParams\n     */\n    public HSetExParams fnx() {\n        this.existance = Keyword.FNX;\n        return this;\n    }\n\n    /**\n     * Only set the key if it already exist.\n     * @return HSetExParams\n     */\n    public HSetExParams fxx() {\n        this.existance = Keyword.FXX;\n        return this;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n        if (existance != null) {\n            args.add(existance);\n        }\n\n        super.addParams(args);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        HSetExParams setParams = (HSetExParams) o;\n        return Objects.equals(existance, setParams.existance) && super.equals((BaseSetExParams) o);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(existance, super.hashCode());\n    }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/HotkeysParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.HotkeysMetric;\nimport redis.clients.jedis.util.JedisAsserts;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * Parameters for the HOTKEYS START command.\n */\npublic class HotkeysParams implements IParams {\n\n  private HotkeysMetric[] metrics;\n  private Integer count;\n  private Integer duration;\n  private Integer sample;\n  private int[] slots;\n\n  public HotkeysParams() {\n  }\n\n  public static HotkeysParams hotkeysParams() {\n    return new HotkeysParams();\n  }\n\n  /**\n   * Specifies which metrics to track. At least one metric is required.\n   * @param metrics the metrics to track (CPU, NET)\n   * @return this\n   */\n  public HotkeysParams metrics(HotkeysMetric... metrics) {\n    JedisAsserts.notNull(metrics, \"metrics must not be null\");\n    JedisAsserts.isTrue(metrics.length > 0, \"at least one metric is required\");\n\n    this.metrics = metrics;\n    return this;\n  }\n\n  /**\n   * Maximum number of hot keys to track.\n   * @param count must be between 10 and 64\n   * @return this\n   * @throws IllegalArgumentException if count is not between 10 and 64\n   */\n  public HotkeysParams count(int count) {\n    JedisAsserts.isTrue(count >= 10 && count <= 64, \"count must be between 1 and 64\");\n\n    this.count = count;\n    return this;\n  }\n\n  /**\n   * Auto-stop tracking after the specified number of seconds.\n   * @param duration 0 means no auto-stop\n   * @return this\n   * @throws IllegalArgumentException if duration is negative\n   */\n  public HotkeysParams duration(int duration) {\n    JedisAsserts.isTrue(duration >= 0, \"duration must be >= 0\");\n\n    this.duration = duration;\n    return this;\n  }\n\n  /**\n   * Sample 1 in N commands.\n   * @param sample 1 means all commands are sampled\n   * @return this\n   * @throws IllegalArgumentException if sample is less than 1\n   */\n  public HotkeysParams sample(int sample) {\n    JedisAsserts.isTrue(sample >= 1, \"sample must be >= 1\");\n\n    this.sample = sample;\n    return this;\n  }\n\n  /**\n   * Filter by hash slots (cluster mode only).\n   * @param slots the hash slots to filter (0-16383)\n   * @return this\n   * @throws IllegalArgumentException if any slot is not between 0 and 16383\n   */\n  public HotkeysParams slots(int... slots) {\n    if (slots != null) {\n      for (int slot : slots) {\n        JedisAsserts.isTrue(slot >= 0 && slot <= 16383, \"each slot must be between 0 and 16383\");\n      }\n    }\n    this.slots = slots;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    JedisAsserts.notNull(metrics, \"metrics must not be null\");\n    JedisAsserts.isTrue(metrics.length > 0, \"at least one metric is required\");\n\n    args.add(Keyword.METRICS);\n    args.add(metrics.length);\n    for (HotkeysMetric metric : metrics) {\n      args.add(metric);\n    }\n\n    if (count != null) {\n      args.add(Keyword.COUNT);\n      args.add(count);\n    }\n\n    if (duration != null) {\n      args.add(Keyword.DURATION);\n      args.add(duration);\n    }\n\n    if (sample != null) {\n      args.add(Keyword.SAMPLE);\n      args.add(sample);\n    }\n\n    if (slots != null && slots.length > 0) {\n      args.add(Keyword.SLOTS);\n      args.add(slots.length);\n      for (int slot : slots) {\n        args.add(slot);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    HotkeysParams that = (HotkeysParams) o;\n    return Arrays.equals(metrics, that.metrics) && Objects.equals(count, that.count)\n        && Objects.equals(duration, that.duration) && Objects.equals(sample, that.sample)\n        && Arrays.equals(slots, that.slots);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hash(count, duration, sample);\n    result = 31 * result + Arrays.hashCode(metrics);\n    result = 31 * result + Arrays.hashCode(slots);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/IParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\n\npublic interface IParams {\n\n  void addParams(CommandArguments args);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/LCSParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class LCSParams implements IParams {\n\n  private boolean len = false;\n  private boolean idx = false;\n  private Long minMatchLen;\n  private boolean withMatchLen = false;\n\n  public static LCSParams LCSParams() { return new LCSParams(); }\n\n  /**\n   * When LEN is given the command returns the length of the longest common substring.\n   * @return LCSParams\n   */\n  public LCSParams len() {\n    this.len = true;\n    return this;\n  }\n\n  /**\n   * When IDX is given the command returns an array with the LCS length\n   * and all the ranges in both the strings, start and end offset for\n   * each string, where there are matches.\n   * @return LCSParams\n   */\n  public LCSParams idx() {\n    this.idx = true;\n    return this;\n  }\n\n  /**\n   * Specify the minimum match length.\n   * @return LCSParams\n   */\n  public LCSParams minMatchLen(long minMatchLen) {\n    this.minMatchLen = minMatchLen;\n    return this;\n  }\n\n  /**\n   * When WITHMATCHLEN is given each array representing a match will also have the length of the match.\n   * @return LCSParams\n   */\n  public LCSParams withMatchLen() {\n    this.withMatchLen = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (len) {\n      args.add(Keyword.LEN);\n    }\n    if (idx) {\n      args.add(Keyword.IDX);\n    }\n    if (minMatchLen != null) {\n      args.add(Keyword.MINMATCHLEN).add(minMatchLen);\n    }\n    if (withMatchLen) {\n      args.add(Keyword.WITHMATCHLEN);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    LCSParams lcsParams = (LCSParams) o;\n    return len == lcsParams.len && idx == lcsParams.idx && withMatchLen == lcsParams.withMatchLen && Objects.equals(minMatchLen, lcsParams.minMatchLen);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(len, idx, minMatchLen, withMatchLen);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/LPosParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class LPosParams implements IParams {\n\n  private Integer rank;\n  private Integer maxlen;\n  \n  public static LPosParams lPosParams() {\n    return new LPosParams();\n  }\n\n  public LPosParams rank(int rank) {\n    this.rank = rank;\n    return this;\n  }\n\n  public LPosParams maxlen(int maxLen) {\n    this.maxlen = maxLen;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (rank != null) {\n      args.add(Keyword.RANK).add(rank);\n    }\n\n    if (maxlen != null) {\n      args.add(Keyword.MAXLEN).add(maxlen);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    LPosParams that = (LPosParams) o;\n    return Objects.equals(rank, that.rank) && Objects.equals(maxlen, that.maxlen);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(rank, maxlen);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/LolwutParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\npublic class LolwutParams implements IParams {\n\n  private Integer version;\n  private String[] opargs;\n\n  public LolwutParams version(int version) {\n    this.version = version;\n    return this;\n  }\n\n  @Deprecated\n  public LolwutParams args(String... args) {\n    return optionalArguments(args);\n  }\n\n  public LolwutParams optionalArguments(String... args) {\n    this.opargs = args;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (version != null) {\n      args.add(Keyword.VERSION).add(version);\n\n      if (opargs != null && opargs.length > 0) {\n        args.addObjects((Object[]) opargs);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    LolwutParams that = (LolwutParams) o;\n    return Objects.equals(version, that.version) && Arrays.equals(opargs, that.opargs);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hash(version);\n    result = 31 * result + Arrays.hashCode(opargs);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/MSetExParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class MSetExParams extends BaseSetExParams<MSetExParams> {\n\n  private Keyword existance;\n\n  public static MSetExParams setParams() {\n    return new MSetExParams();\n  }\n\n  /**\n   * Only set the key if it does not already exist.\n   * @return {@code this}\n   */\n  public MSetExParams nx() {\n    this.existance = Keyword.NX;\n    return this;\n  }\n\n  /**\n   * Only set the key if it already exist.\n   * @return {@code this}\n   */\n  public MSetExParams xx() {\n    this.existance = Keyword.XX;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (existance != null) {\n      args.add(existance);\n    }\n\n    super.addParams(args);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    MSetExParams setParams = (MSetExParams) o;\n    return Objects.equals(existance, setParams.existance) && super.equals(o);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(existance, super.hashCode());\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/MigrateParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class MigrateParams implements IParams {\n\n  private boolean copy = false;\n  private boolean replace = false;\n  private String username = null;\n  private String password = null;\n\n  public MigrateParams() {\n  }\n\n  public static MigrateParams migrateParams() {\n    return new MigrateParams();\n  }\n\n  public MigrateParams copy() {\n    this.copy = true;\n    return this;\n  }\n\n  public MigrateParams replace() {\n    this.replace = true;\n    return this;\n  }\n\n  public MigrateParams auth(String password) {\n    this.password = password;\n    return this;\n  }\n\n  public MigrateParams auth2(String username, String password) {\n    this.username = username;\n    this.password = password;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (copy) {\n      args.add(Keyword.COPY);\n    }\n    if (replace) {\n      args.add(Keyword.REPLACE);\n    }\n    if (username != null) {\n      args.add(Keyword.AUTH2).add(username).add(password);\n    } else if (password != null) {\n      args.add(Keyword.AUTH).add(password);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    MigrateParams that = (MigrateParams) o;\n    return copy == that.copy && replace == that.replace && Objects.equals(username, that.username) && Objects.equals(password, that.password);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(copy, replace, username, password);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ModuleLoadExParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class ModuleLoadExParams implements IParams {\n\n  private final List<KeyValue<String, String>> configs = new ArrayList<>();\n  private final List<String> args = new ArrayList<>();\n\n  public ModuleLoadExParams() {\n  }\n\n  public ModuleLoadExParams moduleLoadexParams() {\n    return new ModuleLoadExParams();\n  }\n\n  public ModuleLoadExParams config(String name, String value) {\n    this.configs.add(KeyValue.of(name, value));\n    return this;\n  }\n\n  public ModuleLoadExParams arg(String arg) {\n    this.args.add(arg);\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    this.configs.forEach(kv -> args.add(Keyword.CONFIG).add(kv.getKey()).add(kv.getValue()));\n\n    if (!this.args.isEmpty()) {\n      args.add(Keyword.ARGS).addObjects(this.args);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ModuleLoadExParams that = (ModuleLoadExParams) o;\n    return Objects.equals(configs, that.configs) && Objects.equals(args, that.args);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(configs, args);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/RestoreParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class RestoreParams implements IParams {\n\n  private boolean replace;\n\n  private boolean absTtl;\n\n  private Long idleTime;\n\n  private Long frequency;\n\n  public static RestoreParams restoreParams() {\n    return new RestoreParams();\n  }\n\n  public RestoreParams replace() {\n    this.replace = true;\n    return this;\n  }\n\n  public RestoreParams absTtl() {\n    this.absTtl = true;\n    return this;\n  }\n\n  public RestoreParams idleTime(long idleTime) {\n    this.idleTime = idleTime;\n    return this;\n  }\n\n  public RestoreParams frequency(long frequency) {\n    this.frequency = frequency;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (replace) {\n      args.add(Keyword.REPLACE);\n    }\n\n    if (absTtl) {\n      args.add(Keyword.ABSTTL);\n    }\n\n    if (idleTime != null) {\n      args.add(Keyword.IDLETIME).add(idleTime);\n    }\n\n    if (frequency != null) {\n      args.add(Keyword.FREQ).add(frequency);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    RestoreParams that = (RestoreParams) o;\n    return replace == that.replace && absTtl == that.absTtl && Objects.equals(idleTime, that.idleTime) && Objects.equals(frequency, that.frequency);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(replace, absTtl, idleTime, frequency);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ScanParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport static redis.clients.jedis.Protocol.Keyword.MATCH;\n\nimport java.nio.ByteBuffer;\nimport java.util.EnumMap;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.Protocol;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class ScanParams implements IParams {\n\n  private final Map<Keyword, ByteBuffer> params = new EnumMap<>(Keyword.class);\n\n  public static final String SCAN_POINTER_START = String.valueOf(0);\n  public static final byte[] SCAN_POINTER_START_BINARY = SafeEncoder.encode(SCAN_POINTER_START);\n\n  public ScanParams match(final byte[] pattern) {\n    params.put(MATCH, ByteBuffer.wrap(pattern));\n    return this;\n  }\n\n  /**\n   * @see <a href=\"https://redis.io/commands/scan#the-match-option\">MATCH option in Redis documentation</a>\n   */\n  public ScanParams match(final String pattern) {\n    params.put(MATCH, ByteBuffer.wrap(SafeEncoder.encode(pattern)));\n    return this;\n  }\n\n  /**\n   * @see <a href=\"https://redis.io/commands/scan#the-count-option\">COUNT option in Redis documentation</a>\n   */\n  public ScanParams count(final Integer count) {\n    params.put(Keyword.COUNT, ByteBuffer.wrap(Protocol.toByteArray(count)));\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    for (Map.Entry<Keyword, ByteBuffer> param : params.entrySet()) {\n      args.add(param.getKey());\n      args.add(param.getValue().array());\n    }\n  }\n\n  public byte[] binaryMatch() {\n    if (params.containsKey(MATCH)) {\n      return params.get(MATCH).array();\n    } else {\n      return null;\n    }\n  }\n\n  public String match() {\n    if (params.containsKey(MATCH)) {\n      return new String(params.get(MATCH).array());\n    } else {\n      return null;\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ScanParams that = (ScanParams) o;\n    return Objects.equals(params, that.params);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(params);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/SetParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.util.CompareCondition;\n\npublic class SetParams extends BaseSetExParams<SetParams> {\n\n  private Keyword existance;\n  private CompareCondition condition;\n\n  public static SetParams setParams() {\n    return new SetParams();\n  }\n\n  /**\n   * Only set the key if it does not already exist.\n   * @return SetParams\n   */\n  public SetParams nx() {\n    this.existance = Keyword.NX;\n    return this;\n  }\n\n  /**\n   * Only set the key if it already exist.\n   * @return SetParams\n   */\n  public SetParams xx() {\n    this.existance = Keyword.XX;\n    return this;\n  }\n\n  /**\n   * Set the specified expire time, in seconds.\n   * @param remainingSeconds\n   * @return SetParams\n   */\n  @Override\n  public SetParams ex(long remainingSeconds) {\n    return super.ex(remainingSeconds);\n  }\n\n  /**\n   * Set the specified expire time, in milliseconds.\n   * @param remainingMilliseconds\n   * @return SetParams\n   */\n  @Override\n  public SetParams px(long remainingMilliseconds) {\n    return super.px(remainingMilliseconds);\n  }\n\n  /**\n   * Set the specified Unix time at which the key will expire, in seconds.\n   * @param timestampSeconds\n   * @return SetParams\n   */\n  @Override\n  public SetParams exAt(long timestampSeconds) {\n    return super.exAt(timestampSeconds);\n  }\n\n  /**\n   * Set the specified Unix time at which the key will expire, in milliseconds.\n   * @param timestampMilliseconds\n   * @return SetParams\n   */\n  @Override\n  public SetParams pxAt(long timestampMilliseconds) {\n    return super.pxAt(timestampMilliseconds);\n  }\n\n  /**\n   * Retain the time to live associated with the key.\n   *\n   * @deprecated Since 6.1.0 use {@link #keepTtl()} instead.\n   * @return SetParams\n   */\n  @Override\n  public SetParams keepttl() {\n    return keepTtl();\n  }\n\n  /**\n   * Retain the time to live associated with the key.\n   * @return SetParams\n   */\n  @Override\n  public SetParams keepTtl() {\n    return super.keepTtl();\n  }\n\n  /**\n   * Set a compare condition for compare-and-set operations.\n   * @param condition the condition to apply\n   * @return {@link SetParams}\n   */\n  @Experimental\n  public SetParams condition(CompareCondition condition) {\n    this.condition = condition;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    // Condition must be added before NX/XX per Redis command syntax\n    if (condition != null) {\n      condition.addTo(args);\n    }\n\n    if (existance != null) {\n      args.add(existance);\n    }\n\n    super.addParams(args);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    SetParams setParams = (SetParams) o;\n    return Objects.equals(existance, setParams.existance)\n        && Objects.equals(condition, setParams.condition)\n        && super.equals((BaseSetExParams) o);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(existance, condition, super.hashCode());\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ShutdownParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.SaveMode;\n\nimport java.util.Objects;\n\npublic class ShutdownParams implements IParams {\n\n  private SaveMode saveMode;\n  private boolean now;\n  private boolean force;\n\n  public static ShutdownParams shutdownParams() {\n    return new ShutdownParams();\n  }\n\n  public ShutdownParams saveMode(SaveMode saveMode) {\n    this.saveMode = saveMode;\n    return this;\n  }\n\n  public ShutdownParams nosave() {\n    return this.saveMode(SaveMode.NOSAVE);\n  }\n\n  public ShutdownParams save() {\n    return this.saveMode(SaveMode.SAVE);\n  }\n\n  public ShutdownParams now() {\n    this.now = true;\n    return this;\n  }\n\n  public ShutdownParams force() {\n    this.force = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (this.saveMode != null) {\n      args.add(saveMode);\n    }\n    if (this.now) {\n      args.add(Keyword.NOW);\n    }\n    if (this.force) {\n      args.add(Keyword.FORCE);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ShutdownParams that = (ShutdownParams) o;\n    return now == that.now && force == that.force && saveMode == that.saveMode;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(saveMode, now, force);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/SortingParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.SortingOrder;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Builder Class for {@code SORT} command parameters.\n */\n// TODO:\npublic class SortingParams implements IParams {\n\n  private final List<Object> params = new ArrayList<>();\n\n  /**\n   * Sort by weight in keys.\n   * <p>\n   * Takes a pattern that is used in order to generate the key names of the weights used for\n   * sorting. Weight key names are obtained substituting the first occurrence of * with the actual\n   * value of the elements on the list.\n   * <p>\n   * The pattern for a normal key/value pair is \"field*\" and for a value in a hash\n   * \"field*-&gt;fieldname\".\n   * @param pattern\n   * @return the SortingParams Object\n   */\n  public SortingParams by(final String pattern) {\n    return by(SafeEncoder.encode(pattern));\n  }\n\n  /**\n   * Sort by weight in keys.\n   * <p>\n   * Takes a pattern that is used in order to generate the key names of the weights used for\n   * sorting. Weight key names are obtained substituting the first occurrence of * with the actual\n   * value of the elements on the list.\n   * <p>\n   * The pattern for a normal key/value pair is \"field*\" and for a value in a hash\n   * \"field*-&gt;fieldname\".\n   * @param pattern\n   * @return the SortingParams Object\n   */\n  public SortingParams by(final byte[] pattern) {\n    params.add(Keyword.BY);\n    params.add(pattern);\n    return this;\n  }\n\n  /**\n   * No sorting.\n   * <p>\n   * This is useful if you want to retrieve an external key (using {@link #get(String...) GET}) but\n   * you don't want the sorting overhead.\n   * @return the SortingParams Object\n   */\n  public SortingParams nosort() {\n    params.add(Keyword.BY);\n    params.add(Keyword.NOSORT);\n    return this;\n  }\n\n  /**\n   * Get the Sorting in Descending Order.\n   * @return the sortingParams Object\n   */\n  public SortingParams desc() {\n    return sortingOrder(SortingOrder.DESC);\n  }\n\n  /**\n   * Get the Sorting in Ascending Order. This is the default order.\n   * @return the SortingParams Object\n   */\n  public SortingParams asc() {\n    return sortingOrder(SortingOrder.ASC);\n  }\n\n  /**\n   * Get by the Sorting Order.\n   * @param order the Sorting order\n   * @return the SortingParams object\n   */\n  public SortingParams sortingOrder(SortingOrder order) {\n    params.add(order.getRaw());\n    return this;\n  }\n\n  /**\n   * Limit the Numbers of returned Elements.\n   * @param start is zero based\n   * @param count\n   * @return the SortingParams Object\n   */\n  public SortingParams limit(final int start, final int count) {\n    params.add(Keyword.LIMIT);\n    params.add(start);\n    params.add(count);\n    return this;\n  }\n\n  /**\n   * Sort lexicographicaly. Note that Redis is utf-8 aware assuming you set the right value for the\n   * LC_COLLATE environment variable.\n   * @return the SortingParams Object\n   */\n  public SortingParams alpha() {\n    params.add(Keyword.ALPHA);\n    return this;\n  }\n\n  /**\n   * Retrieving external keys from the result of the search.\n   * <p>\n   * Takes a pattern that is used in order to generate the key names of the result of sorting. The\n   * key names are obtained substituting the first occurrence of * with the actual value of the\n   * elements on the list.\n   * <p>\n   * The pattern for a normal key/value pair is \"field*\" and for a value in a hash\n   * \"field*-&gt;fieldname\".\n   * <p>\n   * To get the list itself use the char # as pattern.\n   * @param patterns\n   * @return the SortingParams Object\n   */\n  public SortingParams get(String... patterns) {\n    for (final String pattern : patterns) {\n      params.add(Keyword.GET);\n      params.add(pattern);\n    }\n    return this;\n  }\n\n  /**\n   * Retrieving external keys from the result of the search.\n   * <p>\n   * Takes a pattern that is used in order to generate the key names of the result of sorting. The\n   * key names are obtained substituting the first occurrence of * with the actual value of the\n   * elements on the list.\n   * <p>\n   * The pattern for a normal key/value pair is \"field*\" and for a value in a hash\n   * \"field*-&gt;fieldname\".\n   * <p>\n   * To get the list itself use the char # as pattern.\n   * @param patterns\n   * @return the SortingParams Object\n   */\n  public SortingParams get(byte[]... patterns) {\n    for (final byte[] pattern : patterns) {\n      params.add(Keyword.GET);\n      params.add(pattern);\n    }\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addObjects(params);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    SortingParams that = (SortingParams) o;\n    return Objects.equals(params, that.params);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(params);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/VAddParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * Parameters for the VADD command.\n */\n@Experimental\npublic class VAddParams implements IParams {\n\n  private boolean cas;\n  private QuantizationType quantization;\n  private Integer ef;\n  private String attributes;\n  private Integer m;\n\n  public enum QuantizationType {\n    NOQUANT, Q8, BIN\n  }\n\n  public VAddParams() {\n  }\n\n  /**\n   * Performs the operation partially using threads, in a check-and-set style. The neighbor\n   * candidates collection, which is slow, is performed in the background, while the command is\n   * executed in the main thread.\n   * @return VAddParams\n   */\n  public VAddParams cas() {\n    this.cas = true;\n    return this;\n  }\n\n  /**\n   * Forces the vector to be created without int8 quantization.\n   * @return VAddParams\n   */\n  public VAddParams noQuant() {\n    this.quantization = QuantizationType.NOQUANT;\n    return this;\n  }\n\n  /**\n   * Forces the vector to use signed 8-bit quantization. This is the default.\n   * @return VAddParams\n   */\n  public VAddParams q8() {\n    this.quantization = QuantizationType.Q8;\n    return this;\n  }\n\n  /**\n   * Forces the vector to use binary quantization instead of int8. This is much faster and uses less\n   * memory, but impacts the recall quality.\n   * @return VAddParams\n   */\n  public VAddParams bin() {\n    this.quantization = QuantizationType.BIN;\n    return this;\n  }\n\n  /**\n   * Plays a role in the effort made to find good candidates when connecting the new node to the\n   * existing Hierarchical Navigable Small World (HNSW) graph. The default is 200. Using a larger\n   * value may help in achieving a better recall.\n   * @param buildExplorationFactor the exploration factor\n   * @return VAddParams\n   */\n  public VAddParams ef(int buildExplorationFactor) {\n    this.ef = buildExplorationFactor;\n    return this;\n  }\n\n  /**\n   * Associates attributes in the form of a JavaScript object to the newly created entry or updates\n   * the attributes (if they already exist).\n   * @param attributes the attributes as a JSON string\n   * @return VAddParams\n   */\n  public VAddParams setAttr(String attributes) {\n    this.attributes = attributes;\n    return this;\n  }\n\n  /**\n   * The maximum number of connections that each node of the graph will have with other nodes. The\n   * default is 16. More connections means more memory, but provides for more efficient graph\n   * exploration.\n   * @param numLinks the maximum number of connections\n   * @return VAddParams\n   */\n  public VAddParams m(int numLinks) {\n    this.m = numLinks;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (cas) {\n      args.add(Protocol.Keyword.CAS);\n    }\n\n    if (quantization != null) {\n      switch (quantization) {\n        case NOQUANT:\n          args.add(Protocol.Keyword.NOQUANT);\n          break;\n        case Q8:\n          args.add(Protocol.Keyword.Q8);\n          break;\n        case BIN:\n          args.add(Protocol.Keyword.BIN);\n          break;\n      }\n    }\n\n    if (ef != null) {\n      args.add(Protocol.Keyword.EF).add(ef);\n    }\n\n    if (attributes != null) {\n      args.add(Protocol.Keyword.SETATTR).add(attributes);\n    }\n\n    if (m != null) {\n      args.add(Protocol.Keyword.M).add(m);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/VSimParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * Parameters for the VSIM command.\n */\n@Experimental\npublic class VSimParams implements IParams {\n\n  private Integer count;\n  private Double epsilon;\n  private Integer ef;\n  private String filter;\n  private Integer filterEf;\n  private boolean truth;\n  private boolean noThread;\n\n  public VSimParams() {\n  }\n\n  /**\n   * Limits the number of returned results.\n   * @param num the maximum number of results to return\n   * @return VSimParams\n   */\n  public VSimParams count(int num) {\n    this.count = num;\n    return this;\n  }\n\n  /**\n   * Sets the epsilon (delta) parameter for distance-based filtering. Only elements with a\n   * similarity score of (1 - epsilon) or better are returned. For example, epsilon=0.2 means only\n   * elements with similarity &gt;= 0.8 are returned.\n   * @param delta a floating point number between 0 and 1\n   * @return VSimParams\n   */\n  public VSimParams epsilon(double delta) {\n    this.epsilon = delta;\n    return this;\n  }\n\n  /**\n   * Controls the search effort. Higher values explore more nodes, improving recall at the cost of\n   * speed. Typical values range from 50 to 1000.\n   * @param searchExplorationFactor the exploration factor\n   * @return VSimParams\n   */\n  public VSimParams ef(int searchExplorationFactor) {\n    this.ef = searchExplorationFactor;\n    return this;\n  }\n\n  /**\n   * Applies a filter expression to restrict matching elements.\n   * @param expression the filter expression\n   * @return VSimParams\n   */\n  public VSimParams filter(String expression) {\n    this.filter = expression;\n    return this;\n  }\n\n  /**\n   * Limits the number of filtering attempts for the FILTER expression.\n   * @param maxFilteringEffort the maximum filtering effort\n   * @return VSimParams\n   */\n  public VSimParams filterEf(int maxFilteringEffort) {\n    this.filterEf = maxFilteringEffort;\n    return this;\n  }\n\n  /**\n   * Forces an exact linear scan of all elements, bypassing the HNSW graph. Use for benchmarking or\n   * to calculate recall. This is significantly slower (O(N)).\n   * @return VSimParams\n   */\n  public VSimParams truth() {\n    this.truth = true;\n    return this;\n  }\n\n  /**\n   * Executes the search in the main thread instead of a background thread. Useful for small vector\n   * sets or benchmarks. This may block the server during execution.\n   * @return VSimParams\n   */\n  public VSimParams noThread() {\n    this.noThread = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (count != null) {\n      args.add(Protocol.Keyword.COUNT).add(count);\n    }\n\n    if (epsilon != null) {\n      args.add(Protocol.Keyword.EPSILON).add(epsilon);\n    }\n\n    if (ef != null) {\n      args.add(Protocol.Keyword.EF).add(ef);\n    }\n\n    if (filter != null) {\n      args.add(Protocol.Keyword.FILTER).add(filter);\n    }\n\n    if (filterEf != null) {\n      args.add(Protocol.Keyword.FILTER_EF).add(filterEf);\n    }\n\n    if (truth) {\n      args.add(Protocol.Keyword.TRUTH);\n    }\n\n    if (noThread) {\n      args.add(Protocol.Keyword.NOTHREAD);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XAddParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.args.RawableFactory;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\npublic class XAddParams implements IParams {\n\n  private Rawable id;\n\n  private Long maxLen;\n\n  private boolean approximateTrimming;\n\n  private boolean exactTrimming;\n\n  private boolean nomkstream;\n\n  private String minId;\n\n  private Long limit;\n\n  private StreamDeletionPolicy trimMode;\n\n  private byte[] producerId;\n\n  private byte[] idempotentId;\n\n  private boolean idmpAuto;\n\n  public static XAddParams xAddParams() {\n    return new XAddParams();\n  }\n\n  public XAddParams noMkStream() {\n    this.nomkstream = true;\n    return this;\n  }\n\n  public XAddParams id(byte[] id) {\n    this.id = RawableFactory.from(id);\n    return this;\n  }\n\n  public XAddParams id(String id) {\n    this.id = RawableFactory.from(id);\n    return this;\n  }\n\n  public XAddParams id(StreamEntryID id) {\n    return id(id.toString());\n  }\n\n  public XAddParams id(long time, long sequence) {\n    return id(time + \"-\" + sequence);\n  }\n\n  public XAddParams id(long time) {\n    return id(time + \"-*\");\n  }\n\n  public XAddParams maxLen(long maxLen) {\n    this.maxLen = maxLen;\n    return this;\n  }\n\n  public XAddParams minId(String minId) {\n    this.minId = minId;\n    return this;\n  }\n\n  public XAddParams approximateTrimming() {\n    this.approximateTrimming = true;\n    return this;\n  }\n\n  public XAddParams exactTrimming() {\n    this.exactTrimming = true;\n    return this;\n  }\n\n  public XAddParams limit(long limit) {\n    this.limit = limit;\n    return this;\n  }\n\n  /**\n   * When trimming, defines desired behaviour for handling consumer group references.\n   * see {@link StreamDeletionPolicy} for details.\n   *\n   * @return XAddParams\n   */\n  public XAddParams trimmingMode(StreamDeletionPolicy trimMode) {\n    this.trimMode = trimMode;\n    return this;\n  }\n\n  /**\n   * Enable idempotent producer mode with automatic idempotent ID generation.\n   * Redis will calculate an idempotent ID based on the message content.\n   *\n   * @param producerId unique producer identifier (binary)\n   * @return XAddParams\n   */\n  public XAddParams idmpAuto(byte[] producerId) {\n    this.producerId = producerId;\n    this.idmpAuto = true;\n    this.idempotentId = null;\n    return this;\n  }\n\n  /**\n   * Enable idempotent producer mode with automatic idempotent ID generation.\n   * Redis will calculate an idempotent ID based on the message content.\n   *\n   * @param producerId unique producer identifier (string)\n   * @return XAddParams\n   */\n  public XAddParams idmpAuto(String producerId) {\n    return idmpAuto(SafeEncoder.encode(producerId));\n  }\n\n  /**\n   * Enable idempotent producer mode with explicit idempotent ID.\n   * The caller provides both producer ID and idempotent ID.\n   *\n   * @param producerId unique producer identifier (binary)\n   * @param idempotentId unique idempotent identifier for this message (binary)\n   * @return XAddParams\n   */\n  public XAddParams idmp(byte[] producerId, byte[] idempotentId) {\n    this.producerId = producerId;\n    this.idempotentId = idempotentId;\n    this.idmpAuto = false;\n    return this;\n  }\n\n  /**\n   * Enable idempotent producer mode with explicit idempotent ID.\n   * The caller provides both producer ID and idempotent ID.\n   *\n   * @param producerId unique producer identifier (string)\n   * @param idempotentId unique idempotent identifier for this message (string)\n   * @return XAddParams\n   */\n  public XAddParams idmp(String producerId, String idempotentId) {\n    return idmp(SafeEncoder.encode(producerId), SafeEncoder.encode(idempotentId));\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (nomkstream) {\n      args.add(Keyword.NOMKSTREAM);\n    }\n\n    if (trimMode != null) {\n      args.add(trimMode);\n    }\n\n    if (producerId != null) {\n      if (idmpAuto) {\n        args.add(Keyword.IDMPAUTO).add(producerId);\n      } else if (idempotentId != null) {\n        args.add(Keyword.IDMP).add(producerId).add(idempotentId);\n      }\n    }\n\n    if (maxLen != null) {\n      args.add(Keyword.MAXLEN);\n\n      if (approximateTrimming) {\n        args.add(Protocol.BYTES_TILDE);\n      } else if (exactTrimming) {\n        args.add(Protocol.BYTES_EQUAL);\n      }\n\n      args.add(maxLen);\n    } else if (minId != null) {\n      args.add(Keyword.MINID);\n\n      if (approximateTrimming) {\n        args.add(Protocol.BYTES_TILDE);\n      } else if (exactTrimming) {\n        args.add(Protocol.BYTES_EQUAL);\n      }\n\n      args.add(minId);\n    }\n\n    if (limit != null) {\n      args.add(Keyword.LIMIT).add(limit);\n    }\n\n    args.add(id != null ? id : StreamEntryID.NEW_ENTRY);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XAddParams that = (XAddParams) o;\n    return approximateTrimming == that.approximateTrimming && exactTrimming == that.exactTrimming\n        && nomkstream == that.nomkstream && idmpAuto == that.idmpAuto\n        && Objects.equals(id, that.id) && Objects.equals(maxLen, that.maxLen)\n        && Objects.equals(minId, that.minId) && Objects.equals(limit, that.limit)\n        && trimMode == that.trimMode && Objects.deepEquals(producerId, that.producerId)\n        && Objects.deepEquals(idempotentId, that.idempotentId);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(id, maxLen, approximateTrimming, exactTrimming, nomkstream, minId, limit,\n        trimMode, Arrays.hashCode(producerId), Arrays.hashCode(idempotentId), idmpAuto);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XAutoClaimParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class XAutoClaimParams implements IParams {\n\n  private Integer count;\n\n  public XAutoClaimParams() {\n  }\n\n  public static XAutoClaimParams xAutoClaimParams() {\n    return new XAutoClaimParams();\n  }\n\n  /**\n   * Set the count of stream entries/ids to return as part of the command output.\n   * @param count COUNT\n   * @return XAutoClaimParams\n   */\n  public XAutoClaimParams count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (count != null) {\n      args.add(Keyword.COUNT.getRaw()).add(count);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XAutoClaimParams that = (XAutoClaimParams) o;\n    return Objects.equals(count, that.count);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(count);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XCfgSetParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\n/**\n * Parameters for the XCFGSET command to configure idempotent producer settings for a stream.\n */\npublic class XCfgSetParams implements IParams {\n\n  private Integer idmpDuration;\n  private Integer idmpMaxsize;\n\n  public static XCfgSetParams xCfgSetParams() {\n    return new XCfgSetParams();\n  }\n\n  /**\n   * Set the duration (in seconds) that Redis keeps each idempotent ID. Minimum value: 1 second;\n   * Maximum value: 86400 seconds (24h); default value: 100 seconds.\n   * @param duration duration in seconds (1-86400)\n   * @return XCfgSetParams\n   * @throws IllegalArgumentException if duration is not between 1 and 86400\n   */\n  public XCfgSetParams idmpDuration(int duration) {\n    if (duration < 1 || duration > 86400) {\n      throw new IllegalArgumentException(\"IDMP-DURATION must be between 1 and 86400 seconds\");\n    }\n    this.idmpDuration = duration;\n    return this;\n  }\n\n  /**\n   * Set the maximum number of most recent idempotent IDs that Redis keeps for each producer ID.\n   * Minimum value: 1; Maximum value: 10000; default value: 100.\n   * @param maxsize maximum number of idempotent IDs per producer (1-10000)\n   * @return XCfgSetParams\n   * @throws IllegalArgumentException if maxsize is not between 1 and 10000\n   */\n  public XCfgSetParams idmpMaxsize(int maxsize) {\n    if (maxsize < 1 || maxsize > 10000) {\n      throw new IllegalArgumentException(\"IDMP-MAXSIZE must be between 1 and 10000\");\n    }\n    this.idmpMaxsize = maxsize;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (idmpDuration != null) {\n      args.add(\"IDMP-DURATION\").add(idmpDuration);\n    }\n    if (idmpMaxsize != null) {\n      args.add(\"IDMP-MAXSIZE\").add(idmpMaxsize);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XCfgSetParams that = (XCfgSetParams) o;\n    return Objects.equals(idmpDuration, that.idmpDuration)\n        && Objects.equals(idmpMaxsize, that.idmpMaxsize);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(idmpDuration, idmpMaxsize);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XClaimParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class XClaimParams implements IParams {\n\n  private Long idleTime;\n  private Long idleUnixTime;\n  private Integer retryCount;\n  private boolean force;\n\n  public XClaimParams() {\n  }\n\n  public static XClaimParams xClaimParams() {\n    return new XClaimParams();\n  }\n\n  /**\n   * Set the idle time (last time it was delivered) of the message.\n   * @param idleTime\n   * @return XClaimParams\n   */\n  public XClaimParams idle(long idleTime) {\n    this.idleTime = idleTime;\n    return this;\n  }\n\n  /**\n   * Set the idle time to a specific Unix time (in milliseconds).\n   * @param idleUnixTime\n   * @return XClaimParams\n   */\n  public XClaimParams time(long idleUnixTime) {\n    this.idleUnixTime = idleUnixTime;\n    return this;\n  }\n\n  /**\n   * Set the retry counter to the specified value.\n   * @param count\n   * @return XClaimParams\n   */\n  public XClaimParams retryCount(int count) {\n    this.retryCount = count;\n    return this;\n  }\n\n  /**\n   * Creates the pending message entry in the PEL even if certain specified IDs are not already in\n   * the PEL assigned to a different client.\n   * @return XClaimParams\n   */\n  public XClaimParams force() {\n    this.force = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (idleTime != null) {\n      args.add(Keyword.IDLE).add(idleTime);\n    }\n    if (idleUnixTime != null) {\n      args.add(Keyword.TIME).add(idleUnixTime);\n    }\n    if (retryCount != null) {\n      args.add(Keyword.RETRYCOUNT).add(retryCount);\n    }\n    if (force) {\n      args.add(Keyword.FORCE);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XClaimParams that = (XClaimParams) o;\n    return force == that.force && Objects.equals(idleTime, that.idleTime) && Objects.equals(idleUnixTime, that.idleUnixTime) && Objects.equals(retryCount, that.retryCount);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(idleTime, idleUnixTime, retryCount, force);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XPendingParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport static redis.clients.jedis.args.RawableFactory.from;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.Rawable;\n\nimport java.util.Objects;\n\npublic class XPendingParams implements IParams {\n\n  private Long idle;\n  private Rawable start;\n  private Rawable end;\n  private Integer count;\n  private Rawable consumer;\n\n  public XPendingParams(StreamEntryID start, StreamEntryID end, int count) {\n    this(start.toString(), end.toString(), count);\n  }\n\n  public XPendingParams(String start, String end, int count) {\n    this(from(start), from(end), count);\n  }\n\n  public XPendingParams(byte[] start, byte[] end, int count) {\n    this(from(start), from(end), count);\n  }\n\n  private XPendingParams(Rawable start, Rawable end, Integer count) {\n    this.start = start;\n    this.end = end;\n    this.count = count;\n  }\n\n  public XPendingParams() {\n    this.start = null;\n    this.end = null;\n    this.count = null;\n  }\n\n  public static XPendingParams xPendingParams(StreamEntryID start, StreamEntryID end, int count) {\n    return new XPendingParams(start, end, count);\n  }\n\n  public static XPendingParams xPendingParams(String start, String end, int count) {\n    return new XPendingParams(start, end, count);\n  }\n\n  public static XPendingParams xPendingParams(byte[] start, byte[] end, int count) {\n    return new XPendingParams(start, end, count);\n  }\n\n  public static XPendingParams xPendingParams() {\n    return new XPendingParams();\n  }\n\n  public XPendingParams idle(long idle) {\n    this.idle = idle;\n    return this;\n  }\n\n  public XPendingParams start(StreamEntryID start) {\n    this.start = from(start.toString());\n    return this;\n  }\n\n  public XPendingParams end(StreamEntryID end) {\n    this.end = from(end.toString());\n    return this;\n  }\n\n  public XPendingParams count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  public XPendingParams consumer(String consumer) {\n    this.consumer = from(consumer);\n    return this;\n  }\n\n  public XPendingParams consumer(byte[] consumer) {\n    this.consumer = from(consumer);\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (count == null) {\n      throw new IllegalArgumentException(\"start, end and count must be set.\");\n    }\n    if (start == null) start = from(\"-\");\n    if (end == null) end = from(\"+\");\n\n    if (idle != null) {\n      args.add(Keyword.IDLE).add(idle);\n    }\n\n    args.add(start).add(end).add(count);\n\n    if (consumer != null) {\n      args.add(consumer);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XPendingParams that = (XPendingParams) o;\n    return Objects.equals(idle, that.idle) && Objects.equals(start, that.start) && Objects.equals(end, that.end) && Objects.equals(count, that.count) && Objects.equals(consumer, that.consumer);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(idle, start, end, count, consumer);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XReadGroupParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class XReadGroupParams implements IParams {\n\n  private Integer count = null;\n  private Integer block = null;\n  private boolean noack = false;\n  private Long claim = null;\n\n  public static XReadGroupParams xReadGroupParams() {\n    return new XReadGroupParams();\n  }\n\n  public XReadGroupParams count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  public XReadGroupParams block(int block) {\n    this.block = block;\n    return this;\n  }\n\n  public XReadGroupParams noAck() {\n    this.noack = true;\n    return this;\n  }\n\n  public XReadGroupParams claim(long minIdleMillis) {\n    this.claim = minIdleMillis;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (count != null) {\n      args.add(Keyword.COUNT).add(count);\n    }\n    if (block != null) {\n      args.add(Keyword.BLOCK).add(block).blocking();\n    }\n    if (noack) {\n      args.add(Keyword.NOACK);\n    }\n    if (claim != null) {\n      args.add(Keyword.CLAIM).add(claim);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XReadGroupParams that = (XReadGroupParams) o;\n    return noack == that.noack && Objects.equals(count, that.count) && Objects.equals(block, that.block)\n        && Objects.equals(claim, that.claim);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(count, block, noack, claim);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XReadParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class XReadParams implements IParams {\n\n  private Integer count = null;\n  private Integer block = null;\n\n  public static XReadParams xReadParams() {\n    return new XReadParams();\n  }\n\n  public XReadParams count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  public XReadParams block(int block) {\n    this.block = block;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (count != null) {\n      args.add(Keyword.COUNT).add(count);\n    }\n    if (block != null) {\n      args.add(Keyword.BLOCK).add(block).blocking();\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XReadParams that = (XReadParams) o;\n    return Objects.equals(count, that.count) && Objects.equals(block, that.block);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(count, block);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/XTrimParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\n\nimport java.util.Objects;\n\npublic class XTrimParams implements IParams {\n\n  private Long maxLen;\n\n  private boolean approximateTrimming;\n\n  private boolean exactTrimming;\n\n  private String minId;\n\n  private Long limit;\n\n  private StreamDeletionPolicy trimMode;\n\n  public static XTrimParams xTrimParams() {\n    return new XTrimParams();\n  }\n\n\n  public XTrimParams maxLen(long maxLen) {\n    this.maxLen = maxLen;\n    return this;\n  }\n\n  public XTrimParams minId(String minId) {\n    this.minId = minId;\n    return this;\n  }\n\n  public XTrimParams approximateTrimming() {\n    this.approximateTrimming = true;\n    return this;\n  }\n\n  public XTrimParams exactTrimming() {\n    this.exactTrimming = true;\n    return this;\n  }\n\n  public XTrimParams limit(long limit) {\n    this.limit = limit;\n    return this;\n  }\n\n  /**\n   * Defines desired behaviour for handling consumer group references.\n   * see {@link StreamDeletionPolicy} for details.\n   *\n   * @return XAddParams\n   */\n  public XTrimParams trimmingMode(StreamDeletionPolicy trimMode) {\n    this.trimMode = trimMode;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (maxLen != null) {\n      args.add(Keyword.MAXLEN);\n\n      if (approximateTrimming) {\n        args.add(Protocol.BYTES_TILDE);\n      } else if (exactTrimming) {\n        args.add(Protocol.BYTES_EQUAL);\n      }\n\n      args.add(Protocol.toByteArray(maxLen));\n    } else if (minId != null) {\n      args.add(Keyword.MINID);\n\n      if (approximateTrimming) {\n        args.add(Protocol.BYTES_TILDE);\n      } else if (exactTrimming) {\n        args.add(Protocol.BYTES_EQUAL);\n      }\n\n      args.add(minId);\n    }\n\n    if (limit != null) {\n      args.add(Keyword.LIMIT).add(limit);\n    }\n\n    if (trimMode != null) {\n      args.add(trimMode);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    XTrimParams that = (XTrimParams) o;\n    return approximateTrimming == that.approximateTrimming && exactTrimming == that.exactTrimming && Objects.equals(maxLen, that.maxLen) && Objects.equals(minId, that.minId) && Objects.equals(limit, that.limit) && trimMode == that.trimMode;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(maxLen, approximateTrimming, exactTrimming, minId, limit, trimMode);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ZAddParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\npublic class ZAddParams implements IParams {\n\n  private Keyword existence;\n  private Keyword comparison;\n  private boolean change;\n\n  public ZAddParams() {\n  }\n\n  public static ZAddParams zAddParams() {\n    return new ZAddParams();\n  }\n\n  /**\n   * Only set the key if it does not already exist.\n   * @return ZAddParams\n   */\n  public ZAddParams nx() {\n    this.existence = Keyword.NX;\n    return this;\n  }\n\n  /**\n   * Only set the key if it already exists.\n   * @return ZAddParams\n   */\n  public ZAddParams xx() {\n    this.existence = Keyword.XX;\n    return this;\n  }\n\n  /**\n   * Only update existing elements if the new score is greater than the current score.\n   * @return ZAddParams\n   */\n  public ZAddParams gt() {\n    this.comparison = Keyword.GT;\n    return this;\n  }\n\n  /**\n   * Only update existing elements if the new score is less than the current score.\n   * @return ZAddParams\n   */\n  public ZAddParams lt() {\n    this.comparison = Keyword.LT;\n    return this;\n  }\n\n  /**\n   * Modify the return value from the number of new elements added to the total number of elements\n   * changed\n   * @return ZAddParams\n   */\n  public ZAddParams ch() {\n    this.change = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (existence != null) {\n      args.add(existence);\n    }\n    if (comparison != null) {\n      args.add(comparison);\n    }\n    if (change) {\n      args.add(Keyword.CH);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ZAddParams that = (ZAddParams) o;\n    return change == that.change && existence == that.existence && comparison == that.comparison;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(existence, comparison, change);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ZIncrByParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\n\nimport java.util.Objects;\n\n/**\n * Parameters for ZINCRBY commands. In fact, Redis doesn't have parameters for ZINCRBY. Instead\n * Redis has INCR parameter for ZADD.\n * <p>\n * When users call ZADD with INCR option, its restriction (only one member) and return type is same\n * to ZINCRBY. Document page for ZADD also describes INCR option to act like ZINCRBY. So we decided\n * to wrap \"ZADD with INCR option\" to ZINCRBY.\n * <p>\n * Works with Redis 3.0.2 and onwards.\n */\npublic class ZIncrByParams implements IParams {\n\n  private Keyword existance;\n\n  public ZIncrByParams() {\n  }\n\n  public static ZIncrByParams zIncrByParams() {\n    return new ZIncrByParams();\n  }\n\n  /**\n   * Only set the key if it does not already exist.\n   * @return ZIncrByParams\n   */\n  public ZIncrByParams nx() {\n    this.existance = Keyword.NX;\n    return this;\n  }\n\n  /**\n   * Only set the key if it already exist.\n   * @return ZIncrByParams\n   */\n  public ZIncrByParams xx() {\n    this.existance = Keyword.XX;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (existance != null) {\n      args.add(existance);\n    }\n\n    args.add(Keyword.INCR);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ZIncrByParams that = (ZIncrByParams) o;\n    return existance == that.existance;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(existance);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ZParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class ZParams implements IParams {\n\n  public enum Aggregate implements Rawable {\n\n    SUM, MIN, MAX;\n\n    private final byte[] raw;\n\n    private Aggregate() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  private final List<Object> params = new ArrayList<>();\n\n  public ZParams weights(final double... weights) {\n    params.add(Keyword.WEIGHTS);\n    for (final double weight : weights) {\n      params.add(weight);\n    }\n    return this;\n  }\n\n  public ZParams aggregate(final Aggregate aggregate) {\n    params.add(Keyword.AGGREGATE);\n    params.add(aggregate);\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addObjects(params);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ZParams zParams = (ZParams) o;\n    return Objects.equals(params, zParams.params);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(params);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/ZRangeParams.java",
    "content": "package redis.clients.jedis.params;\n\nimport static redis.clients.jedis.Protocol.Keyword.BYLEX;\nimport static redis.clients.jedis.Protocol.Keyword.BYSCORE;\nimport static redis.clients.jedis.args.RawableFactory.from;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.args.Rawable;\n\nimport java.util.Objects;\n\npublic class ZRangeParams implements IParams {\n\n  private final Keyword by;\n  private final Rawable min;\n  private final Rawable max;\n  private boolean rev = false;\n\n  private boolean limit = false;\n  private int offset;\n  private int count;\n\n  private ZRangeParams() {\n    throw new InstantiationError(\"Empty constructor must not be called.\");\n  }\n\n  public ZRangeParams(int min, int max) {\n    this((long) min, (long) max);\n  }\n\n  public ZRangeParams(long min, long max) {\n    this.by = null;\n    this.min = from(min);\n    this.max = from(max);\n  }\n\n  public static ZRangeParams zrangeParams(int min, int max) {\n    return new ZRangeParams(min, max);\n  }\n\n  public static ZRangeParams zrangeParams(long min, long max) {\n    return new ZRangeParams(min, max);\n  }\n\n  public ZRangeParams(double min, double max) {\n    this.by = BYSCORE;\n    this.min = from(min);\n    this.max = from(max);\n  }\n\n  public static ZRangeParams zrangeByScoreParams(double min, double max) {\n    return new ZRangeParams(min, max);\n  }\n\n  private ZRangeParams(Keyword by, Rawable min, Rawable max) {\n    if (by == null || by == BYSCORE || by == BYLEX) {\n      // ok\n    } else {\n      throw new IllegalArgumentException(by.name() + \" is not a valid ZRANGE type argument.\");\n    }\n    this.by = by;\n    this.min = min;\n    this.max = max;\n  }\n\n  public ZRangeParams(Keyword by, String min, String max) {\n    this(by, from(min), from(max));\n  }\n\n  public ZRangeParams(Keyword by, byte[] min, byte[] max) {\n    this(by, from(min), from(max));\n  }\n\n  public static ZRangeParams zrangeByLexParams(String min, String max) {\n    return new ZRangeParams(BYLEX, min, max);\n  }\n\n  public static ZRangeParams zrangeByLexParams(byte[] min, byte[] max) {\n    return new ZRangeParams(BYLEX, min, max);\n  }\n\n  public ZRangeParams rev() {\n    this.rev = true;\n    return this;\n  }\n\n  public ZRangeParams limit(int offset, int count) {\n    this.limit = true;\n    this.offset = offset;\n    this.count = count;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    args.add(min).add(max);\n    if (by != null) {\n      args.add(by);\n    }\n\n    if (rev) {\n      args.add(Keyword.REV);\n    }\n\n    if (limit) {\n      args.add(Keyword.LIMIT).add(offset).add(count);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    ZRangeParams that = (ZRangeParams) o;\n    return rev == that.rev && limit == that.limit && offset == that.offset && count == that.count && by == that.by && Objects.equals(min, that.min) && Objects.equals(max, that.max);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(by, min, max, rev, limit, offset, count);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/params/package-info.java",
    "content": "/**\n * This package contains the classes that represent optional parameters of core Redis commands.\n */\npackage redis.clients.jedis.params;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java",
    "content": "package redis.clients.jedis.providers;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.JedisClusterInfoCache;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\nimport redis.clients.jedis.exceptions.JedisException;\n\nimport static redis.clients.jedis.RedisClusterClient.INIT_NO_ERROR_PROPERTY;\n\npublic class ClusterConnectionProvider implements ConnectionProvider {\n\n  protected final JedisClusterInfoCache cache;\n\n  public ClusterConnectionProvider(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig) {\n    this.cache = new JedisClusterInfoCache(clientConfig, clusterNodes);\n    initializeSlotsCache(clusterNodes, clientConfig);\n  }\n\n  @Experimental\n  public ClusterConnectionProvider(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache) {\n    this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, clusterNodes);\n    initializeSlotsCache(clusterNodes, clientConfig);\n  }\n\n  public ClusterConnectionProvider(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this.cache = new JedisClusterInfoCache(clientConfig, poolConfig, clusterNodes);\n    initializeSlotsCache(clusterNodes, clientConfig);\n  }\n\n  @Experimental\n  public ClusterConnectionProvider(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes);\n    initializeSlotsCache(clusterNodes, clientConfig);\n  }\n\n  public ClusterConnectionProvider(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig, Duration topologyRefreshPeriod) {\n    this.cache = new JedisClusterInfoCache(clientConfig, poolConfig, clusterNodes, topologyRefreshPeriod);\n    initializeSlotsCache(clusterNodes, clientConfig);\n  }\n\n  @Experimental\n  public ClusterConnectionProvider(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache,\n      GenericObjectPoolConfig<Connection> poolConfig, Duration topologyRefreshPeriod) {\n    this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes, topologyRefreshPeriod);\n    initializeSlotsCache(clusterNodes, clientConfig);\n  }\n\n  private void initializeSlotsCache(Set<HostAndPort> startNodes, JedisClientConfig clientConfig) {\n    if (startNodes.isEmpty()) {\n      throw new JedisClusterOperationException(\"No nodes to initialize cluster slots cache.\");\n    }\n\n    ArrayList<HostAndPort> startNodeList = new ArrayList<>(startNodes);\n    Collections.shuffle(startNodeList);\n\n    JedisException firstException = null;\n    for (HostAndPort hostAndPort : startNodeList) {\n      try (Connection jedis = new Connection(hostAndPort, clientConfig)) {\n        cache.discoverClusterNodesAndSlots(jedis);\n        return;\n      } catch (JedisException e) {\n        if (firstException == null) {\n          firstException = e;\n        }\n        // try next nodes\n      }\n    }\n\n    if (System.getProperty(INIT_NO_ERROR_PROPERTY) != null) {\n      return;\n    }\n    JedisClusterOperationException uninitializedException\n        = new JedisClusterOperationException(\"Could not initialize cluster slots cache.\");\n    uninitializedException.addSuppressed(firstException);\n    throw uninitializedException;\n  }\n\n  @Override\n  public void close() {\n    cache.close();\n  }\n\n  public void renewSlotCache() {\n    cache.renewClusterSlots(null);\n  }\n\n  public void renewSlotCache(Connection jedis) {\n    cache.renewClusterSlots(jedis);\n  }\n\n  public Map<String, ConnectionPool> getNodes() {\n    return cache.getNodes();\n  }\n\n  public Map<String, ConnectionPool> getPrimaryNodes() {\n    return cache.getPrimaryNodes();\n  }\n\n  public HostAndPort getNode(int slot) {\n    return slot >= 0 ? cache.getSlotNode(slot) : null;\n  }\n\n  public Connection getConnection(HostAndPort node) {\n    return node != null ? cache.setupNodeIfNotExist(node).getResource() : getConnection();\n  }\n\n  @Override\n  public Connection getConnection(CommandArguments args) {\n    Set<Integer> slots = args.getKeyHashSlots();\n\n    if (slots.size() > 1) {\n      throw new JedisClusterOperationException(\"Cannot get connection for command with multiple hash slots\");\n    }\n\n    int slot = slots.iterator().next();\n    return slot >= 0 ? getConnectionFromSlot(slot) : getConnection();\n  }\n\n  public Connection getReplicaConnection(CommandArguments args) {\n    Set<Integer> slots = args.getKeyHashSlots();\n\n    if (slots.size() > 1) {\n      throw new JedisClusterOperationException(\"Cannot get connection for command with multiple hash slots\");\n    }\n\n    int slot = slots.iterator().next();\n    return slot >= 0 ? getReplicaConnectionFromSlot(slot) : getConnection();\n  }\n\n  @Override\n  public Connection getConnection() {\n    // In antirez's redis-rb-cluster implementation, getRandomConnection always return\n    // valid connection (able to ping-pong) or exception if all connections are invalid\n\n    List<ConnectionPool> pools = cache.getShuffledPrimaryNodesPool();\n\n    JedisException suppressed = null;\n    for (ConnectionPool pool : pools) {\n      Connection jedis = null;\n      try {\n        jedis = pool.getResource();\n        if (jedis == null) {\n          continue;\n        }\n\n        jedis.ping();\n        return jedis;\n\n      } catch (JedisException ex) {\n        if (suppressed == null) { // remembering first suppressed exception\n          suppressed = ex;\n        }\n        if (jedis != null) {\n          jedis.close();\n        }\n      }\n    }\n\n    JedisClusterOperationException noReachableNode = new JedisClusterOperationException(\"No reachable node in cluster.\");\n    if (suppressed != null) {\n      noReachableNode.addSuppressed(suppressed);\n    }\n    throw noReachableNode;\n  }\n\n  public Connection getConnectionFromSlot(int slot) {\n    ConnectionPool connectionPool = cache.getSlotPool(slot);\n    if (connectionPool != null) {\n      // It can't guaranteed to get valid connection because of node assignment\n      return connectionPool.getResource();\n    } else {\n      // It's abnormal situation for cluster mode that we have just nothing for slot.\n      // Try to rediscover state\n      renewSlotCache();\n      connectionPool = cache.getSlotPool(slot);\n      if (connectionPool != null) {\n        return connectionPool.getResource();\n      } else {\n        // no choice, fallback to new connection to random node\n        return getConnection();\n      }\n    }\n  }\n\n  public Connection getReplicaConnectionFromSlot(int slot) {\n    List<ConnectionPool> connectionPools = cache.getSlotReplicaPools(slot);\n    ThreadLocalRandom random = ThreadLocalRandom.current();\n    if (connectionPools != null && !connectionPools.isEmpty()) {\n      // pick up randomly a connection\n      int idx = random.nextInt(connectionPools.size());\n      return connectionPools.get(idx).getResource();\n    }\n\n    renewSlotCache();\n    connectionPools = cache.getSlotReplicaPools(slot);\n    if (connectionPools != null && !connectionPools.isEmpty()) {\n      int idx = random.nextInt(connectionPools.size());\n      return connectionPools.get(idx).getResource();\n    }\n\n    return getConnectionFromSlot(slot);\n  }\n\n\n  @Override\n  public Map<String, ConnectionPool> getConnectionMap() {\n    return Collections.unmodifiableMap(getNodes());\n  }\n\n  @Override\n  public Map<String, ConnectionPool> getPrimaryNodesConnectionMap() {\n    return Collections.unmodifiableMap(getPrimaryNodes());\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/providers/ConnectionProvider.java",
    "content": "package redis.clients.jedis.providers;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\n\npublic interface ConnectionProvider extends AutoCloseable {\n\n  Connection getConnection();\n\n  Connection getConnection(CommandArguments args);\n\n  default Map<?, ?> getConnectionMap() {\n    final Connection c = getConnection();\n    return Collections.singletonMap(c.toString(), c);\n  }\n\n  default Map<?, ?> getPrimaryNodesConnectionMap() {\n    final Connection c = getConnection();\n    return Collections.singletonMap(c.toString(), c);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/providers/ManagedConnectionProvider.java",
    "content": "package redis.clients.jedis.providers;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\n\npublic class ManagedConnectionProvider implements ConnectionProvider {\n\n  private Connection connection;\n\n  public final void setConnection(Connection connection) {\n    this.connection = connection;\n  }\n\n  @Override\n  public void close() {\n  }\n\n  @Override\n  public final Connection getConnection() {\n    return connection;\n  }\n\n  @Override\n  public final Connection getConnection(CommandArguments args) {\n    return connection;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java",
    "content": "package redis.clients.jedis.providers;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionFactory;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.util.Pool;\n\npublic class PooledConnectionProvider implements ConnectionProvider {\n\n  private final Pool<Connection> pool;\n  private Object connectionMapKey = \"\";\n\n  public PooledConnectionProvider(HostAndPort hostAndPort) {\n    this(new ConnectionFactory(hostAndPort));\n    this.connectionMapKey = hostAndPort;\n  }\n\n  public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig) {\n    this(new ConnectionPool(hostAndPort, clientConfig));\n    this.connectionMapKey = hostAndPort;\n  }\n\n  @Experimental\n  public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache) {\n    this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache));\n    this.connectionMapKey = hostAndPort;\n  }\n\n  public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ConnectionPool(hostAndPort, clientConfig, poolConfig));\n    this.connectionMapKey = hostAndPort;\n  }\n\n  @Experimental\n  public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache, poolConfig));\n    this.connectionMapKey = hostAndPort;\n  }\n\n  public PooledConnectionProvider(PooledObjectFactory<Connection> factory) {\n    this(new ConnectionPool(factory));\n    this.connectionMapKey = factory;\n  }\n\n  public PooledConnectionProvider(PooledObjectFactory<Connection> factory,\n      GenericObjectPoolConfig<Connection> poolConfig) {\n    this(new ConnectionPool(factory, poolConfig));\n    this.connectionMapKey = factory;\n  }\n\n  private PooledConnectionProvider(Pool<Connection> pool) {\n    this.pool = pool;\n  }\n\n  @Override\n  public void close() {\n    pool.close();\n  }\n\n  public final Pool<Connection> getPool() {\n    return pool;\n  }\n\n  @Override\n  public Connection getConnection() {\n    return pool.getResource();\n  }\n\n  @Override\n  public Connection getConnection(CommandArguments args) {\n    return pool.getResource();\n  }\n\n  @Override\n  public Map<?, Pool<Connection>> getConnectionMap() {\n    return Collections.singletonMap(connectionMapKey, pool);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java",
    "content": "package redis.clients.jedis.providers;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.Delay;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.util.Pool;\n\npublic class SentineledConnectionProvider implements ConnectionProvider {\n\n  private static final Logger LOG = LoggerFactory.getLogger(SentineledConnectionProvider.class);\n\n  protected static final long DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS = 5000;\n\n  public static final Delay DEFAULT_RESUBSCRIBE_DELAY = Delay\n          .constant(Duration.ofMillis(DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS));\n\n  private static final Sleeper DEFAULT_SLEEPER = Thread::sleep;\n\n  private volatile HostAndPort currentMaster;\n\n  private volatile ConnectionPool pool;\n\n  private final String masterName;\n\n  private final JedisClientConfig masterClientConfig;\n\n  private final Cache clientSideCache;\n\n  private final GenericObjectPoolConfig<Connection> masterPoolConfig;\n\n  protected final Collection<SentinelListener> sentinelListeners = new ArrayList<>();\n\n  private final JedisClientConfig sentinelClientConfig;\n\n  private final Delay resubscribeDelay;\n\n  private final Lock initPoolLock = new ReentrantLock(true);\n\n  private final SentinelConnectionFactory sentinelConnectionFactory;\n\n  private final Sleeper sleeper;\n\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    this(masterName, masterClientConfig, null, null, sentinels, sentinelClientConfig);\n  }\n\n  @Experimental\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n      Cache clientSideCache, Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    this(masterName, masterClientConfig, clientSideCache, null, sentinels, sentinelClientConfig);\n  }\n\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n      final GenericObjectPoolConfig<Connection> poolConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    this(masterName, masterClientConfig, poolConfig, sentinels, sentinelClientConfig, DEFAULT_RESUBSCRIBE_DELAY);\n  }\n\n  @Experimental\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n          Cache clientSideCache, final GenericObjectPoolConfig<Connection> poolConfig,\n          Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig) {\n    this(masterName, masterClientConfig, clientSideCache, poolConfig, sentinels, sentinelClientConfig,\n            DEFAULT_RESUBSCRIBE_DELAY);\n  }\n\n  /**\n   *\n   * @deprecated use\n   *             {@link #SentineledConnectionProvider(String, JedisClientConfig, GenericObjectPoolConfig, Set, JedisClientConfig, Delay)}\n   */\n  @Deprecated\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n          final GenericObjectPoolConfig<Connection> poolConfig, Set<HostAndPort> sentinels,\n          final JedisClientConfig sentinelClientConfig, final long subscribeRetryWaitTimeMillis) {\n    this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig,\n        Delay.constant(Duration.ofMillis(subscribeRetryWaitTimeMillis)));\n  }\n\n  /**\n   * Creates a new SentineledConnectionProvider.\n   *\n   * @param masterName name of the master\n   * @param masterClientConfig client configuration for the master\n   * @param poolConfig pool configuration for the master\n   * @param sentinels set of sentinel addresses\n   * @param sentinelClientConfig client configuration for the sentinel\n   * @param subscribeRetryWaitTimeMillis delay before resubscribing to sentinel after a connection loss\n   *\n   * @since 7.3.0\n   */\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n          final GenericObjectPoolConfig<Connection> poolConfig, Set<HostAndPort> sentinels,\n          final JedisClientConfig sentinelClientConfig, final Delay subscribeRetryWaitTimeMillis) {\n    this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig, subscribeRetryWaitTimeMillis);\n  }\n\n  /**\n   * @deprecated use\n   *             {@link #SentineledConnectionProvider(String, JedisClientConfig, Cache, GenericObjectPoolConfig, Set, JedisClientConfig, Delay)}\n   */\n  @Experimental\n  @Deprecated\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n      Cache clientSideCache, final GenericObjectPoolConfig<Connection> poolConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig,\n      final long subscribeRetryWaitTimeMillis) {\n\n    this(masterName, masterClientConfig, clientSideCache, poolConfig, sentinels,\n        sentinelClientConfig, Delay.constant(Duration.ofMillis(subscribeRetryWaitTimeMillis)));\n  }\n\n  @Experimental\n  public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n      Cache clientSideCache, final GenericObjectPoolConfig<Connection> poolConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig,\n      final Delay resubscribeDelay) {\n    this(masterName, masterClientConfig, clientSideCache, poolConfig, sentinels,\n        sentinelClientConfig, resubscribeDelay, null, null);\n  }\n\n  SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,\n      Cache clientSideCache, final GenericObjectPoolConfig<Connection> poolConfig,\n      Set<HostAndPort> sentinels, final JedisClientConfig sentinelClientConfig,\n      final Delay resubscribeDelay, SentinelConnectionFactory sentinelConnectionFactory,\n      Sleeper sleeper) {\n\n    this.masterName = masterName;\n    this.masterClientConfig = masterClientConfig;\n    this.clientSideCache = clientSideCache;\n    this.masterPoolConfig = poolConfig;\n\n    this.sentinelClientConfig = sentinelClientConfig;\n    this.resubscribeDelay = resubscribeDelay;\n\n    this.sentinelConnectionFactory = sentinelConnectionFactory != null ? sentinelConnectionFactory\n        : defaultSentinelConnectionFactory();\n\n    this.sleeper = sleeper != null ? sleeper : DEFAULT_SLEEPER;\n\n    HostAndPort master = initSentinels(sentinels);\n    initMaster(master);\n  }\n\n  @Override\n  public Connection getConnection() {\n    return pool.getResource();\n  }\n\n  @Override\n  public Connection getConnection(CommandArguments args) {\n    return pool.getResource();\n  }\n\n  @Override\n  public Map<?, Pool<Connection>> getConnectionMap() {\n    return Collections.singletonMap(currentMaster, pool);\n  }\n\n  @Override\n  public Map<?, Pool<Connection>> getPrimaryNodesConnectionMap() {\n    return Collections.singletonMap(currentMaster, pool);\n  }\n\n  @Override\n  public void close() {\n    sentinelListeners.forEach(SentinelListener::shutdown);\n\n    pool.close();\n  }\n\n  public HostAndPort getCurrentMaster() {\n    return currentMaster;\n  }\n\n  private void initMaster(HostAndPort master) {\n    initPoolLock.lock();\n\n    try {\n      if (!master.equals(currentMaster)) {\n        currentMaster = master;\n\n        ConnectionPool newPool = createNodePool(currentMaster);\n\n        ConnectionPool existingPool = pool;\n        pool = newPool;\n        LOG.info(\"Created connection pool to master at {}.\", master);\n        if (clientSideCache != null) {\n          clientSideCache.flush();\n        }\n\n        if (existingPool != null) {\n          // although we clear the pool, we still have to check the returned object in getResource,\n          // this call only clears idle instances, not borrowed instances\n          // existingPool.clear(); // necessary??\n          existingPool.close();\n        }\n      }\n    } finally {\n      initPoolLock.unlock();\n    }\n  }\n\n  private ConnectionPool createNodePool(HostAndPort master) {\n    if (masterPoolConfig == null) {\n      if (clientSideCache == null) {\n        return new ConnectionPool(master, masterClientConfig);\n      } else {\n        return new ConnectionPool(master, masterClientConfig, clientSideCache);\n      }\n    } else {\n      if (clientSideCache == null) {\n        return new ConnectionPool(master, masterClientConfig, masterPoolConfig);\n      } else {\n        return new ConnectionPool(master, masterClientConfig, clientSideCache, masterPoolConfig);\n      }\n    }\n  }\n\n  private HostAndPort initSentinels(Set<HostAndPort> sentinels) {\n\n    HostAndPort master = null;\n    boolean sentinelAvailable = false;\n\n    LOG.debug(\"Trying to find master from available sentinels...\");\n\n    for (HostAndPort sentinel : sentinels) {\n\n      LOG.debug(\"Connecting to Sentinel {}...\", sentinel);\n\n      try (Jedis jedis = sentinelConnectionFactory.createConnection(sentinel,\n        sentinelClientConfig)) {\n\n        List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);\n\n        // connected to sentinel...\n        sentinelAvailable = true;\n\n        if (masterAddr == null || masterAddr.size() != 2) {\n          LOG.warn(\"Sentinel {} is not monitoring master {}.\", sentinel, masterName);\n          continue;\n        }\n\n        master = toHostAndPort(masterAddr);\n        LOG.debug(\"Redis master reported at {}.\", master);\n        break;\n      } catch (JedisException e) {\n        // resolves #1036, it should handle JedisException there's another chance\n        // of raising JedisDataException\n        LOG.warn(\"Could not get master address from {}.\", sentinel, e);\n      }\n    }\n\n    if (master == null) {\n      if (sentinelAvailable) {\n        // can connect to sentinel, but master name seems to not monitored\n        throw new JedisException(\n            \"Can connect to sentinel, but \" + masterName + \" seems to be not monitored.\");\n      } else {\n        throw new JedisConnectionException(\n            \"All sentinels down, cannot determine where \" + masterName + \" is running.\");\n      }\n    }\n\n    LOG.info(\"Redis master running at {}. Starting sentinel listeners...\", master);\n\n    for (HostAndPort sentinel : sentinels) {\n\n      SentinelListener listener = new SentinelListener(sentinel);\n      // whether SentinelListener threads are alive or not, process can be stopped\n      listener.setDaemon(true);\n      sentinelListeners.add(listener);\n      listener.start();\n    }\n\n    return master;\n  }\n\n  /**\n   * Must be of size 2.\n   */\n  private static HostAndPort toHostAndPort(List<String> masterAddr) {\n    return toHostAndPort(masterAddr.get(0), masterAddr.get(1));\n  }\n\n  private static HostAndPort toHostAndPort(String hostStr, String portStr) {\n    return new HostAndPort(hostStr, Integer.parseInt(portStr));\n  }\n\n  protected class SentinelListener extends Thread {\n\n    protected final HostAndPort node;\n    protected volatile Jedis sentinelJedis;\n    protected AtomicBoolean running = new AtomicBoolean(false);\n    protected long subscribeAttempt = 0;\n\n    public SentinelListener(HostAndPort node) {\n      super(String.format(\"%s-SentinelListener-[%s]\", masterName, node.toString()));\n      this.node = node;\n    }\n\n    @Override\n    public void run() {\n\n      running.set(true);\n\n      while (running.get()) {\n        try {\n          // double check that it is not being shutdown\n          if (!running.get()) {\n            break;\n          }\n\n          sentinelJedis = sentinelConnectionFactory.createConnection(node, sentinelClientConfig);\n\n          // code for active refresh\n          List<String> masterAddr = sentinelJedis.sentinelGetMasterAddrByName(masterName);\n          if (masterAddr == null || masterAddr.size() != 2) {\n            LOG.warn(\"Can not get master {} address. Sentinel: {}.\", masterName, node);\n          } else {\n            initMaster(toHostAndPort(masterAddr));\n          }\n\n          sentinelJedis.subscribe(new JedisPubSub() {\n            @Override\n            public void onSubscribe(String channel, int subscribedChannels) {\n              // Successfully subscribed - reset attempt counter\n              subscribeAttempt = 0;\n              LOG.debug(\"Successfully subscribed to {} on Sentinel {}. Reset attempt counter.\",\n                channel, node);\n            }\n\n            @Override\n            public void onMessage(String channel, String message) {\n              LOG.debug(\"Sentinel {} published: {}.\", node, message);\n\n              String[] switchMasterMsg = message.split(\" \");\n\n              if (switchMasterMsg.length > 3) {\n\n                if (masterName.equals(switchMasterMsg[0])) {\n                  initMaster(toHostAndPort(switchMasterMsg[3], switchMasterMsg[4]));\n                } else {\n                  LOG.debug(\"Ignoring message on +switch-master for master {}. Our master is {}.\",\n                    switchMasterMsg[0], masterName);\n                }\n\n              } else {\n                LOG.error(\"Invalid message received on sentinel {} on channel +switch-master: {}.\",\n                  node, message);\n              }\n            }\n          }, \"+switch-master\");\n\n        } catch (JedisException e) {\n\n          if (running.get()) {\n            long subscribeRetryWaitTimeMillis = resubscribeDelay.delay(subscribeAttempt).toMillis();\n            subscribeAttempt++;\n            LOG.warn(\"Lost connection to Sentinel {}. Sleeping {}ms and retrying.\", node,\n              subscribeRetryWaitTimeMillis, e);\n            try {\n              sleeper.sleep(subscribeRetryWaitTimeMillis);\n            } catch (InterruptedException se) {\n              LOG.error(\"Sleep interrupted.\", se);\n            }\n          } else {\n            LOG.debug(\"Unsubscribing from sentinel {}.\", node);\n          }\n        } finally {\n          IOUtils.closeQuietly(sentinelJedis);\n        }\n      }\n    }\n\n    // must not throw exception\n    public void shutdown() {\n      try {\n        LOG.debug(\"Shutting down listener on {}.\", node);\n        running.set(false);\n        // This isn't good, the Jedis object is not thread safe\n        if (sentinelJedis != null) {\n          sentinelJedis.close();\n        }\n      } catch (RuntimeException e) {\n        LOG.error(\"Error while shutting down.\", e);\n      }\n    }\n  }\n\n  protected SentinelConnectionFactory defaultSentinelConnectionFactory() {\n    return (node, config) -> new Jedis(node, config);\n  }\n\n  @FunctionalInterface\n  interface Sleeper {\n\n    void sleep(long millis) throws InterruptedException;\n\n  }\n\n  @FunctionalInterface\n  protected interface SentinelConnectionFactory {\n\n    Jedis createConnection(HostAndPort node, JedisClientConfig config);\n\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/providers/package-info.java",
    "content": "/**\n * This package contains the implementations of ConnectionProvider interface.\n */\npackage redis.clients.jedis.providers;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/AccessControlLogEntry.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.Serializable;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * This class holds information about an Access Control Log entry (returned by ACL LOG command) They\n * can be accessed via getters. For future purpose there is also {@link #getlogEntry} method that\n * returns a generic {@code Map} - in case where more info is returned from a server\n */\n// TODO: remove\npublic class AccessControlLogEntry implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  public static final String COUNT = \"count\";\n  public static final String REASON = \"reason\";\n  public static final String CONTEXT = \"context\";\n  public static final String OBJECT = \"object\";\n  public static final String USERNAME = \"username\";\n  public static final String AGE_SECONDS = \"age-seconds\";\n  public static final String CLIENT_INFO = \"client-info\";\n  // Redis 7.2\n  public static final String ENTRY_ID = \"entry-id\";\n  public static final String TIMESTAMP_CREATED = \"timestamp-created\";\n  public static final String TIMESTAMP_LAST_UPDATED = \"timestamp-last-updated\";\n\n  private final long count;\n  private final String reason;\n  private final String context;\n  private final String object;\n  private final String username;\n  private final Double ageSeconds;\n  private final Map<String, String> clientInfo;\n  private final Map<String, Object> logEntry;\n  private final long entryId;\n  private final long timestampCreated;\n  private final long timestampLastUpdated;\n\n  public AccessControlLogEntry(Map<String, Object> map) {\n    count = (long) map.get(COUNT);\n    reason = (String) map.get(REASON);\n    context = (String) map.get(CONTEXT);\n    object = (String) map.get(OBJECT);\n    username = (String) map.get(USERNAME);\n    ageSeconds = (Double) map.get(AGE_SECONDS);\n    clientInfo = getMapFromRawClientInfo((String) map.get(CLIENT_INFO));\n    logEntry = map;\n    // Redis 7.2\n    entryId = map.containsKey(ENTRY_ID) ? (long) map.get(ENTRY_ID) : -1L;\n    timestampCreated = map.containsKey(TIMESTAMP_CREATED) ? (long) map.get(TIMESTAMP_CREATED) : -1L;\n    timestampLastUpdated = map.containsKey(TIMESTAMP_LAST_UPDATED) ? (long) map.get(TIMESTAMP_LAST_UPDATED) : -1L;\n  }\n\n  public long getCount() {\n    return count;\n  }\n\n  public String getReason() {\n    return reason;\n  }\n\n  public String getContext() {\n    return context;\n  }\n\n  public String getObject() {\n    return object;\n  }\n\n  public String getUsername() {\n    return username;\n  }\n\n  public Double getAgeSeconds() {\n    return ageSeconds;\n  }\n\n  public Map<String, String> getClientInfo() {\n    return clientInfo;\n  }\n\n  /**\n   * @return Generic map containing all key-value pairs returned by the server\n   */\n  public Map<String, Object> getlogEntry() {\n    return logEntry;\n  }\n\n  public long getEntryId() {\n    return entryId;\n  }\n\n  public long getTimestampCreated() {\n    return timestampCreated;\n  }\n\n  public long getTimestampLastUpdated() {\n    return timestampLastUpdated;\n  }\n\n  /**\n   * Convert the client-info string into a Map of String. When the value is empty, the value in the\n   * map is set to an empty string The key order is maintained to reflect the string return by Redis\n   * @param clientInfo\n   * @return A Map with all client info\n   */\n  private Map<String, String> getMapFromRawClientInfo(String clientInfo) {\n    String[] entries = clientInfo.split(\" \");\n    Map<String, String> clientInfoMap = new LinkedHashMap<>(entries.length);\n    for (String entry : entries) {\n      String[] kvArray = entry.split(\"=\");\n      clientInfoMap.put(kvArray[0], (kvArray.length == 2) ? kvArray[1] : \"\");\n    }\n    return clientInfoMap;\n  }\n\n  @Override\n  public String toString() {\n    return \"AccessControlLogEntry{\" + \"count=\" + count + \", reason='\" + reason + '\\''\n        + \", context='\" + context + '\\'' + \", object='\" + object + '\\'' + \", username='\" + username\n        + '\\'' + \", ageSeconds='\" + ageSeconds + '\\'' + \", clientInfo=\" + clientInfo\n        + \", entryId=\" + entryId + \", timestampCreated=\" + timestampCreated\n        + \", timestampLastUpdated=\" + timestampLastUpdated + '}';\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/AccessControlUser.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.StringJoiner;\n\n// TODO: remove\npublic class AccessControlUser {\n\n  private final Map<String, Object> userInfo;\n  private final List<String> flags;\n  private final List<String> passwords;\n  private final String commands;\n  private final List<String> keysList;\n  private final String keys;\n  private final List<String> channelsList;\n  private final String channels;\n  private final List<String> selectors;\n\n  public AccessControlUser(Map<String, Object> map) {\n    this.userInfo = map;\n\n    this.flags = (List<String>) map.get(\"flags\");\n\n    this.passwords = (List<String>) map.get(\"passwords\");\n\n    this.commands = (String) map.get(\"commands\");\n\n    Object localKeys = map.get(\"keys\");\n    if (localKeys == null) {\n      this.keys = null;\n      this.keysList = null;\n    } else if (localKeys instanceof List) {\n      this.keysList = (List<String>) localKeys;\n      this.keys = joinStrings(this.keysList);\n    } else {\n      this.keys = (String) localKeys;\n      this.keysList = Arrays.asList(this.keys.split(\" \"));\n    }\n\n    Object localChannels = map.get(\"channels\");\n    if (localChannels == null) {\n      this.channels = null;\n      this.channelsList = null;\n    } else if (localChannels instanceof List) {\n      this.channelsList = (List<String>) localChannels;\n      this.channels = joinStrings(this.channelsList);\n    } else {\n      this.channels = (String) localChannels;\n      this.channelsList = Arrays.asList(this.channels.split(\" \"));\n    }\n\n    this.selectors = (List<String>) map.get(\"selectors\");\n  }\n\n  private static String joinStrings(List<String> list) {\n    StringJoiner joiner = new StringJoiner(\" \");\n    list.forEach(s -> joiner.add(s));\n    return joiner.toString();\n  }\n\n  public List<String> getFlags() {\n    return flags;\n  }\n\n  /**\n   * @deprecated Use {@link AccessControlUser#getPasswords()}.\n   */\n  @Deprecated\n  public List<String> getPassword() {\n    return passwords;\n  }\n\n  public List<String> getPasswords() {\n    return passwords;\n  }\n\n  public String getCommands() {\n    return commands;\n  }\n\n  /**\n   * @return Generic map containing all key-value pairs returned by the server\n   */\n  public Map<String, Object> getUserInfo() {\n    return userInfo;\n  }\n\n  public String getKeys() {\n    return keys;\n  }\n\n  public List<String> getKeysList() {\n    return keysList;\n  }\n\n  public List<String> getChannelsList() {\n    return channelsList;\n  }\n\n  public String getChannels() {\n    return channels;\n  }\n\n  public List<String> getSelectors() {\n    return selectors;\n  }\n\n  @Override\n  public String toString() {\n    return \"AccessControlUser{\" + \"flags=\" + flags + \", passwords=\" + passwords\n        + \", commands='\" + commands + \"', keys='\" + keys + \"', channels='\" + channels\n        + \"', selectors=\" + selectors + \"}\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/ClusterShardInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class holds information about a shard of the cluster with command {@code CLUSTER SHARDS}.\n * They can be accessed via getters. There is also {@link ClusterShardInfo#getClusterShardInfo()}\n * method that returns a generic {@link Map} in case more info are returned from the server.\n */\npublic class ClusterShardInfo {\n\n  public static final String SLOTS = \"slots\";\n  public static final String NODES = \"nodes\";\n\n  private final List<List<Long>> slots;\n  private final List<ClusterShardNodeInfo> nodes;\n\n  private final Map<String, Object> clusterShardInfo;\n\n  /**\n   * @param map contains key-value pairs with cluster shard info\n   */\n  @SuppressWarnings(\"unchecked\")\n  public ClusterShardInfo(Map<String, Object> map) {\n    slots = (List<List<Long>>) map.get(SLOTS);\n    nodes = (List<ClusterShardNodeInfo>) map.get(NODES);\n\n    clusterShardInfo = map;\n  }\n\n  public List<List<Long>> getSlots() {\n    return slots;\n  }\n\n  public List<ClusterShardNodeInfo> getNodes() {\n    return nodes;\n  }\n\n  public Map<String, Object> getClusterShardInfo() {\n    return clusterShardInfo;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/ClusterShardNodeInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.Map;\n\n/**\n * This class holds information about a node of the cluster with command {@code CLUSTER SHARDS}.\n * They can be accessed via getters. There is also {@link ClusterShardNodeInfo#getClusterShardNodeInfo()}\n * method that returns a generic {@link Map} in case more info are returned from the server.\n */\npublic class ClusterShardNodeInfo {\n\n  public static final String ID = \"id\";\n  public static final String ENDPOINT = \"endpoint\";\n  public static final String IP = \"ip\";\n  public static final String HOSTNAME = \"hostname\";\n  public static final String PORT = \"port\";\n  public static final String TLS_PORT = \"tls-port\";\n  public static final String ROLE = \"role\";\n  public static final String REPLICATION_OFFSET = \"replication-offset\";\n  public static final String HEALTH = \"health\";\n\n  private final String id;\n  private final String endpoint;\n  private final String ip;\n  private final String hostname;\n  private final Long port;\n  private final Long tlsPort;\n  private final String role;\n  private final Long replicationOffset;\n  private final String health;\n\n  private final Map<String, Object> clusterShardNodeInfo;\n\n  /**\n   * @param map contains key-value pairs with node info\n   */\n  public ClusterShardNodeInfo(Map<String, Object> map) {\n    id = (String) map.get(ID);\n    endpoint = (String) map.get(ENDPOINT);\n    ip = (String) map.get(IP);\n    hostname = (String) map.get(HOSTNAME);\n    port = (Long) map.get(PORT);\n    tlsPort = (Long) map.get(TLS_PORT);\n    role = (String) map.get(ROLE);\n    replicationOffset = (Long) map.get(REPLICATION_OFFSET);\n    health = (String) map.get(HEALTH);\n\n    clusterShardNodeInfo = map;\n  }\n\n  public String getId() {\n    return id;\n  }\n\n  public String getEndpoint() {\n    return endpoint;\n  }\n\n  public String getIp() {\n    return ip;\n  }\n\n  public String getHostname() {\n    return hostname;\n  }\n\n  public Long getPort() {\n    return port;\n  }\n\n  public Long getTlsPort() {\n    return tlsPort;\n  }\n\n  public String getRole() {\n    return role;\n  }\n\n  public Long getReplicationOffset() {\n    return replicationOffset;\n  }\n\n  public String getHealth() {\n    return health;\n  }\n\n  public Map<String, Object> getClusterShardNodeInfo() {\n    return clusterShardNodeInfo;\n  }\n\n  public boolean isSsl() {\n    return tlsPort != null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/CommandDocument.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.Builder;\n\nimport static redis.clients.jedis.BuilderFactory.STRING;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class CommandDocument {\n\n  private static final String SUMMARY_STR = \"summary\";\n  private static final String SINCE_STR = \"since\";\n  private static final String GROUP_STR = \"group\";\n  private static final String COMPLEXITY_STR = \"complexity\";\n  private static final String HISTORY_STR = \"history\";\n\n  private final String summary;\n  private final String since;\n  private final String group;\n  private final String complexity;\n  private final List<String> history;\n\n  @Deprecated\n  public CommandDocument(String summary, String since, String group, String complexity, List<String> history) {\n    this.summary = summary;\n    this.since = since;\n    this.group = group;\n    this.complexity = complexity;\n    this.history = (List) history;\n  }\n\n  public CommandDocument(Map<String, Object> map) {\n    this.summary = (String) map.get(SUMMARY_STR);\n    this.since = (String) map.get(SINCE_STR);\n    this.group = (String) map.get(GROUP_STR);\n    this.complexity = (String) map.get(COMPLEXITY_STR);\n\n    List<Object> historyObject = (List<Object>) map.get(HISTORY_STR);\n    if (historyObject == null) {\n      this.history = null;\n    } else if (historyObject.isEmpty()) {\n      this.history = Collections.emptyList();\n    } else if (historyObject.get(0) instanceof KeyValue) {\n      this.history = historyObject.stream().map(o -> (KeyValue) o)\n          .map(kv -> (String) kv.getKey() + \": \" + (String) kv.getValue())\n          .collect(Collectors.toList());\n    } else {\n      this.history = historyObject.stream().map(o -> (List) o)\n          .map(l -> (String) l.get(0) + \": \" + (String) l.get(1))\n          .collect(Collectors.toList());\n    }\n  }\n\n  public String getSummary() {\n    return summary;\n  }\n\n  public String getSince() {\n    return since;\n  }\n\n  public String getGroup() {\n    return group;\n  }\n\n  public String getComplexity() {\n    return complexity;\n  }\n\n  public List<String> getHistory() {\n    return history;\n  }\n\n  @Deprecated\n  public static final Builder<CommandDocument> COMMAND_DOCUMENT_BUILDER = new Builder<CommandDocument>() {\n    @Override\n    public CommandDocument build(Object data) {\n      List<Object> commandData = (List<Object>) data;\n      String summary = STRING.build(commandData.get(1));\n      String since = STRING.build(commandData.get(3));\n      String group = STRING.build(commandData.get(5));\n      String complexity = STRING.build(commandData.get(7));\n      List<String> history = null;\n      if (STRING.build(commandData.get(8)).equals(\"history\")) {\n        List<List<Object>> rawHistory = (List<List<Object>>) commandData.get(9);\n        history = new ArrayList<>(rawHistory.size());\n        for (List<Object> timePoint : rawHistory) {\n          history.add(STRING.build(timePoint.get(0)) + \": \" + STRING.build(timePoint.get(1)));\n        }\n      }\n      return new CommandDocument(summary, since, group, complexity, history);\n    }\n  };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/CommandInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.Builder;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static redis.clients.jedis.BuilderFactory.LONG;\nimport static redis.clients.jedis.BuilderFactory.STRING;\nimport static redis.clients.jedis.BuilderFactory.STRING_LIST;\n\npublic class CommandInfo {\n\n  private final String name;\n  private final long arity;\n  private final List<String> flags;\n  private final long firstKey;\n  private final long lastKey;\n  private final long step;\n  private final List<String> aclCategories;\n  private final List<String> tips;\n  private final Map<String, CommandInfo> subcommands;\n\n  /**\n   * THIS IGNORES 'subcommands' parameter.\n   * @param subcommands WILL BE IGNORED\n   * @deprecated\n   */\n  @Deprecated\n  public CommandInfo(long arity, List<String> flags, long firstKey, long lastKey, long step,\n      List<String> aclCategories, List<String> tips, List<String> subcommands) {\n    this((String) null, arity, flags, firstKey, lastKey, step, aclCategories, tips, (Map) null);\n  }\n\n  private CommandInfo(String name, long arity, List<String> flags, long firstKey, long lastKey, long step,\n      List<String> aclCategories, List<String> tips, Map<String, CommandInfo> subcommands) {\n    this.name = name;\n    this.arity = arity;\n    this.flags = flags;\n    this.firstKey = firstKey;\n    this.lastKey = lastKey;\n    this.step = step;\n    this.aclCategories = aclCategories;\n    this.tips = tips;\n    this.subcommands = subcommands;\n  }\n\n  /**\n   * Command name\n   */\n  public String getName() {\n    return name;\n  }\n\n  /**\n   * Arity is the number of arguments a command expects. It follows a simple pattern:\n   * A positive integer means a fixed number of arguments.\n   * A negative integer means a minimal number of arguments.\n   *\n   * Examples:\n   *\n   * GET's arity is 2 since the command only accepts one argument and always has the format GET _key_.\n   * MGET's arity is -2 since the command accepts at least one argument, but possibly multiple ones: MGET _key1_ [key2] [key3] ....\n   */\n  public long getArity() {\n    return arity;\n  }\n\n  /**\n   * Command flags\n   */\n  public List<String> getFlags() {\n    return flags;\n  }\n\n  /**\n   * The position of the command's first key name argument\n   */\n  public long getFirstKey() {\n    return firstKey;\n  }\n\n  /**\n   * The position of the command's last key name argument\n   * Commands that accept a single key have both first key and last key set to 1\n   */\n  public long getLastKey() {\n    return lastKey;\n  }\n\n  /**\n   * This value is the step, or increment, between the first key and last key values where the keys are\n   */\n  public long getStep() {\n    return step;\n  }\n\n  /**\n   * An array of simple strings that are the ACL categories to which the command belongs\n   */\n  public List<String> getAclCategories() {\n    return aclCategories;\n  }\n\n  /**\n   * Helpful information about the command\n   */\n  public List<String> getTips() {\n    return tips;\n  }\n\n  /**\n   * All the command's subcommands, if any\n   */\n  public Map<String, CommandInfo> getSubcommands() {\n    return subcommands;\n  }\n\n  public static final Builder<CommandInfo> COMMAND_INFO_BUILDER = new Builder<CommandInfo>() {\n    @Override\n    public CommandInfo build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<Object> commandData = (List<Object>) data;\n      if (commandData.isEmpty()) {\n        return null;\n      }\n\n      String name = STRING.build(commandData.get(0));\n      long arity = LONG.build(commandData.get(1));\n      List<String> flags = STRING_LIST.build(commandData.get(2));\n      long firstKey = LONG.build(commandData.get(3));\n      long lastKey = LONG.build(commandData.get(4));\n      long step = LONG.build(commandData.get(5));\n      // Redis 6.0\n      List<String> aclCategories = commandData.size() >= 7 ? STRING_LIST.build(commandData.get(6)) : null;\n      // Redis 7.0\n      List<String> tips = commandData.size() >= 8 ? STRING_LIST.build(commandData.get(7)) : null;\n      Map<String, CommandInfo> subcommands = commandData.size() >= 10\n          ? COMMAND_INFO_RESPONSE.build(commandData.get(9)) : null;\n\n      return new CommandInfo(name, arity, flags, firstKey, lastKey, step, aclCategories, tips, subcommands);\n    }\n  };\n\n  public static final Builder<Map<String, CommandInfo>> COMMAND_INFO_RESPONSE = new Builder<Map<String, CommandInfo>>() {\n    @Override\n    public Map<String, CommandInfo> build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<Object> rawList = (List<Object>) data;\n      Map<String, CommandInfo> map = new HashMap<>(rawList.size());\n\n      for (Object rawCommandInfo : rawList) {\n        CommandInfo info = CommandInfo.COMMAND_INFO_BUILDER.build(rawCommandInfo);\n        if (info != null) {\n          map.put(info.getName(), info);\n        }\n      }\n\n      return map;\n    }\n  };\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/FunctionStats.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class FunctionStats {\n\n  private final Map<String, Object> runningScript;\n  private final Map<String, Map<String, Object>> engines;\n\n  public FunctionStats(Map<String, Object> script, Map<String, Map<String, Object>> engines) {\n    this.runningScript = script;\n    this.engines = engines;\n  }\n\n  public Map<String, Object> getRunningScript() {\n    return runningScript;\n  }\n\n  public Map<String, Map<String, Object>> getEngines() {\n    return engines;\n  }\n\n  public static final Builder<FunctionStats> FUNCTION_STATS_BUILDER = new Builder<FunctionStats>() {\n\n    @Override\n    public FunctionStats build(Object data) {\n      if (data == null) return null;\n      List list = (List) data;\n      if (list.isEmpty()) return null;\n\n      if (list.get(0) instanceof KeyValue) {\n\n        Map<String, Object> runningScriptMap = null;\n        Map<String, Map<String, Object>> enginesMap = null;\n\n        for (KeyValue kv : (List<KeyValue>) list) {\n          switch (BuilderFactory.STRING.build(kv.getKey())) {\n            case \"running_script\":\n              runningScriptMap = BuilderFactory.ENCODED_OBJECT_MAP.build(kv.getValue());\n              break;\n            case \"engines\":\n              List<KeyValue> ilist = (List<KeyValue>) kv.getValue();\n              enginesMap = new LinkedHashMap<>(ilist.size());\n              for (KeyValue ikv : (List<KeyValue>) kv.getValue()) {\n                enginesMap.put(BuilderFactory.STRING.build(ikv.getKey()),\n                    BuilderFactory.ENCODED_OBJECT_MAP.build(ikv.getValue()));\n              }\n              break;\n          }\n        }\n\n        return new FunctionStats(runningScriptMap, enginesMap);\n      }\n\n      Map<String, Object> runningScriptMap = list.get(1) == null ? null\n          : BuilderFactory.ENCODED_OBJECT_MAP.build(list.get(1));\n\n      List<Object> enginesList = (List<Object>) list.get(3);\n\n      Map<String, Map<String, Object>> enginesMap = new LinkedHashMap<>(enginesList.size() / 2);\n      for (int i = 0; i < enginesList.size(); i += 2) {\n        enginesMap.put(BuilderFactory.STRING.build(enginesList.get(i)),\n            BuilderFactory.ENCODED_OBJECT_MAP.build(enginesList.get(i + 1)));\n      }\n\n      return new FunctionStats(runningScriptMap, enginesMap);\n    }\n  };\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/GeoRadiusResponse.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\npublic class GeoRadiusResponse {\n\n  private byte[] member;\n  private double distance;\n  private GeoCoordinate coordinate;\n  private long rawScore;\n\n  public GeoRadiusResponse(byte[] member) {\n    this.member = member;\n  }\n\n  public void setDistance(double distance) {\n    this.distance = distance;\n  }\n\n  public void setCoordinate(GeoCoordinate coordinate) {\n    this.coordinate = coordinate;\n  }\n\n  public void setRawScore(long rawScore) {\n    this.rawScore = rawScore;\n  }\n\n  public byte[] getMember() {\n    return member;\n  }\n\n  public String getMemberByString() {\n    return SafeEncoder.encode(member);\n  }\n\n  /**\n   * @return The distance of the returned item from the specified center. The distance is returned\n   * in the same unit as the unit specified as the radius argument of the command.\n   */\n  public double getDistance() {\n    return distance;\n  }\n\n  /**\n   * @return The longitude,latitude coordinates of the matching item.\n   */\n  public GeoCoordinate getCoordinate() {\n    return coordinate;\n  }\n\n  /**\n   * @return The raw geohash-encoded sorted set score of the item, in the form of a 52 bit unsigned\n   * integer. This is only useful for low level hacks or debugging and is otherwise of little\n   * interest for the general user.\n   */\n  public long getRawScore() {\n    return rawScore;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == this) {\n      return true;\n    }\n\n    if (!(obj instanceof GeoRadiusResponse)) {\n      return false;\n    }\n\n    GeoRadiusResponse response = (GeoRadiusResponse) obj;\n    return Double.compare(distance, response.getDistance()) == 0\n            && rawScore == response.getRawScore() && coordinate.equals(response.coordinate)\n            && Arrays.equals(member, response.getMember());\n  }\n\n  @Override\n  public int hashCode() {\n    int hash = 7;\n    hash = 67 * hash + Arrays.hashCode(this.member);\n    hash = 67 * hash + (int) (Double.doubleToLongBits(this.distance) ^ (Double.doubleToLongBits(this.distance) >>> 32));\n    hash = 67 * hash + Objects.hashCode(this.coordinate);\n    hash = 67 * hash + (int) (this.rawScore ^ (this.rawScore >>> 32));\n    return hash;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/HotkeysInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.util.KeyValue;\n\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static redis.clients.jedis.BuilderFactory.*;\n\n/**\n * Response object for the HOTKEYS GET command. Contains statistics about hot keys tracked by CPU\n * time and network bytes.\n */\npublic class HotkeysInfo implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  // Field names from the response\n  public static final String TRACKING_ACTIVE = \"tracking-active\";\n  public static final String SAMPLE_RATIO = \"sample-ratio\";\n  public static final String SELECTED_SLOTS = \"selected-slots\";\n  public static final String SAMPLED_COMMANDS_SELECTED_SLOTS_US = \"sampled-commands-selected-slots-us\";\n  public static final String ALL_COMMANDS_SELECTED_SLOTS_US = \"all-commands-selected-slots-us\";\n  public static final String ALL_COMMANDS_ALL_SLOTS_US = \"all-commands-all-slots-us\";\n  public static final String NET_BYTES_SAMPLED_COMMANDS_SELECTED_SLOTS = \"net-bytes-sampled-commands-selected-slots\";\n  public static final String NET_BYTES_ALL_COMMANDS_SELECTED_SLOTS = \"net-bytes-all-commands-selected-slots\";\n  public static final String NET_BYTES_ALL_COMMANDS_ALL_SLOTS = \"net-bytes-all-commands-all-slots\";\n  public static final String COLLECTION_START_TIME_UNIX_MS = \"collection-start-time-unix-ms\";\n  public static final String COLLECTION_DURATION_MS = \"collection-duration-ms\";\n  public static final String TOTAL_CPU_TIME_USER_MS = \"total-cpu-time-user-ms\";\n  public static final String TOTAL_CPU_TIME_SYS_MS = \"total-cpu-time-sys-ms\";\n  public static final String TOTAL_NET_BYTES = \"total-net-bytes\";\n  public static final String BY_CPU_TIME_US = \"by-cpu-time-us\";\n  public static final String BY_NET_BYTES = \"by-net-bytes\";\n\n  private final boolean trackingActive;\n  private final long sampleRatio;\n  private final List<int[]> selectedSlots; // List of [start, end] slot ranges\n  private final Long sampledCommandSelectedSlotsUs;\n  private final Long allCommandsSelectedSlotsUs;\n  private final long allCommandsAllSlotsUs;\n  private final Long netBytesSampledCommandsSelectedSlots;\n  private final Long netBytesAllCommandsSelectedSlots;\n  private final long netBytesAllCommandsAllSlots;\n  private final long collectionStartTimeUnixMs;\n  private final long collectionDurationMs;\n  private final long totalCpuTimeUserMs;\n  private final long totalCpuTimeSysMs;\n  private final long totalNetBytes;\n  private final Map<String, Long> byCpuTimeUs;\n  private final Map<String, Long> byNetBytes;\n\n  public HotkeysInfo(boolean trackingActive, long sampleRatio, List<int[]> selectedSlots,\n      Long sampledCommandSelectedSlotsUs, Long allCommandsSelectedSlotsUs,\n      long allCommandsAllSlotsUs, Long netBytesSampledCommandsSelectedSlots,\n      Long netBytesAllCommandsSelectedSlots, long netBytesAllCommandsAllSlots,\n      long collectionStartTimeUnixMs, long collectionDurationMs, long totalCpuTimeUserMs,\n      long totalCpuTimeSysMs, long totalNetBytes, Map<String, Long> byCpuTimeUs,\n      Map<String, Long> byNetBytes) {\n    this.trackingActive = trackingActive;\n    this.sampleRatio = sampleRatio;\n    this.selectedSlots = selectedSlots;\n    this.sampledCommandSelectedSlotsUs = sampledCommandSelectedSlotsUs;\n    this.allCommandsSelectedSlotsUs = allCommandsSelectedSlotsUs;\n    this.allCommandsAllSlotsUs = allCommandsAllSlotsUs;\n    this.netBytesSampledCommandsSelectedSlots = netBytesSampledCommandsSelectedSlots;\n    this.netBytesAllCommandsSelectedSlots = netBytesAllCommandsSelectedSlots;\n    this.netBytesAllCommandsAllSlots = netBytesAllCommandsAllSlots;\n    this.collectionStartTimeUnixMs = collectionStartTimeUnixMs;\n    this.collectionDurationMs = collectionDurationMs;\n    this.totalCpuTimeUserMs = totalCpuTimeUserMs;\n    this.totalCpuTimeSysMs = totalCpuTimeSysMs;\n    this.totalNetBytes = totalNetBytes;\n    this.byCpuTimeUs = byCpuTimeUs;\n    this.byNetBytes = byNetBytes;\n  }\n\n  public boolean isTrackingActive() {\n    return trackingActive;\n  }\n\n  public long getSampleRatio() {\n    return sampleRatio;\n  }\n\n  /**\n   * Returns the selected slots. Each element is an int array that can be:\n   * <ul>\n   * <li>A single slot: {@code [slot]} (array with 1 element)</li>\n   * <li>A slot range: {@code [start, end]} (array with 2 elements, inclusive)</li>\n   * </ul>\n   * @return list of slot entries, empty if all slots are selected\n   */\n  public List<int[]> getSelectedSlots() {\n    return selectedSlots;\n  }\n\n  public Long getSampledCommandSelectedSlotsUs() {\n    return sampledCommandSelectedSlotsUs;\n  }\n\n  public Long getAllCommandsSelectedSlotsUs() {\n    return allCommandsSelectedSlotsUs;\n  }\n\n  public long getAllCommandsAllSlotsUs() {\n    return allCommandsAllSlotsUs;\n  }\n\n  public Long getNetBytesSampledCommandsSelectedSlots() {\n    return netBytesSampledCommandsSelectedSlots;\n  }\n\n  public Long getNetBytesAllCommandsSelectedSlots() {\n    return netBytesAllCommandsSelectedSlots;\n  }\n\n  public long getNetBytesAllCommandsAllSlots() {\n    return netBytesAllCommandsAllSlots;\n  }\n\n  public long getCollectionStartTimeUnixMs() {\n    return collectionStartTimeUnixMs;\n  }\n\n  public long getCollectionDurationMs() {\n    return collectionDurationMs;\n  }\n\n  public long getTotalCpuTimeUserMs() {\n    return totalCpuTimeUserMs;\n  }\n\n  public long getTotalCpuTimeSysMs() {\n    return totalCpuTimeSysMs;\n  }\n\n  public long getTotalNetBytes() {\n    return totalNetBytes;\n  }\n\n  public Map<String, Long> getByCpuTimeUs() {\n    return byCpuTimeUs;\n  }\n\n  public Map<String, Long> getByNetBytes() {\n    return byNetBytes;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static Map<String, Long> parseKeyValueMap(Object data) {\n    if (data == null) {\n      return Collections.emptyMap();\n    }\n    List<?> list = (List<?>) data;\n    if (list.isEmpty()) {\n      return Collections.emptyMap();\n    }\n    Map<String, Long> result = new LinkedHashMap<>();\n    if (list.get(0) instanceof KeyValue) {\n      for (KeyValue<?, ?> kv : (List<KeyValue<?, ?>>) list) {\n        result.put(STRING.build(kv.getKey()), LONG.build(kv.getValue()));\n      }\n    } else {\n      // RESP2 format: alternating key-value pairs\n      for (int i = 0; i < list.size(); i += 2) {\n        result.put(STRING.build(list.get(i)), LONG.build(list.get(i + 1)));\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Parse selected-slots which is an array of slot entries. Each entry can be:\n   * <ul>\n   * <li>A single slot: [slot] (array with 1 element)</li>\n   * <li>A slot range: [start, end] (array with 2 elements)</li>\n   * </ul>\n   * Example: [[0, 2], [100]] means slots 0-2 (range) and slot 100 (single).\n   */\n  @SuppressWarnings(\"unchecked\")\n  private static List<int[]> parseSlotRanges(Object data) {\n    if (data == null) {\n      return Collections.emptyList();\n    }\n    List<?> list = (List<?>) data;\n    if (list.isEmpty()) {\n      return Collections.emptyList();\n    }\n    List<int[]> result = new java.util.ArrayList<>(list.size());\n    for (Object item : list) {\n      if (item instanceof List) {\n        List<?> range = (List<?>) item;\n        if (range.size() == 1) {\n          // Single slot\n          int slot = LONG.build(range.get(0)).intValue();\n          result.add(new int[] { slot });\n        } else if (range.size() == 2) {\n          // Slot range\n          int start = LONG.build(range.get(0)).intValue();\n          int end = LONG.build(range.get(1)).intValue();\n          result.add(new int[] { start, end });\n        }\n      }\n    }\n    return result;\n  }\n\n  public static final Builder<HotkeysInfo> HOTKEYS_INFO_BUILDER = new Builder<HotkeysInfo>() {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public HotkeysInfo build(Object data) {\n      if (data == null) {\n        return null;\n      }\n\n      List<?> list = (List<?>) data;\n      if (list.isEmpty()) {\n        return null;\n      }\n\n      // Check if the response is wrapped in an outer array (single element that is a List)\n      // This happens when Redis returns [[key1, val1, key2, val2, ...]]\n      if (list.size() == 1 && list.get(0) instanceof List) {\n        list = (List<?>) list.get(0);\n        if (list.isEmpty()) {\n          return null;\n        }\n      }\n\n      boolean trackingActive = false;\n      long sampleRatio = 1;\n      List<int[]> selectedSlots = Collections.emptyList();\n      Long sampledCommandSelectedSlotsUs = null;\n      Long allCommandsSelectedSlotsUs = null;\n      long allCommandsAllSlotsUs = 0;\n      Long netBytesSampledCommandsSelectedSlots = null;\n      Long netBytesAllCommandsSelectedSlots = null;\n      long netBytesAllCommandsAllSlots = 0;\n      long collectionStartTimeUnixMs = 0;\n      long collectionDurationMs = 0;\n      long totalCpuTimeUserMs = 0;\n      long totalCpuTimeSysMs = 0;\n      long totalNetBytes = 0;\n      Map<String, Long> byCpuTimeUs = Collections.emptyMap();\n      Map<String, Long> byNetBytes = Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        // RESP3 format\n        for (KeyValue<?, ?> kv : (List<KeyValue<?, ?>>) list) {\n          String key = STRING.build(kv.getKey());\n          Object value = kv.getValue();\n          switch (key) {\n            case TRACKING_ACTIVE:\n              trackingActive = LONG.build(value) == 1;\n              break;\n            case SAMPLE_RATIO:\n              sampleRatio = LONG.build(value);\n              break;\n            case SELECTED_SLOTS:\n              selectedSlots = parseSlotRanges(value);\n              break;\n            case SAMPLED_COMMANDS_SELECTED_SLOTS_US:\n              sampledCommandSelectedSlotsUs = LONG.build(value);\n              break;\n            case ALL_COMMANDS_SELECTED_SLOTS_US:\n              allCommandsSelectedSlotsUs = LONG.build(value);\n              break;\n            case ALL_COMMANDS_ALL_SLOTS_US:\n              allCommandsAllSlotsUs = LONG.build(value);\n              break;\n            case NET_BYTES_SAMPLED_COMMANDS_SELECTED_SLOTS:\n              netBytesSampledCommandsSelectedSlots = LONG.build(value);\n              break;\n            case NET_BYTES_ALL_COMMANDS_SELECTED_SLOTS:\n              netBytesAllCommandsSelectedSlots = LONG.build(value);\n              break;\n            case NET_BYTES_ALL_COMMANDS_ALL_SLOTS:\n              netBytesAllCommandsAllSlots = LONG.build(value);\n              break;\n            case COLLECTION_START_TIME_UNIX_MS:\n              collectionStartTimeUnixMs = LONG.build(value);\n              break;\n            case COLLECTION_DURATION_MS:\n              collectionDurationMs = LONG.build(value);\n              break;\n            case TOTAL_CPU_TIME_USER_MS:\n              totalCpuTimeUserMs = LONG.build(value);\n              break;\n            case TOTAL_CPU_TIME_SYS_MS:\n              totalCpuTimeSysMs = LONG.build(value);\n              break;\n            case TOTAL_NET_BYTES:\n              totalNetBytes = LONG.build(value);\n              break;\n            case BY_CPU_TIME_US:\n              byCpuTimeUs = parseKeyValueMap(value);\n              break;\n            case BY_NET_BYTES:\n              byNetBytes = parseKeyValueMap(value);\n              break;\n          }\n        }\n      } else {\n        // RESP2 format: alternating key-value pairs\n        for (int i = 0; i < list.size(); i += 2) {\n          String key = STRING.build(list.get(i));\n          Object value = list.get(i + 1);\n          switch (key) {\n            case TRACKING_ACTIVE:\n              trackingActive = LONG.build(value) == 1;\n              break;\n            case SAMPLE_RATIO:\n              sampleRatio = LONG.build(value);\n              break;\n            case SELECTED_SLOTS:\n              selectedSlots = parseSlotRanges(value);\n              break;\n            case SAMPLED_COMMANDS_SELECTED_SLOTS_US:\n              sampledCommandSelectedSlotsUs = LONG.build(value);\n              break;\n            case ALL_COMMANDS_SELECTED_SLOTS_US:\n              allCommandsSelectedSlotsUs = LONG.build(value);\n              break;\n            case ALL_COMMANDS_ALL_SLOTS_US:\n              allCommandsAllSlotsUs = LONG.build(value);\n              break;\n            case NET_BYTES_SAMPLED_COMMANDS_SELECTED_SLOTS:\n              netBytesSampledCommandsSelectedSlots = LONG.build(value);\n              break;\n            case NET_BYTES_ALL_COMMANDS_SELECTED_SLOTS:\n              netBytesAllCommandsSelectedSlots = LONG.build(value);\n              break;\n            case NET_BYTES_ALL_COMMANDS_ALL_SLOTS:\n              netBytesAllCommandsAllSlots = LONG.build(value);\n              break;\n            case COLLECTION_START_TIME_UNIX_MS:\n              collectionStartTimeUnixMs = LONG.build(value);\n              break;\n            case COLLECTION_DURATION_MS:\n              collectionDurationMs = LONG.build(value);\n              break;\n            case TOTAL_CPU_TIME_USER_MS:\n              totalCpuTimeUserMs = LONG.build(value);\n              break;\n            case TOTAL_CPU_TIME_SYS_MS:\n              totalCpuTimeSysMs = LONG.build(value);\n              break;\n            case TOTAL_NET_BYTES:\n              totalNetBytes = LONG.build(value);\n              break;\n            case BY_CPU_TIME_US:\n              byCpuTimeUs = parseKeyValueMap(value);\n              break;\n            case BY_NET_BYTES:\n              byNetBytes = parseKeyValueMap(value);\n              break;\n          }\n        }\n      }\n\n      return new HotkeysInfo(trackingActive, sampleRatio, selectedSlots,\n          sampledCommandSelectedSlotsUs, allCommandsSelectedSlotsUs, allCommandsAllSlotsUs,\n          netBytesSampledCommandsSelectedSlots, netBytesAllCommandsSelectedSlots,\n          netBytesAllCommandsAllSlots, collectionStartTimeUnixMs, collectionDurationMs,\n          totalCpuTimeUserMs, totalCpuTimeSysMs, totalNetBytes, byCpuTimeUs, byNetBytes);\n    }\n  };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/LCSMatchResult.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Result for STRALGO LCS command.\n */\npublic class LCSMatchResult {\n    private String matchString;\n\n    private List<MatchedPosition> matches;\n\n    private long len;\n\n    public LCSMatchResult(String matchString) {\n        this.matchString = matchString;\n    }\n\n    public LCSMatchResult(long len) {\n        this.len = len;\n    }\n\n    public LCSMatchResult(List<MatchedPosition> matches, long len) {\n        this.matches = matches;\n        this.len = len;\n    }\n\n    /**\n     * Creates new {@link LCSMatchResult}.\n     *\n     * @param matchString\n     * @param matches\n     * @param len\n     */\n    public LCSMatchResult(String matchString, List<MatchedPosition> matches, long len) {\n        this.matchString = matchString;\n        this.matches = Collections.unmodifiableList(matches);\n        this.len = len;\n    }\n\n    public String getMatchString() {\n        return matchString;\n    }\n\n    public List<MatchedPosition> getMatches() {\n        return matches;\n    }\n\n    public long getLen() {\n        return len;\n    }\n\n    /**\n     * Match position in each string.\n     */\n    public static class MatchedPosition {\n\n        private final Position a;\n\n        private final Position b;\n\n        private final long matchLen;\n\n        public MatchedPosition(Position a, Position b, long matchLen) {\n            this.a = a;\n            this.b = b;\n            this.matchLen = matchLen;\n        }\n\n        public Position getA() {\n            return a;\n        }\n\n        public Position getB() {\n            return b;\n        }\n\n        public long getMatchLen() {\n            return matchLen;\n        }\n    }\n\n    /**\n     * Position range.\n     */\n    public static class Position {\n\n        private final long start;\n\n        private final long end;\n\n        public Position(long start, long end) {\n            this.start = start;\n            this.end = end;\n        }\n\n        public long getStart() {\n            return start;\n        }\n\n        public long getEnd() {\n            return end;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/LatencyHistoryInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.Builder;\n\nimport java.util.List;\n\nimport static redis.clients.jedis.BuilderFactory.LONG;\n\npublic class LatencyHistoryInfo {\n\n    private final long timestamp;\n    private final long latency;\n\n    public LatencyHistoryInfo(long timestamp, long latency) {\n        this.timestamp = timestamp;\n        this.latency = latency;\n    }\n\n    public long getTimestamp() {\n        return timestamp;\n    }\n\n    public long getLatency() {\n        return latency;\n    }\n\n    public static final Builder<LatencyHistoryInfo> LATENCY_HISTORY_BUILDER = new Builder<LatencyHistoryInfo>() {\n        @Override\n        public LatencyHistoryInfo build(Object data) {\n            List<Object> commandData = (List<Object>) data;\n\n            long timestamp = LONG.build(commandData.get(0));\n            long latency = LONG.build(commandData.get(1));\n\n            return new LatencyHistoryInfo(timestamp, latency);\n        }\n    };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/LatencyLatestInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.Builder;\n\nimport java.util.List;\n\nimport static redis.clients.jedis.BuilderFactory.LONG;\nimport static redis.clients.jedis.BuilderFactory.STRING;\n\npublic class LatencyLatestInfo {\n\n    private final String command;\n    private final long timestamp;\n    private final long lastEventLatency;\n    private final long maxEventLatency;\n\n    public LatencyLatestInfo(String command, long timestamp, long lastEventLatency, long maxEventLatency) {\n        this.command = command;\n        this.timestamp = timestamp;\n        this.lastEventLatency = lastEventLatency;\n        this.maxEventLatency = maxEventLatency;\n    }\n\n    public String getCommand() {\n        return command;\n    }\n\n    public long getTimestamp() {\n        return timestamp;\n    }\n\n    public long getLastEventLatency() {\n        return lastEventLatency;\n    }\n\n    public long getMaxEventLatency() {\n        return maxEventLatency;\n    }\n\n    public static final Builder<LatencyLatestInfo> LATENCY_LATEST_BUILDER = new Builder<LatencyLatestInfo>() {\n        @Override\n        public LatencyLatestInfo build(Object data) {\n            List<Object> commandData = (List<Object>) data;\n\n            String command = STRING.build(commandData.get(0));\n            long timestamp = LONG.build(commandData.get(1));\n            long lastEventLatency = LONG.build(commandData.get(2));\n            long maxEventLatency = LONG.build(commandData.get(3));\n\n            return new LatencyLatestInfo(command, timestamp, lastEventLatency, maxEventLatency);\n        }\n    };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/LibraryInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport static redis.clients.jedis.BuilderFactory.STRING;\nimport static redis.clients.jedis.BuilderFactory.ENCODED_OBJECT_MAP;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.util.KeyValue;\n\n//[library_name=mylib, engine=LUA, functions=[[name=myfunc, description=null, flags=[]]], library_code=#!LUA name=mylib \n// redis.register_function('myfunc', function(keys, args) return args[1] end)]\npublic class LibraryInfo {\n\n  private final String libraryName;\n  private final String engine;\n  private final List<Map<String, Object>> functions;\n  private final String libraryCode;\n\n  /**\n   * @deprecated Since 7.4. This constructor will be removed in the next major release.\n   *             Use {@link #LibraryInfo(String, String, List, String)} instead.\n   */\n  @Deprecated\n  public LibraryInfo(String libraryName, String engineName, List<Map<String, Object>> functions) {\n    this(libraryName, engineName, functions, null);\n  }\n\n  public LibraryInfo(String libraryName, String engineName, List<Map<String, Object>> functions, String code) {\n    this.libraryName = libraryName;\n    this.engine = engineName;\n    this.functions = functions;\n    this.libraryCode = code;\n  }\n\n  public String getLibraryName() {\n    return libraryName;\n  }\n\n  public String getEngine() {\n    return engine;\n  }\n\n  public List<Map<String, Object>> getFunctions() {\n    return functions;\n  }\n\n  public String getLibraryCode() {\n    return libraryCode;\n  }\n\n  private static class LibraryInfoHolder {\n    String libraryName;\n    String engineName;\n    String libraryCode;\n    List<Map<String, Object>> functions;\n  }\n\n  public static final Builder<LibraryInfo> LIBRARY_INFO = new Builder<LibraryInfo>() {\n    @Override\n    public LibraryInfo build(Object data) {\n      if (data == null) return null;\n      List list = (List) data;\n      if (list.isEmpty()) return null;\n\n      LibraryInfoHolder holder = new LibraryInfoHolder();\n\n      if (list.get(0) instanceof KeyValue) {\n        // RESP3 format: list of KeyValue objects\n        for (KeyValue kv : (List<KeyValue>) list) {\n          processField(kv.getKey(), kv.getValue(), holder);\n        }\n      } else {\n        // RESP2 format: flat list with alternating key-value pairs\n        // Note: Redis Enterprise may include extra fields like \"consistent\"\n        for (int i = 0; i + 1 < list.size(); i += 2) {\n          processField(list.get(i), list.get(i + 1), holder);\n        }\n      }\n\n      return new LibraryInfo(holder.libraryName, holder.engineName, holder.functions, holder.libraryCode);\n    }\n\n    private void processField(Object key, Object value, LibraryInfoHolder holder) {\n      switch (BuilderFactory.STRING.build(key)) {\n        case \"library_name\":\n          holder.libraryName = BuilderFactory.STRING.build(value);\n          break;\n        case \"engine\":\n          holder.engineName = BuilderFactory.STRING.build(value);\n          break;\n        case \"functions\":\n          holder.functions = ((List<Object>) value).stream()\n              .map(ENCODED_OBJECT_MAP::build)\n              .collect(Collectors.toList());\n          break;\n        case \"library_code\":\n          holder.libraryCode = BuilderFactory.STRING.build(value);\n          break;\n      }\n    }\n  };\n\n  /**\n   * @deprecated Use {@link LibraryInfo#LIBRARY_INFO}.\n   */\n  @Deprecated\n  public static final Builder<LibraryInfo> LIBRARY_BUILDER = LIBRARY_INFO;\n\n  public static final Builder<List<LibraryInfo>> LIBRARY_INFO_LIST = new Builder<List<LibraryInfo>>() {\n    @Override\n    public List<LibraryInfo> build(Object data) {\n      List<Object> list = (List<Object>) data;\n      return list.stream().map(o -> LibraryInfo.LIBRARY_INFO.build(o)).collect(Collectors.toList());\n    }\n  };\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/RawVector.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * Result of a VEMB RAW command, containing raw vector data and metadata. For regular VEMB commands\n * (without RAW), use List&lt;Double&gt; directly.\n */\n@Experimental\npublic class RawVector {\n\n  private final String quantizationType;\n  private final byte[] rawData;\n  private final Double norm;\n  private final Double quantizationRange;\n\n  /**\n   * Constructor for RAW VEMB results.\n   * @param quantizationType the quantization type (fp32, bin, or q8)\n   * @param rawData the raw vector data blob\n   * @param norm the L2 norm of the vector before normalization\n   * @param quantizationRange the quantization range (only for q8, null otherwise)\n   */\n  public RawVector(String quantizationType, byte[] rawData, Double norm, Double quantizationRange) {\n    this.quantizationType = quantizationType;\n    this.rawData = rawData;\n    this.norm = norm;\n    this.quantizationRange = quantizationRange;\n  }\n\n  /**\n   * Get the quantization type.\n   * @return quantization type string (fp32, bin, or q8)\n   */\n  public String getQuantizationType() {\n    return quantizationType;\n  }\n\n  /**\n   * Get the raw vector data.\n   * @return raw data blob\n   */\n  public byte[] getRawData() {\n    return rawData;\n  }\n\n  /**\n   * Get the L2 norm.\n   * @return L2 norm value\n   */\n  public Double getNorm() {\n    return norm;\n  }\n\n  /**\n   * Get the quantization range (for q8 quantization).\n   * @return quantization range, or null if not q8 quantization\n   */\n  public Double getQuantizationRange() {\n    return quantizationRange;\n  }\n\n  @Override\n  public String toString() {\n    return \"RawVector{quantizationType='\" + quantizationType + \"', norm=\" + norm\n        + \", quantizationRange=\" + quantizationRange + \", rawDataLength=\"\n        + (rawData != null ? rawData.length : 0) + \"}\";\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n\n    RawVector that = (RawVector) o;\n\n    if (quantizationType != null ? !quantizationType.equals(that.quantizationType)\n        : that.quantizationType != null)\n      return false;\n    if (rawData != null ? !java.util.Arrays.equals(rawData, that.rawData) : that.rawData != null)\n      return false;\n    if (norm != null ? !norm.equals(that.norm) : that.norm != null) return false;\n    return quantizationRange != null ? quantizationRange.equals(that.quantizationRange)\n        : that.quantizationRange == null;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = quantizationType != null ? quantizationType.hashCode() : 0;\n    result = 31 * result + (rawData != null ? java.util.Arrays.hashCode(rawData) : 0);\n    result = 31 * result + (norm != null ? norm.hashCode() : 0);\n    result = 31 * result + (quantizationRange != null ? quantizationRange.hashCode() : 0);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/ScanResult.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.List;\nimport redis.clients.jedis.params.ScanParams;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class ScanResult<T> {\n  private byte[] cursor;\n  private List<T> results;\n\n  public ScanResult(String cursor, List<T> results) {\n    this(SafeEncoder.encode(cursor), results);\n  }\n\n  public ScanResult(byte[] cursor, List<T> results) {\n    this.cursor = cursor;\n    this.results = results;\n  }\n\n  /**\n   * Returns the new value of the cursor\n   * @return the new cursor value. {@link ScanParams#SCAN_POINTER_START} when a complete iteration has finished\n   */\n  public String getCursor() {\n    return SafeEncoder.encode(cursor);\n  }\n\n  /**\n   * Is the iteration complete. I.e. was the complete dataset scanned.\n   * @return {@code true} if the iteration is complete\n   */\n  public boolean isCompleteIteration() {\n    return ScanParams.SCAN_POINTER_START.equals(getCursor());\n  }\n\n  public byte[] getCursorAsBytes() {\n    return cursor;\n  }\n\n  /**\n   * The scan results from the current call.\n   * @return the scan results\n   */\n  public List<T> getResult() {\n    return results;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/Slowlog.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class Slowlog {\n\n  private final long id;\n  private final long timeStamp;\n  private final long executionTime;\n  private final List<String> args;\n  private HostAndPort clientIpPort;\n  private String clientName;\n\n  private static final String COMMA = \",\";\n\n  @SuppressWarnings(\"unchecked\")\n  private Slowlog(List<Object> properties) {\n    this.id = (Long) properties.get(0);\n    this.timeStamp = (Long) properties.get(1);\n    this.executionTime = (Long) properties.get(2);\n\n    this.args = BuilderFactory.STRING_LIST.build(properties.get(3));\n    if (properties.size() == 4) return;\n\n    this.clientIpPort = HostAndPort.from(SafeEncoder.encode((byte[]) properties.get(4)));\n    this.clientName = SafeEncoder.encode((byte[]) properties.get(5));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public static List<Slowlog> from(List<Object> nestedMultiBulkReply) {\n    List<Slowlog> logs = new ArrayList<>(nestedMultiBulkReply.size());\n    for (Object obj : nestedMultiBulkReply) {\n      List<Object> properties = (List<Object>) obj;\n      logs.add(new Slowlog(properties));\n    }\n    return logs;\n  }\n\n  public long getId() {\n    return id;\n  }\n\n  public long getTimeStamp() {\n    return timeStamp;\n  }\n\n  public long getExecutionTime() {\n    return executionTime;\n  }\n\n  public List<String> getArgs() {\n    return args;\n  }\n\n  public HostAndPort getClientIpPort() {\n    return clientIpPort;\n  }\n\n  public String getClientName() {\n    return clientName;\n  }\n\n  @Override\n  public String toString() {\n    return new StringBuilder().append(id).append(COMMA).append(timeStamp).append(COMMA)\n        .append(executionTime).append(COMMA).append(args).toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamConsumerFullInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.StreamEntryID;\n\n/**\n * This class holds information about a stream consumer with command\n * {@code xinfo stream mystream full}. They can be accessed via getters. There is also\n * {@link StreamConsumerFullInfo#getConsumerInfo()} method that returns a generic {@link Map} in\n * case more info are returned from the server.\n */\npublic class StreamConsumerFullInfo implements Serializable {\n\n  public static final String NAME = \"name\";\n  public static final String SEEN_TIME = \"seen-time\";\n  public static final String ACTIVE_TIME = \"active-time\";\n  public static final String PEL_COUNT = \"pel-count\";\n  public static final String PENDING = \"pending\";\n\n  private final String name;\n  private final Long seenTime;\n  private final Long activeTime; // since Redis 7.2\n  private final Long pelCount;\n  private final List<List<Object>> pending;\n  private final Map<String, Object> consumerInfo;\n\n  @SuppressWarnings(\"unchecked\")\n  public StreamConsumerFullInfo(Map<String, Object> map) {\n    consumerInfo = map;\n    name = (String) map.get(NAME);\n    seenTime = (Long) map.get(SEEN_TIME);\n    activeTime = (Long) map.get(ACTIVE_TIME);\n    pending = (List<List<Object>>) map.get(PENDING);\n    pelCount = (Long) map.get(PEL_COUNT);\n\n    pending.forEach(entry -> entry.set(0, new StreamEntryID((String) entry.get(0))));\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public Long getSeenTime() {\n    return seenTime;\n  }\n\n  /**\n   * Since Redis 7.2.\n   */\n  public Long getActiveTime() {\n    return activeTime;\n  }\n\n  public Long getPelCount() {\n    return pelCount;\n  }\n\n  public List<List<Object>> getPending() {\n    return pending;\n  }\n\n  /**\n   * All data.\n   */\n  public Map<String, Object> getConsumerInfo() {\n    return consumerInfo;\n  }\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamConsumerInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.Map;\n\n/**\n * This class holds information about a consumer. They can be accessed via getters. There is also\n * {@link StreamConsumersInfo#getConsumerInfo()}} method that returns a generic {@code Map} in case\n * more info are returned from the server.\n */\npublic class StreamConsumerInfo {\n\n  public static final String NAME = \"name\";\n  public static final String IDLE = \"idle\";\n  public static final String PENDING = \"pending\";\n  public static final String INACTIVE = \"inactive\";\n\n  private final String name;\n  private final long idle;\n  private final long pending;\n  private final Long inactive;\n  private final Map<String, Object> consumerInfo;\n\n  /**\n   * @param map contains key-value pairs with consumer info\n   */\n  public StreamConsumerInfo(Map<String, Object> map) {\n    consumerInfo = map;\n    name = (String) map.get(NAME);\n    idle = (Long) map.get(IDLE);\n    pending = (Long) map.get(PENDING);\n    inactive = (Long) map.get(INACTIVE);\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public long getIdle() {\n    return idle;\n  }\n\n  public long getPending() {\n    return pending;\n  }\n\n  /**\n   * Since Redis 7.2.\n   */\n  public Long getInactive() {\n    return inactive;\n  }\n\n  /**\n   * All data.\n   * @return Generic map containing all key-value pairs returned by the server\n   */\n  public Map<String, Object> getConsumerInfo() {\n    return consumerInfo;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamConsumersInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.Map;\n\n/**\n * This class holds information about a consumer. They can be accessed via getters. There is also\n * {@link StreamConsumersInfo#getConsumerInfo()}} method that returns a generic {@code Map} in case\n * more info are returned from the server.\n * @deprecated Use {@link StreamConsumerInfo}.\n */\n// TODO: rename to StreamConsumerInfo ?\n@Deprecated\npublic class StreamConsumersInfo extends StreamConsumerInfo {\n\n  /**\n   * @param map contains key-value pairs with consumer info\n   */\n  public StreamConsumersInfo(Map<String, Object> map) {\n    super(map);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamEntry.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.util.Map;\nimport redis.clients.jedis.StreamEntryID;\n\npublic class StreamEntry implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  private StreamEntryID id;\n  private Map<String, String> fields;\n  private Long millisElapsedFromDelivery;\n  private Long deliveredCount;\n\n  public StreamEntry(StreamEntryID id, Map<String, String> fields) {\n    this.id = id;\n    this.fields = fields;\n  }\n\n  public StreamEntry(StreamEntryID id, Map<String, String> fields, Long millisElapsedFromDelivery, Long deliveredCount) {\n    this.id = id;\n    this.fields = fields;\n    this.millisElapsedFromDelivery = millisElapsedFromDelivery;\n    this.deliveredCount = deliveredCount;\n  }\n\n  /**\n   * @return the milliseconds since the last delivery of this message when CLAIM was used.\n   *         <ul>\n   *         <li>{@code null} when not applicable</li>\n   *         <li>{@code 0} means not claimed from the pending entries list (PEL)</li>\n   *         <li>{@code > 0} means claimed from the PEL</li>\n   *         </ul>\n   * @since 7.1\n   */\n  public Long getMillisElapsedFromDelivery() {\n    return millisElapsedFromDelivery;\n  }\n\n  /**\n   * @return the number of prior deliveries of this message when CLAIM was used:\n   *         <ul>\n   *         <li>{@code null} when not applicable</li>\n   *         <li>{@code 0} means not claimed from the pending entries list (PEL)</li>\n   *         <li>{@code > 0} means claimed from the PEL</li>\n   *         </ul>\n   * @since 7.1\n   */\n  public Long getDeliveredCount() {\n    return deliveredCount;\n  }\n\n  public boolean isClaimed() {\n    return this.deliveredCount != null && this.deliveredCount > 0;\n  }\n\n  public StreamEntryID getID() {\n    return id;\n  }\n\n  public Map<String, String> getFields() {\n    return fields;\n  }\n\n  @Override\n  public String toString() {\n    return id + \" \" + fields;\n  }\n\n  private void writeObject(java.io.ObjectOutputStream out) throws IOException {\n    out.writeUnshared(this.id);\n    out.writeUnshared(this.fields);\n  }\n\n  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {\n    this.id = (StreamEntryID) in.readUnshared();\n    this.fields = (Map<String, String>) in.readUnshared();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamEntryBinary.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.util.Map;\nimport redis.clients.jedis.StreamEntryID;\n\npublic class StreamEntryBinary implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  private StreamEntryID id;\n  private Map<byte[], byte[]> fields;\n  private Long millisElapsedFromDelivery;\n  private Long deliveredCount;\n\n  public StreamEntryBinary(StreamEntryID id, Map<byte[], byte[]> fields) {\n    this.id = id;\n    this.fields = fields;\n  }\n\n  public StreamEntryBinary(StreamEntryID id, Map<byte[], byte[]> fields, Long millisElapsedFromDelivery, Long deliveredCount) {\n    this.id = id;\n    this.fields = fields;\n    this.millisElapsedFromDelivery = millisElapsedFromDelivery;\n    this.deliveredCount = deliveredCount;\n  }\n\n  /**\n   * @return the milliseconds since the last delivery of this message when CLAIM was used.\n   *         <ul>\n   *         <li>{@code null} when not applicable</li>\n   *         <li>{@code 0} means not claimed from the pending entries list (PEL)</li>\n   *         <li>{@code > 0} means claimed from the PEL</li>\n   *         </ul>\n   * @since 7.1\n   */\n  public Long getMillisElapsedFromDelivery() {\n    return millisElapsedFromDelivery;\n  }\n\n  /**\n   * @return the number of prior deliveries of this message when CLAIM was used:\n   *         <ul>\n   *         <li>{@code null} when not applicable</li>\n   *         <li>{@code 0} means not claimed from the pending entries list (PEL)</li>\n   *         <li>{@code > 0} means claimed from the PEL</li>\n   *         </ul>\n   * @since 7.1\n   */\n  public Long getDeliveredCount() {\n    return deliveredCount;\n  }\n\n  public boolean isClaimed() {\n    return this.deliveredCount != null && this.deliveredCount > 0;\n  }\n\n  public StreamEntryID getID() {\n    return id;\n  }\n\n  public Map<byte[], byte[]> getFields() {\n    return fields;\n  }\n\n  @Override\n  public String toString() {\n    return id + \" \" + fields;\n  }\n\n  private void writeObject(java.io.ObjectOutputStream out) throws IOException {\n    out.writeUnshared(this.id);\n    out.writeUnshared(this.fields);\n  }\n\n  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {\n    this.id = (StreamEntryID) in.readUnshared();\n    this.fields = (Map<byte[], byte[]>) in.readUnshared();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java",
    "content": "package redis.clients.jedis.resps;\n\n/**\n * Represents the result of a stream entry deletion operation for XDELEX and XACKDEL commands.\n * <ul>\n * <li>NOT_FOUND (-1): ID doesn't exist in stream</li>\n * <li>DELETED (1): Entry was deleted/acknowledged and deleted</li>\n * <li>NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED (2): Entry wasn't deleted.</li>\n * </ul>\n */\npublic enum StreamEntryDeletionResult {\n\n  /**\n   * The stream entry ID doesn't exist in the stream.\n   * <p>\n   * Returned when trying to delete/acknowledge a non-existent entry.\n   * </p>\n   */\n  NOT_FOUND(-1),\n\n  /**\n   * The entry was successfully deleted/acknowledged and deleted.\n   * <p>\n   * This is the typical successful case.\n   * </p>\n   */\n  DELETED(1),\n\n  /**\n   * The entry was not deleted due to one of the following reasons:\n   * <ul>\n   * <li>For XDELEX: The entry was not acknowledged by any consumer group</li>\n   * <li>For XACKDEL: The entry still has pending references in other consumer groups</li>\n   * </ul>\n   */\n  NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED(2);\n\n  private final int code;\n\n  StreamEntryDeletionResult(int code) {\n    this.code = code;\n  }\n\n  /**\n   * Gets the numeric code returned by Redis for this result.\n   * @return the numeric code (-1, 1, or 2)\n   */\n  public int getCode() {\n    return code;\n  }\n\n  /**\n   * Creates a StreamEntryDeletionResult from the numeric code returned by Redis.\n   * @param code the numeric code from Redis\n   * @return the corresponding StreamEntryDeletionResult\n   * @throws IllegalArgumentException if the code is not recognized\n   */\n  public static StreamEntryDeletionResult fromCode(int code) {\n    switch (code) {\n      case -1:\n        return NOT_FOUND;\n      case 1:\n        return DELETED;\n      case 2:\n        return NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED;\n      default:\n        throw new IllegalArgumentException(\"Unknown stream entry deletion result code: \" + code);\n    }\n  }\n\n  /**\n   * Creates a StreamEntryDeletionResult from a Long value returned by Redis.\n   * @param value the Long value from Redis\n   * @return the corresponding StreamEntryDeletionResult\n   * @throws IllegalArgumentException if the value is null or not recognized\n   */\n  public static StreamEntryDeletionResult fromLong(Long value) {\n    if (value == null) {\n      throw new IllegalArgumentException(\"Stream entry deletion result value cannot be null\");\n    }\n    return fromCode(value.intValue());\n  }\n\n  @Override\n  public String toString() {\n    return name() + \"(\" + code + \")\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamFullInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.StreamEntryID;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class holds information about a stream info with command {@code xinfo stream mystream full}.\n * They can be accessed via getters. There is also {@link StreamFullInfo#getStreamFullInfo()} method\n * that returns a generic {@link Map} in case where more info are returned from the server.\n */\npublic class StreamFullInfo implements Serializable {\n\n  public static final String LENGTH = \"length\";\n  public static final String RADIX_TREE_KEYS = \"radix-tree-keys\";\n  public static final String RADIX_TREE_NODES = \"radix-tree-nodes\";\n  public static final String GROUPS = \"groups\";\n  public static final String LAST_GENERATED_ID = \"last-generated-id\";\n  public static final String ENTRIES = \"entries\";\n\n  private final long length;\n  private final long radixTreeKeys;\n  private final long radixTreeNodes;\n  private final List<StreamGroupFullInfo> groups;\n  private final StreamEntryID lastGeneratedId;\n  private final List<StreamEntry> entries;\n  private final Map<String, Object> streamFullInfo;\n\n  /**\n   * @param map contains key-value pairs with stream info\n   */\n  @SuppressWarnings(\"unchecked\")\n  public StreamFullInfo(Map<String, Object> map) {\n\n    streamFullInfo = map;\n    length = (Long) map.get(LENGTH);\n    radixTreeKeys = (Long) map.get(RADIX_TREE_KEYS);\n    radixTreeNodes = (Long) map.get(RADIX_TREE_NODES);\n    groups = (List<StreamGroupFullInfo>) map.get(GROUPS);\n    lastGeneratedId = (StreamEntryID) map.get(LAST_GENERATED_ID);\n    entries = (List<StreamEntry>) map.get(ENTRIES);\n\n  }\n\n  public long getLength() {\n    return length;\n  }\n\n  public long getRadixTreeKeys() {\n    return radixTreeKeys;\n  }\n\n  public long getRadixTreeNodes() {\n    return radixTreeNodes;\n  }\n\n  public List<StreamGroupFullInfo> getGroups() {\n    return groups;\n  }\n\n  public StreamEntryID getLastGeneratedId() {\n    return lastGeneratedId;\n  }\n\n  public List<StreamEntry> getEntries() {\n    return entries;\n  }\n\n  public Map<String, Object> getStreamFullInfo() {\n    return streamFullInfo;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamGroupFullInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.StreamEntryID;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class holds information about a stream group with command {@code xinfo stream mystream full}.\n * They can be accessed via getters. There is also {@link StreamGroupFullInfo#getGroupFullInfo()}\n * method that returns a generic {@link Map} in case more info are returned from the server.\n */\npublic class StreamGroupFullInfo implements Serializable {\n\n  public static final String NAME = \"name\";\n  public static final String CONSUMERS = \"consumers\";\n  public static final String PENDING = \"pending\";\n  public static final String LAST_DELIVERED = \"last-delivered-id\";\n  public static final String PEL_COUNT = \"pel-count\";\n\n  private final String name;\n  private final List<StreamConsumerFullInfo> consumers;\n  private final List<List<Object>> pending;\n  private final Long pelCount;\n  private final StreamEntryID lastDeliveredId;\n  private final Map<String, Object> groupFullInfo;\n\n  /**\n   * @param map contains key-value pairs with group info\n   */\n  @SuppressWarnings(\"unchecked\")\n  public StreamGroupFullInfo(Map<String, Object> map) {\n\n    groupFullInfo = map;\n    name = (String) map.get(NAME);\n    consumers = (List<StreamConsumerFullInfo>) map.get(CONSUMERS);\n    pending = (List<List<Object>>) map.get(PENDING);\n    lastDeliveredId = (StreamEntryID) map.get(LAST_DELIVERED);\n    pelCount = (Long) map.get(PEL_COUNT);\n\n    pending.stream().forEach(entry -> entry.set(0, new StreamEntryID((String) entry.get(0))));\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public List<StreamConsumerFullInfo> getConsumers() {\n    return consumers;\n  }\n\n  public List<List<Object>> getPending() {\n    return pending;\n  }\n\n  public StreamEntryID getLastDeliveredId() {\n    return lastDeliveredId;\n  }\n\n  /**\n   * @return Generic map containing all key-value pairs returned by the server\n   */\n  public Map<String, Object> getGroupFullInfo() {\n    return groupFullInfo;\n  }\n\n  public Long getPelCount() {\n    return pelCount;\n  }\n\n}"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamGroupInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.Serializable;\nimport java.util.Map;\nimport redis.clients.jedis.StreamEntryID;\n\n/**\n * This class holds information about a stream group. They can be accessed via getters. There is also\n * {@link StreamGroupInfo#getGroupInfo()} method that returns a generic {@code Map} in case more\n * info are returned from the server.\n */\npublic class StreamGroupInfo implements Serializable {\n\n  public static final String NAME = \"name\";\n  public static final String CONSUMERS = \"consumers\";\n  public static final String PENDING = \"pending\";\n  public static final String LAST_DELIVERED = \"last-delivered-id\";\n\n  private final String name;\n  private final long consumers;\n  private final long pending;\n  private final StreamEntryID lastDeliveredId;\n  private final Map<String, Object> groupInfo;\n\n  /**\n   * @param map contains key-value pairs with group info\n   */\n  public StreamGroupInfo(Map<String, Object> map) {\n\n    groupInfo = map;\n    name = (String) map.get(NAME);\n    consumers = (long) map.get(CONSUMERS);\n    pending = (long) map.get(PENDING);\n    lastDeliveredId = (StreamEntryID) map.get(LAST_DELIVERED);\n\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public long getConsumers() {\n    return consumers;\n  }\n\n  public long getPending() {\n    return pending;\n  }\n\n  public StreamEntryID getLastDeliveredId() {\n    return lastDeliveredId;\n  }\n\n  /**\n   * @return Generic map containing all key-value pairs returned by the server\n   */\n  public Map<String, Object> getGroupInfo() {\n    return groupInfo;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.Serializable;\nimport java.util.Map;\nimport redis.clients.jedis.StreamEntryID;\n\n/**\n * This class holds information about stream. They can be accessed via getters. There is also\n * {@link StreamInfo#getStreamInfo} method that returns a generic {@code Map} in case more info are\n * returned from the server.\n */\npublic class StreamInfo implements Serializable {\n\n  public static final String LENGTH = \"length\";\n  public static final String RADIX_TREE_KEYS = \"radix-tree-keys\";\n  public static final String RADIX_TREE_NODES = \"radix-tree-nodes\";\n  public static final String GROUPS = \"groups\";\n  public static final String LAST_GENERATED_ID = \"last-generated-id\";\n  public static final String FIRST_ENTRY = \"first-entry\";\n  public static final String LAST_ENTRY = \"last-entry\";\n  public static final String IDMP_DURATION = \"idmp-duration\";\n  public static final String IDMP_MAXSIZE = \"idmp-maxsize\";\n  public static final String PIDS_TRACKED = \"pids-tracked\";\n  public static final String IIDS_TRACKED = \"iids-tracked\";\n  public static final String IIDS_ADDED = \"iids-added\";\n  public static final String IIDS_DUPLICATES = \"iids-duplicates\";\n\n  private final long length;\n  private final long radixTreeKeys;\n  private final long radixTreeNodes;\n  private final long groups;\n  private final StreamEntryID lastGeneratedId;\n  private final StreamEntry firstEntry;\n  private final StreamEntry lastEntry;\n  private final Long idmpDuration;\n  private final Long idmpMaxsize;\n  private final Long pidsTracked;\n  private final Long iidsTracked;\n  private final Long iidsAdded;\n  private final Long iidsDuplicates;\n  private final Map<String, Object> streamInfo;\n\n  /**\n   * @param map contains key-value pairs with stream info\n   */\n  public StreamInfo(Map<String, Object> map) {\n\n    streamInfo = map;\n    length = (Long) map.get(LENGTH);\n    radixTreeKeys = (Long) map.get(RADIX_TREE_KEYS);\n    radixTreeNodes = (Long) map.get(RADIX_TREE_NODES);\n    groups = (Long) map.get(GROUPS);\n    lastGeneratedId = (StreamEntryID) map.get(LAST_GENERATED_ID);\n    firstEntry = (StreamEntry) map.get(FIRST_ENTRY);\n    lastEntry = (StreamEntry) map.get(LAST_ENTRY);\n    idmpDuration = (Long) map.get(IDMP_DURATION);\n    idmpMaxsize = (Long) map.get(IDMP_MAXSIZE);\n    pidsTracked = (Long) map.get(PIDS_TRACKED);\n    iidsTracked = (Long) map.get(IIDS_TRACKED);\n    iidsAdded = (Long) map.get(IIDS_ADDED);\n    iidsDuplicates = (Long) map.get(IIDS_DUPLICATES);\n\n  }\n\n  public long getLength() {\n    return length;\n  }\n\n  public long getRadixTreeKeys() {\n    return radixTreeKeys;\n  }\n\n  public long getRadixTreeNodes() {\n    return radixTreeNodes;\n  }\n\n  public long getGroups() {\n    return groups;\n  }\n\n  public StreamEntryID getLastGeneratedId() {\n    return lastGeneratedId;\n  }\n\n  public StreamEntry getFirstEntry() {\n    return firstEntry;\n  }\n\n  public StreamEntry getLastEntry() {\n    return lastEntry;\n  }\n\n  /**\n   * @return The duration (in seconds) that each idempotent ID is kept, or null if not configured\n   */\n  public Long getIdmpDuration() {\n    return idmpDuration;\n  }\n\n  /**\n   * @return The maximum number of most recent idempotent IDs kept for each producer ID, or null if not configured\n   */\n  public Long getIdmpMaxsize() {\n    return idmpMaxsize;\n  }\n\n  /**\n   * @return The number of producer IDs currently tracked in the stream, or null if not available\n   */\n  public Long getPidsTracked() {\n    return pidsTracked;\n  }\n\n  /**\n   * @return The number of idempotent IDs currently tracked in the stream (for all producers), or null if not available\n   */\n  public Long getIidsTracked() {\n    return iidsTracked;\n  }\n\n  /**\n   * @return The count of all entries with an idempotent ID added to the stream during its lifetime (not including duplicates), or null if not available\n   */\n  public Long getIidsAdded() {\n    return iidsAdded;\n  }\n\n  /**\n   * @return The count of duplicate idempotent IDs (for all producers) detected during the stream's lifetime, or null if not available\n   */\n  public Long getIidsDuplicates() {\n    return iidsDuplicates;\n  }\n\n  /**\n   * @return Generic map containing all key-value pairs returned by the server\n   */\n  public Map<String, Object> getStreamInfo() {\n    return streamInfo;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamPendingEntry.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport redis.clients.jedis.StreamEntryID;\n\npublic class StreamPendingEntry implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  private StreamEntryID id;\n  private String consumerName;\n  private long idleTime;\n  private long deliveredTimes;\n\n  public StreamPendingEntry(StreamEntryID id, String consumerName, long idleTime,\n      long deliveredTimes) {\n    this.id = id;\n    this.consumerName = consumerName;\n    this.idleTime = idleTime;\n    this.deliveredTimes = deliveredTimes;\n  }\n\n  public StreamEntryID getID() {\n    return id;\n  }\n\n  public long getIdleTime() {\n    return idleTime;\n  }\n\n  public long getDeliveredTimes() {\n    return deliveredTimes;\n  }\n\n  public String getConsumerName() {\n    return consumerName;\n  }\n\n  @Override\n  public String toString() {\n    return this.id + \" \" + this.consumerName + \" idle:\" + this.idleTime + \" times:\"\n        + this.deliveredTimes;\n  }\n\n  private void writeObject(java.io.ObjectOutputStream out) throws IOException {\n    out.writeUnshared(this.id);\n    out.writeUTF(this.consumerName);\n    out.writeLong(idleTime);\n    out.writeLong(this.deliveredTimes);\n  }\n\n  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {\n    this.id = (StreamEntryID) in.readUnshared();\n    this.consumerName = in.readUTF();\n    this.idleTime = in.readLong();\n    this.deliveredTimes = in.readLong();\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/StreamPendingSummary.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.Serializable;\nimport java.util.Map;\nimport redis.clients.jedis.StreamEntryID;\n\npublic class StreamPendingSummary implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  private final long total;\n  private final StreamEntryID minId;\n  private final StreamEntryID maxId;\n  private final Map<String, Long> consumerMessageCount;\n\n  public StreamPendingSummary(long total, StreamEntryID minId, StreamEntryID maxId,\n      Map<String, Long> consumerMessageCount) {\n    this.total = total;\n    this.minId = minId;\n    this.maxId = maxId;\n    this.consumerMessageCount = consumerMessageCount;\n  }\n\n  public long getTotal() {\n    return total;\n  }\n\n  public StreamEntryID getMinId() {\n    return minId;\n  }\n\n  public StreamEntryID getMaxId() {\n    return maxId;\n  }\n\n  public Map<String, Long> getConsumerMessageCount() {\n    return consumerMessageCount;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/TrackingInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.util.KeyValue;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport static redis.clients.jedis.BuilderFactory.*;\n\npublic class TrackingInfo {\n\n    private final List<String> flags;\n    private final long redirect;\n    private final List<String> prefixes;\n\n    public TrackingInfo(List<String> flags, long redirect, List<String> prefixes) {\n        this.flags = flags;\n        this.redirect = redirect;\n        this.prefixes = prefixes;\n    }\n\n\n    public List<String> getFlags() {\n        return flags;\n    }\n\n    public long getRedirect() {\n        return redirect;\n    }\n\n    public List<String> getPrefixes() {\n        return prefixes;\n    }\n\n    public static final Builder<TrackingInfo> TRACKING_INFO_BUILDER = new Builder<TrackingInfo>() {\n        @Override\n        public TrackingInfo build(Object data) {\n            List commandData = (List) data;\n\n            if (commandData.get(0) instanceof KeyValue) {\n                List<String> flags = Collections.emptyList();\n                long redirect = -1;\n                List<String> prefixes = Collections.emptyList();\n\n                for (KeyValue kv : (List<KeyValue>) commandData) {\n                    switch (STRING.build(kv.getKey())) {\n                        case \"flags\":\n                            flags = STRING_LIST.build(kv.getValue());\n                            break;\n                        case \"redirect\":\n                            redirect = LONG.build(kv.getValue());\n                            break;\n                        case \"prefixes\":\n                            prefixes = STRING_LIST.build(kv.getValue());\n                            break;\n                    }\n                }\n\n                return new TrackingInfo(flags, redirect, prefixes);\n            } else {\n                List<String> flags = STRING_LIST.build(commandData.get(1));\n                long redirect = LONG.build(commandData.get(3));\n                List<String> prefixes = STRING_LIST.build(commandData.get(5));\n\n                return new TrackingInfo(flags, redirect, prefixes);\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/Tuple.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\nimport redis.clients.jedis.util.ByteArrayComparator;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class Tuple implements Comparable<Tuple> {\n  private byte[] element;\n  private Double score;\n\n  public Tuple(String element, Double score) {\n    this(SafeEncoder.encode(element), score);\n  }\n\n  public Tuple(byte[] element, Double score) {\n    super();\n    this.element = element;\n    this.score = score;\n  }\n\n  @Override\n  public int hashCode() {\n    final int prime = 31;\n    int result = 1;\n    result = prime * result;\n    if (null != element) {\n      for (final byte b : element) {\n        result = prime * result + b;\n      }\n    }\n    long temp = Double.doubleToLongBits(score);\n    result = prime * result + (int) (temp ^ (temp >>> 32));\n    return result;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null) return false;\n    if (obj == this) return true;\n    if (!(obj instanceof Tuple)) return false;\n\n    Tuple other = (Tuple) obj;\n    if (!Arrays.equals(element, other.element)) return false;\n    return Objects.equals(score, other.score);\n  }\n\n  @Override\n  public int compareTo(Tuple other) {\n    return compare(this, other);\n  }\n\n  public static int compare(Tuple t1, Tuple t2) {\n    int compScore = Double.compare(t1.score, t2.score);\n    if (compScore != 0) return compScore;\n\n    return ByteArrayComparator.compare(t1.element, t2.element);\n  }\n\n  public String getElement() {\n    if (null != element) {\n      return SafeEncoder.encode(element);\n    } else {\n      return null;\n    }\n  }\n\n  public byte[] getBinaryElement() {\n    return element;\n  }\n\n  public double getScore() {\n    return score;\n  }\n\n  @Override\n  // TODO: element=score\n  public String toString() {\n    return '[' + SafeEncoder.encode(element) + ',' + score + ']';\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/VSimScoreAttribs.java",
    "content": "package redis.clients.jedis.resps;\n\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * Response object containing both similarity score and attributes for VSIM command when used with\n * WITHSCORES and WITHATTRIBS options.\n */\n@Experimental\npublic class VSimScoreAttribs {\n\n  private final Double score;\n  private final String attributes;\n\n  /**\n   * Creates a new VSimScoreAttribs instance.\n   * @param score the similarity score (0.0 to 1.0)\n   * @param attributes the element attributes as JSON string, or null if no attributes\n   */\n  public VSimScoreAttribs(Double score, String attributes) {\n    this.score = score;\n    this.attributes = attributes;\n  }\n\n  /**\n   * Gets the similarity score.\n   * @return the similarity score between 0.0 and 1.0\n   */\n  public Double getScore() {\n    return score;\n  }\n\n  /**\n   * Gets the element attributes.\n   * @return the attributes as JSON string, or null if no attributes are set\n   */\n  public String getAttributes() {\n    return attributes;\n  }\n\n  @Override\n  public String toString() {\n    return \"VSimScoreAttribs{\" + \"score=\" + score + \", attributes='\" + attributes + '\\'' + '}';\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n\n    VSimScoreAttribs that = (VSimScoreAttribs) o;\n\n    if (score != null ? !score.equals(that.score) : that.score != null) return false;\n    return attributes != null ? attributes.equals(that.attributes) : that.attributes == null;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = score != null ? score.hashCode() : 0;\n    result = 31 * result + (attributes != null ? attributes.hashCode() : 0);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/VectorInfo.java",
    "content": "package redis.clients.jedis.resps;\n\nimport java.io.Serializable;\nimport java.util.Map;\n\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * This class holds information about a vector set returned by the {@code VINFO} command. They can\n * be accessed via getters. There is also {@link VectorInfo#getVectorInfo()} method that returns a\n * generic {@link Map} in case where more info are returned from the server.\n */\n@Experimental\npublic class VectorInfo implements Serializable {\n  public static final String VECTOR_DIM = \"vector-dim\";\n  public static final String TYPE = \"quant-type\";\n  public static final String SIZE = \"size\";\n  public static final String MAX_NODE_UID = \"hnsw-max-node-uid\";\n  public static final String VSET_UID = \"vset-uid\";\n  public static final String MAX_NODES = \"hnsw-m\";\n  public static final String PROJECTION_INPUT_DIM = \"projection-input-dim\";\n  public static final String ATTRIBUTES_COUNT = \"attributes-count\";\n  public static final String MAX_LEVEL = \"max-level\";\n\n  private final Long dimensionality;\n  private final String type; // Will be converted to QuantizationType if needed\n  private final Long size;\n  private final Long maxNodeUid;\n  private final Long vSetUid;\n  private final Long maxNodes;\n  private final Long projectionInputDim;\n  private final Long attributesCount;\n  private final Long maxLevel;\n  private final Map<String, Object> vectorInfo;\n\n  /**\n   * @param map contains key-value pairs with vector set info\n   */\n  public VectorInfo(Map<String, Object> map) {\n    vectorInfo = map;\n    dimensionality = (Long) map.get(VECTOR_DIM);\n    type = (String) map.get(TYPE);\n    size = (Long) map.get(SIZE);\n    maxNodeUid = (Long) map.get(MAX_NODE_UID);\n    vSetUid = (Long) map.get(VSET_UID);\n    maxNodes = (Long) map.get(MAX_NODES);\n    projectionInputDim = (Long) map.get(PROJECTION_INPUT_DIM);\n    attributesCount = (Long) map.get(ATTRIBUTES_COUNT);\n    maxLevel = (Long) map.get(MAX_LEVEL);\n  }\n\n  public Long getDimensionality() {\n    return dimensionality;\n  }\n\n  public String getType() {\n    return type;\n  }\n\n  public Long getSize() {\n    return size;\n  }\n\n  public Long getMaxNodeUid() {\n    return maxNodeUid;\n  }\n\n  public Long getVSetUid() {\n    return vSetUid;\n  }\n\n  public Long getMaxNodes() {\n    return maxNodes;\n  }\n\n  public Long getProjectionInputDim() {\n    return projectionInputDim;\n  }\n\n  public Long getAttributesCount() {\n    return attributesCount;\n  }\n\n  public Long getMaxLevel() {\n    return maxLevel;\n  }\n\n  public Map<String, Object> getVectorInfo() {\n    return vectorInfo;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/resps/package-info.java",
    "content": "/**\n * This package contains custom responses of core Redis commands.\n */\npackage redis.clients.jedis.resps;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Apply.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.APPLY;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.AS;\n\n/**\n * APPLY operation for search commands. Computes a new field based on an expression.\n */\n@Experimental\npublic class Apply implements IParams {\n\n  private final String expression;\n  private final String alias;\n\n  private Apply(String expression, String alias) {\n    this.expression = expression;\n    this.alias = alias;\n  }\n\n  /**\n   * Create an APPLY operation.\n   * @param expression the expression to apply\n   * @return a new Apply instance\n   */\n  public static Apply of(String expression) {\n    return new Apply(expression, null);\n  }\n\n  /**\n   * Create an APPLY operation.\n   * @param expression the expression to apply\n   * @param alias the alias for the result\n   * @return a new Apply instance\n   */\n  public static Apply of(String expression, String alias) {\n    return new Apply(expression, alias);\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(APPLY);\n    args.add(expression);\n    if (alias != null) {\n      args.add(AS);\n      args.add(alias);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Combiner.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\n\nimport java.util.List;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.YIELD_SCORE_AS;\n\n/**\n * Abstract combiner for combining multiple search scores. Instances are created via\n * {@link Combiners}.\n * @see Combiners\n */\n@Experimental\npublic abstract class Combiner implements IParams {\n\n  private final String name;\n  private String scoreAlias;\n\n  protected Combiner(String name) {\n    this.name = name;\n  }\n\n  public final String getName() {\n    return name;\n  }\n\n  /**\n   * Set an alias for the combined score field using YIELD_SCORE_AS.\n   * @param alias the field name to use for the combined score\n   * @return this instance\n   */\n  public final Combiner as(String alias) {\n    this.scoreAlias = alias;\n    return this;\n  }\n\n  protected abstract List<Object> getOwnArgs();\n\n  @Override\n  public final void addParams(CommandArguments args) {\n    args.add(name);\n\n    List<Object> ownArgs = getOwnArgs();\n    args.add(ownArgs.size());\n    ownArgs.forEach(args::add);\n\n    if (scoreAlias != null) {\n      args.add(YIELD_SCORE_AS);\n      args.add(scoreAlias);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Combiners.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.annots.Experimental;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\n/**\n * Factory class for creating {@link Combiner} instances.\n * <p>\n * Example usage:\n * </p>\n *\n * <pre>\n * // RRF with default parameters\n * Combiners.rrf()\n *\n * // RRF with custom window and constant\n * Combiners.rrf().window(10).constant(60)\n *\n * // Linear combination with weights\n * Combiners.linear().alpha(0.7).beta(0.3)\n *\n * // With score alias\n * Combiners.rrf().as(\"combined_score\")\n * </pre>\n *\n * @see Combiner\n */\n@Experimental\npublic final class Combiners {\n\n  private Combiners() {\n  }\n\n  /**\n   * Create an RRF (Reciprocal Rank Fusion) combiner.\n   * @return a new RRF combiner\n   */\n  public static RRF rrf() {\n    return new RRF();\n  }\n\n  /**\n   * Create a Linear combination combiner.\n   * @return a new Linear combiner\n   */\n  public static Linear linear() {\n    return new Linear();\n  }\n\n  /**\n   * RRF (Reciprocal Rank Fusion) combiner.\n   */\n  public static class RRF extends Combiner {\n    private Integer window;\n    private Double constant;\n\n    RRF() {\n      super(\"RRF\");\n    }\n\n    /**\n     * Set the WINDOW parameter for RRF.\n     * @param window the window size\n     * @return this RRF instance\n     */\n    public RRF window(int window) {\n      this.window = window;\n      return this;\n    }\n\n    /**\n     * Set the CONSTANT parameter for RRF.\n     * @param constant the constant value (typically 60)\n     * @return this RRF instance\n     */\n    public RRF constant(double constant) {\n      this.constant = constant;\n      return this;\n    }\n\n    @Override\n    protected List<Object> getOwnArgs() {\n      if (window == null && constant == null) {\n        return Collections.emptyList();\n      }\n      List<Object> args = new ArrayList<>();\n      if (window != null) {\n        args.add(WINDOW);\n        args.add(window);\n      }\n      if (constant != null) {\n        args.add(CONSTANT);\n        args.add(constant);\n      }\n      return args;\n    }\n  }\n\n  /**\n   * Linear combination combiner.\n   */\n  public static class Linear extends Combiner {\n    private Double alpha;\n    private Double beta;\n    private Integer window;\n\n    Linear() {\n      super(\"LINEAR\");\n    }\n\n    /**\n     * Set the ALPHA parameter (weight for text search score).\n     * @param alpha the alpha value (0.0 to 1.0)\n     * @return this Linear instance\n     */\n    public Linear alpha(double alpha) {\n      this.alpha = alpha;\n      return this;\n    }\n\n    /**\n     * Set the BETA parameter (weight for vector similarity score).\n     * @param beta the beta value (0.0 to 1.0)\n     * @return this Linear instance\n     */\n    public Linear beta(double beta) {\n      this.beta = beta;\n      return this;\n    }\n\n    /**\n     * Set the WINDOW parameter for LINEAR.\n     * @param window the window size\n     * @return this Linear instance\n     */\n    public Linear window(int window) {\n      this.window = window;\n      return this;\n    }\n\n    @Override\n    protected List<Object> getOwnArgs() {\n      if (alpha == null && beta == null && window == null) {\n        return Collections.emptyList();\n      }\n      List<Object> args = new ArrayList<>();\n      if (alpha != null) {\n        args.add(ALPHA);\n        args.add(alpha);\n      }\n      if (beta != null) {\n        args.add(BETA);\n        args.add(beta);\n      }\n      if (window != null) {\n        args.add(WINDOW);\n        args.add(window);\n      }\n      return args;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Document.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * Document represents a single indexed document or entity in the engine\n */\npublic class Document implements Serializable {\n\n  private static final long serialVersionUID = 4884173545291367373L;\n\n  private final String id;\n  private Double score;\n  private final Map<String, Object> fields;\n\n  public Document(String id) {\n    this(id, 1.0);\n  }\n\n  public Document(String id, double score) {\n    this(id, new HashMap<>(), score);\n  }\n\n  public Document(String id, Map<String, Object> fields) {\n    this(id, fields, 1.0f);\n  }\n\n  public Document(String id, Map<String, Object> fields, double score) {\n    this.id = id;\n    this.fields = fields;\n    this.score = score;\n  }\n\n  private Document(String id, Double score, Map<String, Object> fields) {\n    this.id = id;\n    this.score = score;\n    this.fields = fields;\n  }\n\n  public Iterable<Map.Entry<String, Object>> getProperties() {\n    return fields.entrySet();\n  }\n\n  /**\n   * @return the document's id\n   */\n  public String getId() {\n    return id;\n  }\n\n  /**\n   * @return the document's score\n   */\n  public Double getScore() {\n    return score;\n  }\n\n  /**\n   * return the property value inside a key\n   *\n   * @param key key of the property\n   *\n   * @return the property value\n   */\n  public Object get(String key) {\n    return fields.get(key);\n  }\n\n  /**\n   * return the property value inside a key\n   *\n   * @param key key of the property\n   *\n   * @return the property value\n   */\n  public String getString(String key) {\n    Object value = fields.get(key);\n    if (value == null) {\n      return null;\n    } else if (value instanceof String) {\n      return (String) value;\n    } else if (value instanceof byte[]) {\n      return SafeEncoder.encode((byte[]) value);\n    } else {\n      return String.valueOf(value);\n    }\n  }\n\n  public boolean hasProperty(String key) {\n    return fields.containsKey(key);\n  }\n\n  // TODO: private ??\n  public Document set(String key, Object value) {\n    fields.put(key, value);\n    return this;\n  }\n\n  /**\n   * Set the document's score\n   *\n   * @param score new score to set\n   * @return the document itself\n   * @deprecated\n   */\n  @Deprecated\n  public Document setScore(float score) {\n    this.score = (double) score;\n    return this;\n  }\n\n  @Override\n  public String toString() {\n    return \"id:\" + this.getId() + \", score: \" + this.getScore() +\n            \", properties:\" + this.getProperties();\n  }\n\n  /// RESP2 -->\n  public static Document load(String id, double score, byte[] payload, List<byte[]> fields) {\n    return Document.load(id, score, fields, true);\n  }\n\n  public static Document load(String id, double score, List<byte[]> fields, boolean decode) {\n    return load(id, score, fields, decode, null);\n  }\n\n  /**\n   * Parse document object from FT.SEARCH reply.\n   * @param id\n   * @param score\n   * @param fields\n   * @param decode\n   * @param isFieldDecode checked only if {@code decode=true}\n   * @return document\n   */\n  public static Document load(String id, double score, List<byte[]> fields, boolean decode,\n      Map<String, Boolean> isFieldDecode) {\n    Document ret = new Document(id, score);\n    if (fields != null) {\n      for (int i = 0; i < fields.size(); i += 2) {\n        byte[] rawKey = fields.get(i);\n        byte[] rawValue = fields.get(i + 1);\n        String key = SafeEncoder.encode(rawKey);\n        Object value = rawValue == null ? null\n            : (decode && (isFieldDecode == null || !Boolean.FALSE.equals(isFieldDecode.get(key))))\n            ? SafeEncoder.encode(rawValue) : rawValue;\n        ret.set(key, value);\n      }\n    }\n    return ret;\n  }\n  /// <-- RESP2\n\n  /// RESP3 -->\n  // TODO: final\n  static Builder<Document> SEARCH_DOCUMENT = new PerFieldDecoderDocumentBuilder((Map) null);\n\n  static final class PerFieldDecoderDocumentBuilder extends Builder<Document> {\n\n    private static final String ID_STR = \"id\";\n    private static final String SCORE_STR = \"score\";\n    private static final String FIELDS_STR = \"extra_attributes\";\n\n    private final Map<String, Boolean> isFieldDecode;\n\n    public PerFieldDecoderDocumentBuilder(Map<String, Boolean> isFieldDecode) {\n      this.isFieldDecode = isFieldDecode != null ? isFieldDecode : Collections.emptyMap();\n    }\n\n    @Override\n    public Document build(Object data) {\n      List<KeyValue> list = (List<KeyValue>) data;\n      String id = null;\n      Double score = null;\n      Map<String, Object> fields = null;\n      for (KeyValue kv : list) {\n        String key = BuilderFactory.STRING.build(kv.getKey());\n        switch (key) {\n          case ID_STR:\n            id = BuilderFactory.STRING.build(kv.getValue());\n            break;\n          case SCORE_STR:\n            score = BuilderFactory.DOUBLE.build(kv.getValue());\n            break;\n          case FIELDS_STR:\n            fields = makeFieldsMap(isFieldDecode, kv.getValue());\n            break;\n        }\n      }\n      return new Document(id, score, fields);\n    }\n  };\n\n  private static Map<String, Object> makeFieldsMap(Map<String, Boolean> isDecode, Object data) {\n    if (data == null) return null;\n\n    final List<KeyValue> list = (List) data;\n\n    Map<String, Object> map = new HashMap<>(list.size(), 1f);\n    list.stream().filter((kv) -> (kv != null && kv.getKey() != null && kv.getValue() != null))\n        .forEach((kv) -> {\n          String key = BuilderFactory.STRING.build(kv.getKey());\n          map.put(key,\n              (Boolean.FALSE.equals(isDecode.get(key)) ? BuilderFactory.RAW_OBJECT\n                  : BuilderFactory.AGGRESSIVE_ENCODED_OBJECT).build(kv.getValue()));\n        });\n    return map;\n  }\n  /// <-- RESP3\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/FTCreateParams.java",
    "content": "package redis.clients.jedis.search;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\npublic class FTCreateParams implements IParams {\n\n  private IndexDataType dataType;\n  private Collection<String> prefix;\n  private String filter;\n  private String language;\n  private String languageField;\n  private Double score;\n  private String scoreField;\n  private boolean maxTextFields;\n  private boolean noOffsets;\n  private Long temporary;\n  private boolean noHL;\n  private boolean noFields;\n  private boolean noFreqs;\n  private Collection<String> stopwords;\n  private boolean skipInitialScan;\n\n  public FTCreateParams() {\n  }\n\n  public static FTCreateParams createParams() {\n    return new FTCreateParams();\n  }\n\n  /**\n   * Currently supports HASH (default) and JSON. To index JSON, you must have the RedisJSON module\n   * installed.\n   */\n  public FTCreateParams on(IndexDataType dataType) {\n    this.dataType = dataType;\n    return this;\n  }\n\n  /**\n   * Tells the index which keys it should index. You can add several prefixes to index.\n   */\n  public FTCreateParams prefix(String... prefixes) {\n    if (this.prefix == null) {\n      this.prefix = new ArrayList<>(prefixes.length);\n    }\n    Arrays.stream(prefixes).forEach(p -> this.prefix.add(p));\n    return this;\n  }\n\n  /**\n   * This method can be chained to add multiple prefixes.\n   *\n   * @see FTCreateParams#prefix(java.lang.String...)\n   */\n  public FTCreateParams addPrefix(String prefix) {\n    if (this.prefix == null) {\n      this.prefix = new ArrayList<>();\n    }\n    this.prefix.add(prefix);\n    return this;\n  }\n\n  /**\n   * A filter expression with the full RediSearch aggregation expression language.\n   */\n  public FTCreateParams filter(String filter) {\n    this.filter = filter;\n    return this;\n  }\n\n  /**\n   * Indicates the default language for documents in the index.\n   */\n  public FTCreateParams language(String defaultLanguage) {\n    this.language = defaultLanguage;\n    return this;\n  }\n\n  /**\n   * Document attribute set as the document language.\n   */\n  public FTCreateParams languageField(String languageAttribute) {\n    this.languageField = languageAttribute;\n    return this;\n  }\n\n  /**\n   * Default score for documents in the index.\n   */\n  public FTCreateParams score(double defaultScore) {\n    this.score = defaultScore;\n    return this;\n  }\n\n  /**\n   * Document attribute that you use as the document rank based on the user ranking.\n   * Ranking must be between 0.0 and 1.0.\n   */\n  public FTCreateParams scoreField(String scoreField) {\n    this.scoreField = scoreField;\n    return this;\n  }\n\n  /**\n   * Forces RediSearch to encode indexes as if there were more than 32 text attributes.\n   */\n  public FTCreateParams maxTextFields() {\n    this.maxTextFields = true;\n    return this;\n  }\n\n  /**\n   * Does not store term offsets for documents. It saves memory, but does not allow exact searches\n   * or highlighting.\n   */\n  public FTCreateParams noOffsets() {\n    this.noOffsets = true;\n    return this;\n  }\n\n  /**\n   * Creates a lightweight temporary index that expires after a specified period of inactivity.\n   */\n  public FTCreateParams temporary(long seconds) {\n    this.temporary = seconds;\n    return this;\n  }\n\n  /**\n   * Conserves storage space and memory by disabling highlighting support.\n   */\n  public FTCreateParams noHL() {\n    this.noHL = true;\n    return this;\n  }\n\n  /**\n   * @see FTCreateParams#noHL()\n   */\n  public FTCreateParams noHighlights() {\n    return noHL();\n  }\n\n  /**\n   * Does not store attribute bits for each term. It saves memory, but it does not allow filtering\n   * by specific attributes.\n   */\n  public FTCreateParams noFields() {\n    this.noFields = true;\n    return this;\n  }\n\n  /**\n   * Avoids saving the term frequencies in the index. It saves memory, but does not allow sorting\n   * based on the frequencies of a given term within the document.\n   */\n  public FTCreateParams noFreqs() {\n    this.noFreqs = true;\n    return this;\n  }\n\n  /**\n   * Sets the index with a custom stopword list, to be ignored during indexing and search time.\n   */\n  public FTCreateParams stopwords(String... stopwords) {\n    this.stopwords = Arrays.asList(stopwords);\n    return this;\n  }\n\n  /**\n   * The index does not have stopwords, not even the default ones.\n   */\n  public FTCreateParams noStopwords() {\n    this.stopwords = Collections.emptyList();\n    return this;\n  }\n\n  /**\n   * Does not scan and index.\n   */\n  public FTCreateParams skipInitialScan() {\n    this.skipInitialScan = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (dataType != null) {\n      args.add(ON).add(dataType);\n    }\n\n    if (prefix != null) {\n      args.add(PREFIX).add(prefix.size()).addObjects(prefix);\n    }\n\n    if (filter != null) {\n      args.add(FILTER).add(filter);\n    }\n\n    if (language != null) {\n      args.add(LANGUAGE).add(language);\n    }\n    if (languageField != null) {\n      args.add(LANGUAGE_FIELD).add(languageField);\n    }\n\n    if (score != null) {\n      args.add(SCORE).add(score);\n    }\n    if (scoreField != null) {\n      args.add(SCORE_FIELD).add(scoreField);\n    }\n\n    if (maxTextFields) {\n      args.add(MAXTEXTFIELDS);\n    }\n\n    if (noOffsets) {\n      args.add(NOOFFSETS);\n    }\n\n    if (temporary != null) {\n      args.add(TEMPORARY).add(temporary);\n    }\n\n    if (noHL) {\n      args.add(NOHL);\n    }\n\n    if (noFields) {\n      args.add(NOFIELDS);\n    }\n\n    if (noFreqs) {\n      args.add(NOFREQS);\n    }\n\n    if (stopwords != null) {\n      args.add(STOPWORDS).add(stopwords.size());\n      stopwords.forEach(w -> args.add(w));\n    }\n\n    if (skipInitialScan) {\n      args.add(SKIPINITIALSCAN);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/FTProfileParams.java",
    "content": "package redis.clients.jedis.search;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.LIMITED;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\npublic class FTProfileParams implements IParams {\n\n  private boolean limited;\n\n  public FTProfileParams() {\n  }\n\n  public static FTProfileParams profileParams() {\n    return new FTProfileParams();\n  }\n\n  /**\n   * Removes details of {@code reader} iterator.\n   */\n  public FTProfileParams limited() {\n    this.limited = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (limited) {\n      args.add(LIMITED);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/FTSearchParams.java",
    "content": "package redis.clients.jedis.search;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport java.util.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.annots.Internal;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.args.SortingOrder;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.util.LazyRawable;\n\n/**\n * Query represents query parameters and filters to load results from the engine\n */\npublic class FTSearchParams implements IParams {\n\n  private boolean noContent = false;\n  private boolean verbatim = false;\n  private boolean noStopwords = false;\n  private boolean withScores = false;\n  private final List<IParams> filters = new LinkedList<>();\n  private Collection<String> inKeys;\n  private Collection<String> inFields;\n  private Collection<FieldName> returnFieldsNames;\n  private boolean summarize;\n  private SummarizeParams summarizeParams;\n  private boolean highlight;\n  private HighlightParams highlightParams;\n  private Integer slop;\n  private Long timeout;\n  private boolean inOrder;\n  private String language;\n  private String expander;\n  private String scorer;\n  // private boolean explainScore; // TODO\n  private String sortBy;\n  private SortingOrder sortOrder;\n  private int[] limit;\n  private Map<String, Object> params;\n  private Integer dialect;\n\n  /// non command parameters\n  private Map<String, Boolean> returnFieldDecodeMap = null;\n\n  public FTSearchParams() {\n  }\n\n  public static FTSearchParams searchParams() {\n    return new FTSearchParams();\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (noContent) {\n      args.add(NOCONTENT);\n    }\n    if (verbatim) {\n      args.add(VERBATIM);\n    }\n    if (noStopwords) {\n      args.add(NOSTOPWORDS);\n    }\n    if (withScores) {\n      args.add(WITHSCORES);\n    }\n\n    if (!filters.isEmpty()) {\n      filters.forEach(filter -> filter.addParams(args));\n    }\n\n    if (inKeys != null && !inKeys.isEmpty()) {\n      args.add(INKEYS).add(inKeys.size()).addObjects(inKeys);\n    }\n\n    if (inFields != null && !inFields.isEmpty()) {\n      args.add(INFIELDS).add(inFields.size()).addObjects(inFields);\n    }\n\n    if (returnFieldsNames != null && !returnFieldsNames.isEmpty()) {\n      args.add(RETURN);\n      LazyRawable returnCountObject = new LazyRawable();\n      args.add(returnCountObject); // holding a place for setting the total count later.\n      int returnCount = 0;\n      for (FieldName fn : returnFieldsNames) {\n        returnCount += fn.addCommandArguments(args);\n      }\n      returnCountObject.setRaw(Protocol.toByteArray(returnCount));\n    }\n\n    if (summarizeParams != null) {\n      args.addParams(summarizeParams);\n    } else if (summarize) {\n      args.add(SUMMARIZE);\n    }\n\n    if (highlightParams != null) {\n      args.addParams(highlightParams);\n    } else if (highlight) {\n      args.add(HIGHLIGHT);\n    }\n\n    if (slop != null) {\n      args.add(SLOP).add(slop);\n    }\n\n    if (timeout != null) {\n      args.add(TIMEOUT).add(timeout);\n    }\n\n    if (inOrder) {\n      args.add(INORDER);\n    }\n\n    if (language != null) {\n      args.add(LANGUAGE).add(language);\n    }\n\n    if (expander != null) {\n      args.add(EXPANDER).add(expander);\n    }\n\n    if (scorer != null) {\n      args.add(SCORER).add(scorer);\n    }\n//\n//    if (explainScore) {\n//      args.add(EXPLAINSCORE);\n//    }\n\n    if (sortBy != null) {\n      args.add(SORTBY).add(sortBy);\n      if (sortOrder != null) {\n        args.add(sortOrder);\n      }\n    }\n\n    if (limit != null) {\n      args.add(LIMIT).add(limit[0]).add(limit[1]);\n    }\n\n    if (params != null && !params.isEmpty()) {\n      args.add(PARAMS).add(params.size() << 1);\n      params.entrySet().forEach(entry -> args.add(entry.getKey()).add(entry.getValue()));\n    }\n\n    if (dialect != null) {\n      args.add(DIALECT).add(dialect);\n    }\n  }\n\n  /**\n   * Set the query not to return the contents of documents, and rather just return the ids\n   *\n   * @return the query itself\n   */\n  public FTSearchParams noContent() {\n    this.noContent = true;\n    return this;\n  }\n\n  /**\n   * Set the query to verbatim mode, disabling stemming and query expansion\n   *\n   * @return the query object\n   */\n  public FTSearchParams verbatim() {\n    this.verbatim = true;\n    return this;\n  }\n\n  /**\n   * Set the query not to filter for stopwords. In general this should not be used\n   *\n   * @return the query object\n   */\n  public FTSearchParams noStopwords() {\n    this.noStopwords = true;\n    return this;\n  }\n\n  /**\n   * Set the query to return a factored score for each results. This is useful to merge results from\n   * multiple queries.\n   *\n   * @return the query object itself\n   */\n  public FTSearchParams withScores() {\n    this.withScores = true;\n    return this;\n  }\n\n  public FTSearchParams filter(String field, double min, double max) {\n    return filter(new NumericFilter(field, min, max));\n  }\n\n  public FTSearchParams filter(String field, double min, boolean exclusiveMin, double max, boolean exclusiveMax) {\n    return filter(new NumericFilter(field, min, exclusiveMin, max, exclusiveMax));\n  }\n\n  public FTSearchParams filter(NumericFilter numericFilter) {\n    filters.add(numericFilter);\n    return this;\n  }\n\n  public FTSearchParams geoFilter(String field, double lon, double lat, double radius, GeoUnit unit) {\n    return geoFilter(new GeoFilter(field, lon, lat, radius, unit));\n  }\n\n  public FTSearchParams geoFilter(GeoFilter geoFilter) {\n    filters.add(geoFilter);\n    return this;\n  }\n\n  /**\n   * Limit the query to results that are limited to a specific set of keys\n   *\n   * @param keys a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public FTSearchParams inKeys(String... keys) {\n    return inKeys(Arrays.asList(keys));\n  }\n\n  public FTSearchParams inKeys(Collection<String> keys) {\n    this.inKeys = keys;\n    return this;\n  }\n\n  /**\n   * Limit the query to results that are limited to a specific set of fields\n   *\n   * @param fields a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public FTSearchParams inFields(String... fields) {\n    return inFields(Arrays.asList(fields));\n  }\n\n  public FTSearchParams inFields(Collection<String> fields) {\n    if (this.inFields == null) {\n      this.inFields = new ArrayList<>(fields);\n    } else {\n      this.inFields.addAll(fields);\n    }\n    return this;\n  }\n\n  /**\n   * Result's projection - the fields to return by the query\n   *\n   * @param fields a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public FTSearchParams returnFields(String... fields) {\n    if (returnFieldsNames == null) {\n      returnFieldsNames = new ArrayList<>();\n    }\n    Arrays.stream(fields).forEach(f -> returnFieldsNames.add(FieldName.of(f)));\n    return this;\n  }\n\n  public FTSearchParams returnField(FieldName field) {\n    return returnFields(Collections.singleton(field));\n  }\n\n  public FTSearchParams returnFields(FieldName... fields) {\n    return returnFields(Arrays.asList(fields));\n  }\n\n  public FTSearchParams returnFields(Collection<FieldName> fields) {\n    if (returnFieldsNames == null) {\n      returnFieldsNames = new ArrayList<>();\n    }\n    returnFieldsNames.addAll(fields);\n    return this;\n  }\n\n  public FTSearchParams returnField(String field, boolean decode) {\n    returnFields(field);\n    addReturnFieldDecode(field, decode);\n    return this;\n  }\n\n  public FTSearchParams returnField(FieldName field, boolean decode) {\n    returnFields(field);\n    addReturnFieldDecode(field.getAttribute() != null ? field.getAttribute() : field.getName(), decode);\n    return this;\n  }\n\n  private void addReturnFieldDecode(String returnName, boolean decode) {\n    if (returnFieldDecodeMap == null) {\n      returnFieldDecodeMap = new HashMap<>();\n    }\n    returnFieldDecodeMap.put(returnName, decode);\n  }\n\n  public FTSearchParams summarize() {\n    this.summarize = true;\n    return this;\n  }\n\n  public FTSearchParams summarize(SummarizeParams summarizeParams) {\n    this.summarizeParams = summarizeParams;\n    return this;\n  }\n\n  public FTSearchParams highlight() {\n    this.highlight = true;\n    return this;\n  }\n\n  public FTSearchParams highlight(HighlightParams highlightParams) {\n    this.highlightParams = highlightParams;\n    return this;\n  }\n\n  /**\n   * Set the query custom scorer\n   * <p>\n   * See http://redisearch.io for documentation on extending RediSearch\n   *\n   * @param scorer a custom scorer.\n   *\n   * @return the query object itself\n   */\n  public FTSearchParams scorer(String scorer) {\n    this.scorer = scorer;\n    return this;\n  }\n//\n//  public FTSearchParams explainScore() {\n//    this.explainScore = true;\n//    return this;\n//  }\n\n  public FTSearchParams slop(int slop) {\n    this.slop = slop;\n    return this;\n  }\n\n  public FTSearchParams timeout(long timeout) {\n    this.timeout = timeout;\n    return this;\n  }\n\n  public FTSearchParams inOrder() {\n    this.inOrder = true;\n    return this;\n  }\n\n  /**\n   * Set the query language, for stemming purposes\n   * <p>\n   * See http://redisearch.io for documentation on languages and stemming\n   *\n   * @param language a language.\n   *\n   * @return the query object itself\n   */\n  public FTSearchParams language(String language) {\n    this.language = language;\n    return this;\n  }\n\n  /**\n   * Set the query to be sorted by a Sortable field defined in the schema\n   *\n   * @param sortBy the sorting field's name\n   * @param order the sorting order\n   * @return the query object itself\n   */\n  public FTSearchParams sortBy(String sortBy, SortingOrder order) {\n    this.sortBy = sortBy;\n    this.sortOrder = order;\n    return this;\n  }\n\n  /**\n   * Limit the results to a certain offset and limit\n   *\n   * @param offset the first result to show, zero based indexing\n   * @param num how many results we want to show\n   * @return the query itself, for builder-style syntax\n   */\n  public FTSearchParams limit(int offset, int num) {\n    this.limit = new int[]{offset, num};\n    return this;\n  }\n\n  /**\n   * Parameters can be referenced in the query string by a $ , followed by the parameter name,\n   * e.g., $user , and each such reference in the search query to a parameter name is substituted\n   * by the corresponding parameter value.\n   *\n   * @param name\n   * @param value can be String, long or float\n   * @return the query object itself\n   */\n  public FTSearchParams addParam(String name, Object value) {\n    if (params == null) {\n      params = new HashMap<>();\n    }\n    params.put(name, value);\n    return this;\n  }\n\n  public FTSearchParams params(Map<String, Object> paramValues) {\n    if (this.params == null) {\n      this.params = new HashMap<>(paramValues);\n    } else {\n      this.params.putAll(params);\n    }\n    return this;\n  }\n\n  /**\n   * Set the dialect version to execute the query accordingly\n   *\n   * @param dialect integer\n   * @return the query object itself\n   */\n  public FTSearchParams dialect(int dialect) {\n    this.dialect = dialect;\n    return this;\n  }\n\n  /**\n   * This method will not replace the dialect if it has been already set.\n   * @param dialect dialect\n   * @return this\n   */\n  @Internal\n  public FTSearchParams dialectOptional(int dialect) {\n    if (dialect != 0 && this.dialect == null) {\n      this.dialect = dialect;\n    }\n    return this;\n  }\n\n  @Internal\n  public boolean getNoContent() {\n    return noContent;\n  }\n\n  @Internal\n  public boolean getWithScores() {\n    return withScores;\n  }\n\n  @Internal\n  public Map<String, Boolean> getReturnFieldDecodeMap() {\n    return returnFieldDecodeMap;\n  }\n\n  /**\n   * NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive\n   */\n  public static class NumericFilter implements IParams {\n\n    private final String field;\n    private final double min;\n    private final boolean exclusiveMin;\n    private final double max;\n    private final boolean exclusiveMax;\n\n    public NumericFilter(String field, double min, double max) {\n      this(field, min, false, max, false);\n    }\n\n    public NumericFilter(String field, double min, boolean exclusiveMin, double max, boolean exclusiveMax) {\n      this.field = field;\n      this.min = min;\n      this.max = max;\n      this.exclusiveMax = exclusiveMax;\n      this.exclusiveMin = exclusiveMin;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(FILTER).add(field)\n          .add(formatNum(min, exclusiveMin))\n          .add(formatNum(max, exclusiveMax));\n    }\n\n    private Object formatNum(double num, boolean exclude) {\n      return exclude ? (\"(\" + num) : Protocol.toByteArray(num);\n    }\n  }\n\n  /**\n   * GeoFilter encapsulates a radius filter on a geographical indexed fields\n   */\n  public static class GeoFilter implements IParams {\n\n    private final String field;\n    private final double lon;\n    private final double lat;\n    private final double radius;\n    private final GeoUnit unit;\n\n    public GeoFilter(String field, double lon, double lat, double radius, GeoUnit unit) {\n      this.field = field;\n      this.lon = lon;\n      this.lat = lat;\n      this.radius = radius;\n      this.unit = unit;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(GEOFILTER).add(field)\n          .add(lon).add(lat)\n          .add(radius).add(unit);\n    }\n  }\n\n  public static class SummarizeParams implements IParams {\n\n    private Collection<String> fields;\n    private Integer fragsNum;\n    private Integer fragSize;\n    private String separator;\n\n    public SummarizeParams() {\n    }\n\n    public SummarizeParams fields(String... fields) {\n      return fields(Arrays.asList(fields));\n    }\n\n    public SummarizeParams fields(Collection<String> fields) {\n      this.fields = fields;\n      return this;\n    }\n\n    public SummarizeParams fragsNum(int num) {\n      this.fragsNum = num;\n      return this;\n    }\n\n    public SummarizeParams fragSize(int size) {\n      this.fragSize = size;\n      return this;\n    }\n\n    public SummarizeParams separator(String separator) {\n      this.separator = separator;\n      return this;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(SUMMARIZE);\n\n      if (fields != null) {\n        args.add(FIELDS).add(fields.size()).addObjects(fields);\n      }\n      if (fragsNum != null) {\n        args.add(FRAGS).add(fragsNum);\n      }\n      if (fragSize != null) {\n        args.add(LEN).add(fragSize);\n      }\n      if (separator != null) {\n        args.add(SEPARATOR).add(separator);\n      }\n    }\n  }\n\n  public static SummarizeParams summarizeParams() {\n    return new SummarizeParams();\n  }\n\n  public static class HighlightParams implements IParams {\n\n    private Collection<String> fields;\n    private String[] tags;\n\n    public HighlightParams() {\n    }\n\n    public HighlightParams fields(String fields) {\n      return fields(Arrays.asList(fields));\n    }\n\n    public HighlightParams fields(Collection<String> fields) {\n      this.fields = fields;\n      return this;\n    }\n\n    public HighlightParams tags(String open, String close) {\n      this.tags = new String[]{open, close};\n      return this;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(HIGHLIGHT);\n\n      if (fields != null) {\n        args.add(FIELDS).add(fields.size()).addObjects(fields);\n      }\n      if (tags != null) {\n        args.add(TAGS).add(tags[0]).add(tags[1]);\n      }\n    }\n  }\n\n  public static HighlightParams highlightParams() {\n    return new HighlightParams();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/FTSpellCheckParams.java",
    "content": "package redis.clients.jedis.search;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.DIALECT;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.DISTANCE;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.EXCLUDE;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.INCLUDE;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.TERMS;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Map;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class FTSpellCheckParams implements IParams {\n\n  private Collection<Map.Entry<String, Rawable>> terms;\n  private Integer distance;\n  private Integer dialect;\n\n  public FTSpellCheckParams() {\n  }\n\n  public static FTSpellCheckParams spellCheckParams() {\n    return new FTSpellCheckParams();\n  }\n\n  /**\n   * Specifies an inclusion (INCLUDE) of a custom dictionary.\n   */\n  public FTSpellCheckParams includeTerm(String dictionary) {\n    return addTerm(dictionary, INCLUDE);\n  }\n\n  /**\n   * Specifies an exclusion (EXCLUDE) of a custom dictionary.\n   */\n  public FTSpellCheckParams excludeTerm(String dictionary) {\n    return addTerm(dictionary, EXCLUDE);\n  }\n\n  /**\n   * Specifies an inclusion (INCLUDE) or exclusion (EXCLUDE) of a custom dictionary.\n   */\n  private FTSpellCheckParams addTerm(String dictionary, Rawable type) {\n    if (this.terms == null) {\n      this.terms = new ArrayList<>();\n    }\n    this.terms.add(KeyValue.of(dictionary, type));\n    return this;\n  }\n\n  /**\n   * Maximum Levenshtein distance for spelling suggestions (default: 1, max: 4).\n   */\n  public FTSpellCheckParams distance(int distance) {\n    this.distance = distance;\n    return this;\n  }\n\n  /**\n   * Selects the dialect version under which to execute the query.\n   */\n  public FTSpellCheckParams dialect(int dialect) {\n    this.dialect = dialect;\n    return this;\n  }\n\n  /**\n   * This method will not replace the dialect if it has been already set.\n   * @param dialect dialect\n   * @return this\n   */\n  public FTSpellCheckParams dialectOptional(int dialect) {\n    if (dialect != 0 && this.dialect == null) {\n      this.dialect = dialect;\n    }\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (terms != null) {\n      terms.forEach(kv -> args.add(TERMS).add(kv.getValue()).add(kv.getKey()));\n    }\n\n    if (distance != null) {\n      args.add(DISTANCE).add(distance);\n    }\n\n    if (dialect != null) {\n      args.add(DIALECT).add(dialect);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/FieldName.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.List;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.SearchProtocol.SearchKeyword;\n\npublic class FieldName implements IParams {\n\n  private final String name;\n  private String attribute;\n\n  public FieldName(String name) {\n    this.name = name;\n  }\n\n  public FieldName(String name, String attribute) {\n    this.name = name;\n    this.attribute = attribute;\n  }\n\n  public FieldName as(String attribute) {\n    if (attribute == null) {\n      throw new IllegalArgumentException(\"Setting null as field attribute is not allowed.\");\n    }\n    if (this.attribute != null) {\n      throw new IllegalStateException(\"Attribute for this field is already set.\");\n    }\n    this.attribute = attribute;\n    return this;\n  }\n\n  public final String getName() {\n    return name;\n  }\n\n  public final String getAttribute() {\n    return attribute;\n  }\n\n  public int addCommandArguments(List<Object> args) {\n    args.add(name);\n    if (attribute == null) {\n      return 1;\n    }\n\n    args.add(SearchKeyword.AS);\n    args.add(attribute);\n    return 3;\n  }\n\n  public int addCommandArguments(CommandArguments args) {\n    args.add(name);\n    if (attribute == null) {\n      return 1;\n    }\n\n    args.add(SearchKeyword.AS);\n    args.add(attribute);\n    return 3;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    addCommandArguments(args);\n  }\n\n  @Override\n  public String toString() {\n    return attribute == null ? name : (name + \" AS \" + attribute);\n  }\n\n  public static FieldName of(String name) {\n    return new FieldName(name);\n  }\n\n  public static FieldName[] convert(String... names) {\n    if (names == null) {\n      return null;\n    }\n    FieldName[] fields = new FieldName[names.length];\n    for (int i = 0; i < names.length; i++) {\n      fields[i] = FieldName.of(names[i]);\n    }\n    return fields;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Filter.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.util.JedisAsserts;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.FILTER;\n\n/**\n * FILTER operation for search commands. Filters results based on an expression.\n */\n@Experimental\npublic class Filter implements IParams {\n\n  private final String expression;\n\n  private Filter(String expression) {\n    this.expression = expression;\n  }\n\n  /**\n   * Create a FILTER operation.\n   * @param expression the filter expression\n   * @return a new Filter instance\n   */\n  public static Filter of(String expression) {\n    JedisAsserts.notNull(expression, \"Filter expression must not be null\");\n\n    return new Filter(expression);\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(FILTER);\n    args.add(expression);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/FtSearchIteration.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.Collection;\nimport java.util.function.IntFunction;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.search.SearchResult.SearchResultBuilder;\nimport redis.clients.jedis.util.JedisCommandIterationBase;\n\npublic class FtSearchIteration extends JedisCommandIterationBase<SearchResult, Document> {\n\n  private int batchStart;\n  private final int batchSize;\n  private final IntFunction<CommandArguments> args;\n\n  /**\n   * {@link FTSearchParams#limit(int, int)} will be ignored.\n   */\n  public FtSearchIteration(ConnectionProvider connectionProvider, int batchSize, String indexName, String query, FTSearchParams params) {\n    this(connectionProvider, null, batchSize, indexName, query, params);\n  }\n\n  /**\n   * {@link Query#limit(java.lang.Integer, java.lang.Integer)} will be ignored.\n   */\n  public FtSearchIteration(ConnectionProvider connectionProvider, int batchSize, String indexName, Query query) {\n    this(connectionProvider, null, batchSize, indexName, query);\n  }\n\n  /**\n   * {@link FTSearchParams#limit(int, int)} will be ignored.\n   */\n  public FtSearchIteration(ConnectionProvider connectionProvider, RedisProtocol protocol, int batchSize, String indexName, String query, FTSearchParams params) {\n    super(connectionProvider, protocol == RedisProtocol.RESP3 ? SearchResult.SEARCH_RESULT_BUILDER\n        : new SearchResultBuilder(!params.getNoContent(), params.getWithScores(), true));\n    this.batchSize = batchSize;\n    this.args = (limitFirst) -> new CommandArguments(SearchProtocol.SearchCommand.SEARCH)\n        .add(indexName).add(query).addParams(params.limit(limitFirst, this.batchSize));\n  }\n\n  /**\n   * {@link Query#limit(java.lang.Integer, java.lang.Integer)} will be ignored.\n   */\n  public FtSearchIteration(ConnectionProvider connectionProvider, RedisProtocol protocol, int batchSize, String indexName, Query query) {\n    super(connectionProvider, protocol == RedisProtocol.RESP3 ? SearchResult.SEARCH_RESULT_BUILDER\n        : new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true));\n    this.batchSize = batchSize;\n    this.args = (limitFirst) -> new CommandArguments(SearchProtocol.SearchCommand.SEARCH)\n        .add(indexName).addParams(query.limit(limitFirst, this.batchSize));\n  }\n\n  @Override\n  protected boolean isNodeCompleted(SearchResult reply) {\n    return batchStart >= reply.getTotalResults() - batchSize;\n  }\n\n  @Override\n  protected CommandArguments initCommandArguments() {\n    batchStart = 0;\n    return args.apply(batchStart);\n  }\n\n  @Override\n  protected CommandArguments nextCommandArguments(SearchResult lastReply) {\n    batchStart += batchSize;\n    return args.apply(batchStart);\n  }\n\n  @Override\n  protected Collection<Document> convertBatchToData(SearchResult batch) {\n    return batch.getDocuments();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/IndexDataType.java",
    "content": "package redis.clients.jedis.search;\n\npublic enum IndexDataType {\n  HASH,\n  JSON\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/IndexDefinition.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.SearchProtocol.SearchKeyword;\n\n/**\n * IndexDefinition encapsulates configuration for index definition creation and should be given to\n * the client on index creation\n */\npublic class IndexDefinition implements IParams {\n\n  public enum Type {\n    HASH,\n    JSON\n  }\n\n  private final Type type;\n  private String[] prefixes;\n  private String filter;\n  private String languageField;\n  private String language;\n  private String scoreFiled;\n  private double score = 1.0; // Default score when score isn't defined\n\n  public IndexDefinition() {\n    this(null);\n  }\n\n  public IndexDefinition(Type type) {\n    this.type = type;\n  }\n\n  public Type getType() {\n    return type;\n  }\n\n  public String[] getPrefixes() {\n    return prefixes;\n  }\n\n  public IndexDefinition setPrefixes(String... prefixes) {\n    this.prefixes = prefixes;\n    return this;\n  }\n\n  public String getFilter() {\n    return filter;\n  }\n\n  public IndexDefinition setFilter(String filter) {\n    this.filter = filter;\n    return this;\n  }\n\n  public String getLanguageField() {\n    return languageField;\n  }\n\n  public IndexDefinition setLanguageField(String languageField) {\n    this.languageField = languageField;\n    return this;\n  }\n\n  public String getLanguage() {\n    return language;\n  }\n\n  public IndexDefinition setLanguage(String language) {\n    this.language = language;\n    return this;\n  }\n\n  public String getScoreFiled() {\n    return scoreFiled;\n  }\n\n  public IndexDefinition setScoreFiled(String scoreFiled) {\n    this.scoreFiled = scoreFiled;\n    return this;\n  }\n\n  public double getScore() {\n    return score;\n  }\n\n  public IndexDefinition setScore(double score) {\n    this.score = score;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (type != null) {\n      args.add(SearchKeyword.ON.name());\n      args.add(type.name());\n    }\n\n    if (prefixes != null && prefixes.length > 0) {\n      args.add(SearchKeyword.PREFIX.name());\n      args.add(Integer.toString(prefixes.length));\n      args.addObjects((Object[]) prefixes);\n    }\n\n    if (filter != null) {\n      args.add(SearchKeyword.FILTER.name());\n      args.add(filter);\n    }\n\n    if (languageField != null) {\n      args.add(SearchKeyword.LANGUAGE_FIELD.name());\n      args.add(languageField);\n    }\n\n    if (language != null) {\n      args.add(SearchKeyword.LANGUAGE.name());\n      args.add(language);\n    }\n\n    if (scoreFiled != null) {\n      args.add(SearchKeyword.SCORE_FIELD.name());\n      args.add(scoreFiled);\n    }\n\n    if (score != 1.0) {\n      args.add(SearchKeyword.SCORE.name());\n      args.add(Double.toString(score));\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/IndexOptions.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.SearchProtocol.SearchKeyword;\n\n/**\n * IndexOptions encapsulates flags for index creation and should be given to the client on index\n * creation\n *\n * @since 2.0\n */\npublic class IndexOptions implements IParams {\n\n  /**\n   * Set this to tell the index not to save term offset vectors. This reduces memory consumption but\n   * does not allow performing exact matches, and reduces overall relevance of multi-term queries\n   */\n  public static final int USE_TERM_OFFSETS = 0x01;\n\n  /**\n   * If set (default), we keep flags per index record telling us what fields the term appeared on,\n   * and allowing us to filter results by field\n   */\n  public static final int KEEP_FIELD_FLAGS = 0x02;\n\n  /**\n   * With each document:term record, store how often the term appears within the document. This can\n   * be used for sorting documents by their relevance to the given term.\n   */\n  public static final int KEEP_TERM_FREQUENCIES = 0x08;\n\n  public static final int DEFAULT_FLAGS = USE_TERM_OFFSETS | KEEP_FIELD_FLAGS | KEEP_TERM_FREQUENCIES;\n\n  private final int flags;\n  private List<String> stopwords;\n  private long expire = 0L;\n  private IndexDefinition definition;\n\n  /**\n   * Default constructor\n   *\n   * @param flags flag mask\n   */\n  public IndexOptions(int flags) {\n    this.flags = flags;\n  }\n\n  /**\n   * The default indexing options - use term offsets and keep fields flags\n   */\n  public static IndexOptions defaultOptions() {\n    return new IndexOptions(DEFAULT_FLAGS);\n  }\n\n  /**\n   * Set a custom stopword list\n   *\n   * @param stopwords the list of stopwords\n   * @return the options object itself, for builder-style construction\n   */\n  public IndexOptions setStopwords(String... stopwords) {\n    this.stopwords = Arrays.asList(stopwords);\n    return this;\n  }\n\n  /**\n   * Set the index to contain no stopwords, overriding the default list\n   *\n   * @return the options object itself, for builder-style constructions\n   */\n  public IndexOptions setNoStopwords() {\n    stopwords = new ArrayList<>(0);\n    return this;\n  }\n\n  /**\n   * Temporary\n   *\n   * @param expire\n   * @return IndexOptions\n   */\n  public IndexOptions setTemporary(long expire) {\n    this.expire = expire;\n    return this;\n  }\n\n  public IndexDefinition getDefinition() {\n    return definition;\n  }\n\n  public IndexOptions setDefinition(IndexDefinition definition) {\n    this.definition = definition;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (definition != null) {\n      definition.addParams(args);\n    }\n\n    if ((flags & USE_TERM_OFFSETS) == 0) {\n      args.add(SearchKeyword.NOOFFSETS.name());\n    }\n    if ((flags & KEEP_FIELD_FLAGS) == 0) {\n      args.add(SearchKeyword.NOFIELDS.name());\n    }\n    if ((flags & KEEP_TERM_FREQUENCIES) == 0) {\n      args.add(SearchKeyword.NOFREQS.name());\n    }\n    if (expire > 0) {\n      args.add(SearchKeyword.TEMPORARY.name());\n      args.add(Long.toString(this.expire));\n    }\n\n    if (stopwords != null) {\n      args.add(SearchKeyword.STOPWORDS.name());\n      args.add(Integer.toString(stopwords.size()));\n      if (!stopwords.isEmpty()) {\n        args.addObjects(stopwords);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Limit.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.LIMIT;\n\n/**\n * LIMIT operation for search commands. Limits the number of results returned.\n */\n@Experimental\npublic class Limit implements IParams {\n\n  private final int offset;\n  private final int count;\n\n  private Limit(int offset, int count) {\n    this.offset = offset;\n    this.count = count;\n  }\n\n  /**\n   * Create a LIMIT operation.\n   * @param offset the offset\n   * @param count the count\n   * @return a new Limit instance\n   */\n  public static Limit of(int offset, int count) {\n    return new Limit(offset, count);\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(LIMIT);\n    args.add(offset);\n    args.add(count);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/ProfilingInfo.java",
    "content": "package redis.clients.jedis.search;\n\nimport static redis.clients.jedis.BuilderFactory.AGGRESSIVE_ENCODED_OBJECT;\n\nimport redis.clients.jedis.Builder;\n\npublic class ProfilingInfo {\n\n  private final Object profilingInfo;\n\n  private ProfilingInfo(Object profilingInfo) {\n    this.profilingInfo = profilingInfo;\n  }\n\n  public Object getProfilingInfo() {\n    return profilingInfo;\n  }\n\n  @Override\n  public String toString() {\n    return String.valueOf(profilingInfo);\n  }\n\n  public static final Builder<ProfilingInfo> PROFILING_INFO_BUILDER\n      = new Builder<ProfilingInfo>() {\n    @Override\n    public ProfilingInfo build(Object data) {\n      return new ProfilingInfo(AGGRESSIVE_ENCODED_OBJECT.build(data));\n    }\n  };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Query.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.SearchProtocol.SearchKeyword;\nimport redis.clients.jedis.util.LazyRawable;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Query represents query parameters and filters to load results from the engine\n */\npublic class Query implements IParams {\n\n  /**\n   * Filter represents a filtering rules in a query\n   */\n  public abstract static class Filter implements IParams {\n\n    public final String property;\n\n    public Filter(String property) {\n      this.property = property;\n    }\n  }\n\n  /**\n   * NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive\n   */\n  public static class NumericFilter extends Filter {\n\n    private final double min;\n    private final boolean exclusiveMin;\n    private final double max;\n    private final boolean exclusiveMax;\n\n    public NumericFilter(String property, double min, boolean exclusiveMin, double max, boolean exclusiveMax) {\n      super(property);\n      this.min = min;\n      this.max = max;\n      this.exclusiveMax = exclusiveMax;\n      this.exclusiveMin = exclusiveMin;\n    }\n\n    public NumericFilter(String property, double min, double max) {\n      this(property, min, false, max, false);\n    }\n\n    private byte[] formatNum(double num, boolean exclude) {\n      return exclude ? SafeEncoder.encode(\"(\" + num) : Protocol.toByteArray(num);\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(SearchKeyword.FILTER.getRaw());\n      args.add(SafeEncoder.encode(property));\n      args.add(formatNum(min, exclusiveMin));\n      args.add(formatNum(max, exclusiveMax));\n    }\n  }\n\n  /**\n   * GeoFilter encapsulates a radius filter on a geographical indexed fields\n   */\n  public static class GeoFilter extends Filter {\n\n    public static final String KILOMETERS = \"km\";\n    public static final String METERS = \"m\";\n    public static final String FEET = \"ft\";\n    public static final String MILES = \"mi\";\n\n    private final double lon;\n    private final double lat;\n    private final double radius;\n    private final String unit;\n\n    public GeoFilter(String property, double lon, double lat, double radius, String unit) {\n      super(property);\n      this.lon = lon;\n      this.lat = lat;\n      this.radius = radius;\n      this.unit = unit;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(SearchKeyword.GEOFILTER.getRaw());\n      args.add(SafeEncoder.encode(property));\n      args.add(Protocol.toByteArray(lon));\n      args.add(Protocol.toByteArray(lat));\n      args.add(Protocol.toByteArray(radius));\n      args.add(SafeEncoder.encode(unit));\n    }\n  }\n\n  public static class Paging {\n\n    int offset;\n    int num;\n\n    public Paging(int offset, int num) {\n      this.offset = offset;\n      this.num = num;\n    }\n  }\n\n  public static class HighlightTags {\n\n    private final String open;\n    private final String close;\n\n    public HighlightTags(String open, String close) {\n      this.open = open;\n      this.close = close;\n    }\n  }\n\n  /**\n   * The query's filter list. We only support AND operation on all those filters\n   */\n  private final List<Filter> _filters = new LinkedList<>();\n\n  /**\n   * The textual part of the query\n   */\n  private final String _queryString;\n\n  /**\n   * The sorting parameters\n   */\n  private final Paging _paging = new Paging(0, 10);\n\n  private boolean _verbatim = false;\n  private boolean _noContent = false;\n  private boolean _noStopwords = false;\n  private boolean _withScores = false;\n  private String _language = null;\n  private String[] _fields = null;\n  private String[] _keys = null;\n  private String[] _returnFields = null;\n  private FieldName[] returnFieldNames = null;\n  private String[] highlightFields = null;\n  private String[] summarizeFields = null;\n  private String[] highlightTags = null;\n  private String summarizeSeparator = null;\n  private int summarizeNumFragments = -1;\n  private int summarizeFragmentLen = -1;\n  private String _sortBy = null;\n  private boolean _sortAsc = true;\n  private boolean wantsHighlight = false;\n  private boolean wantsSummarize = false;\n  private String _scorer = null;\n  private Map<String, Object> _params = null;\n  private Integer _dialect;\n  private int _slop = -1;\n  private long _timeout = -1;\n  private boolean _inOrder = false;\n  private String _expander = null;\n\n  public Query() {\n    this(\"*\");\n  }\n\n  /**\n   * Create a new index\n   *\n   * @param queryString the textual part of the query\n   */\n  public Query(String queryString) {\n    _queryString = queryString;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(SafeEncoder.encode(_queryString));\n\n    if (_verbatim) {\n      args.add(SearchKeyword.VERBATIM.getRaw());\n    }\n    if (_noContent) {\n      args.add(SearchKeyword.NOCONTENT.getRaw());\n    }\n    if (_noStopwords) {\n      args.add(SearchKeyword.NOSTOPWORDS.getRaw());\n    }\n    if (_withScores) {\n      args.add(SearchKeyword.WITHSCORES.getRaw());\n    }\n    if (_language != null) {\n      args.add(SearchKeyword.LANGUAGE.getRaw());\n      args.add(SafeEncoder.encode(_language));\n    }\n\n    if (_scorer != null) {\n      args.add(SearchKeyword.SCORER.getRaw());\n      args.add(SafeEncoder.encode(_scorer));\n    }\n\n    if (_fields != null && _fields.length > 0) {\n      args.add(SearchKeyword.INFIELDS.getRaw());\n      args.add(Protocol.toByteArray(_fields.length));\n      for (String f : _fields) {\n        args.add(SafeEncoder.encode(f));\n      }\n    }\n\n    if (_sortBy != null) {\n      args.add(SearchKeyword.SORTBY.getRaw());\n      args.add(SafeEncoder.encode(_sortBy));\n      args.add((_sortAsc ? SearchKeyword.ASC : SearchKeyword.DESC).getRaw());\n    }\n\n    if (_paging.offset != 0 || _paging.num != 10) {\n      args.add(SearchKeyword.LIMIT.getRaw()).add(Protocol.toByteArray(_paging.offset)).add(Protocol.toByteArray(_paging.num));\n    }\n\n    if (!_filters.isEmpty()) {\n      _filters.forEach(filter -> filter.addParams(args));\n    }\n\n    if (wantsHighlight) {\n      args.add(SearchKeyword.HIGHLIGHT.getRaw());\n      if (highlightFields != null) {\n        args.add(SearchKeyword.FIELDS.getRaw());\n        args.add(Protocol.toByteArray(highlightFields.length));\n        for (String s : highlightFields) {\n          args.add(SafeEncoder.encode(s));\n        }\n      }\n      if (highlightTags != null) {\n        args.add(SearchKeyword.TAGS.getRaw());\n        for (String t : highlightTags) {\n          args.add(SafeEncoder.encode(t));\n        }\n      }\n    }\n    if (wantsSummarize) {\n      args.add(SearchKeyword.SUMMARIZE.getRaw());\n      if (summarizeFields != null) {\n        args.add(SearchKeyword.FIELDS.getRaw());\n        args.add(Protocol.toByteArray(summarizeFields.length));\n        for (String s : summarizeFields) {\n          args.add(SafeEncoder.encode(s));\n        }\n      }\n      if (summarizeNumFragments != -1) {\n        args.add(SearchKeyword.FRAGS.getRaw());\n        args.add(Protocol.toByteArray(summarizeNumFragments));\n      }\n      if (summarizeFragmentLen != -1) {\n        args.add(SearchKeyword.LEN.getRaw());\n        args.add(Protocol.toByteArray(summarizeFragmentLen));\n      }\n      if (summarizeSeparator != null) {\n        args.add(SearchKeyword.SEPARATOR.getRaw());\n        args.add(SafeEncoder.encode(summarizeSeparator));\n      }\n    }\n\n    if (_keys != null && _keys.length > 0) {\n      args.add(SearchKeyword.INKEYS.getRaw());\n      args.add(Protocol.toByteArray(_keys.length));\n      for (String f : _keys) {\n        args.add(SafeEncoder.encode(f));\n      }\n    }\n\n    if (_returnFields != null && _returnFields.length > 0) {\n      args.add(SearchKeyword.RETURN.getRaw());\n      args.add(Protocol.toByteArray(_returnFields.length));\n      for (String f : _returnFields) {\n        args.add(SafeEncoder.encode(f));\n      }\n    } else if (returnFieldNames != null && returnFieldNames.length > 0) {\n      args.add(SearchKeyword.RETURN.getRaw());\n//      final int returnCountIndex = args.size();\n      LazyRawable returnCountObject = new LazyRawable();\n//      args.add(null); // holding a place for setting the total count later.\n      args.add(returnCountObject); // holding a place for setting the total count later.\n      int returnCount = 0;\n      for (FieldName fn : returnFieldNames) {\n        returnCount += fn.addCommandArguments(args);\n      }\n//      args.set(returnCountIndex, Protocol.toByteArray(returnCount));\n      returnCountObject.setRaw(Protocol.toByteArray(returnCount));\n    }\n\n    if (_params != null && _params.size() > 0) {\n      args.add(SearchKeyword.PARAMS.getRaw());\n      args.add(_params.size() << 1);\n      for (Map.Entry<String, Object> entry : _params.entrySet()) {\n        args.add(entry.getKey());\n        args.add(entry.getValue());\n      }\n    }\n\n    if (_dialect != null) {\n      args.add(SearchKeyword.DIALECT.getRaw());\n      args.add(_dialect);\n    }\n\n    if (_slop >= 0) {\n      args.add(SearchKeyword.SLOP.getRaw());\n      args.add(_slop);\n    }\n\n    if (_timeout >= 0) {\n      args.add(SearchKeyword.TIMEOUT.getRaw());\n      args.add(_timeout);\n    }\n\n    if (_inOrder) {\n      args.add(SearchKeyword.INORDER.getRaw());\n    }\n\n    if (_expander != null) {\n      args.add(SearchKeyword.EXPANDER.getRaw());\n      args.add(SafeEncoder.encode(_expander));\n    }\n  }\n\n  /**\n   * Limit the results to a certain offset and limit\n   *\n   * @param offset the first result to show, zero based indexing\n   * @param limit how many results we want to show\n   * @return the query itself, for builder-style syntax\n   */\n  public Query limit(Integer offset, Integer limit) {\n    _paging.offset = offset;\n    _paging.num = limit;\n    return this;\n  }\n\n  /**\n   * Add a filter to the query's filter list\n   *\n   * @param f either a numeric or geo filter object\n   * @return the query itself\n   */\n  public Query addFilter(Filter f) {\n    _filters.add(f);\n    return this;\n  }\n\n  /**\n   * Set the query to verbatim mode, disabling stemming and query expansion\n   *\n   * @return the query object\n   */\n  public Query setVerbatim() {\n    this._verbatim = true;\n    return this;\n  }\n\n  public boolean getNoContent() {\n    return _noContent;\n  }\n\n  /**\n   * Set the query not to return the contents of documents, and rather just return the ids\n   *\n   * @return the query itself\n   */\n  public Query setNoContent() {\n    this._noContent = true;\n    return this;\n  }\n\n  /**\n   * Set the query not to filter for stopwords. In general this should not be used\n   *\n   * @return the query object\n   */\n  public Query setNoStopwords() {\n    this._noStopwords = true;\n    return this;\n  }\n\n  public boolean getWithScores() {\n    return _withScores;\n  }\n\n  /**\n   * Set the query to return a factored score for each results. This is useful to merge results from\n   * multiple queries.\n   *\n   * @return the query object itself\n   */\n  public Query setWithScores() {\n    this._withScores = true;\n    return this;\n  }\n\n  /**\n   * Set the query language, for stemming purposes\n   * <p>\n   * See http://redisearch.io for documentation on languages and stemming\n   *\n   * @param language a language.\n   *\n   * @return the query object itself\n   */\n  public Query setLanguage(String language) {\n    this._language = language;\n    return this;\n  }\n\n  /**\n   * Set the query custom scorer\n   * <p>\n   * See http://redisearch.io for documentation on extending RediSearch\n   *\n   * @param scorer a custom scorer.\n   *\n   * @return the query object itself\n   */\n  public Query setScorer(String scorer) {\n    this._scorer = scorer;\n    return this;\n  }\n\n  /**\n   * Limit the query to results that are limited to a specific set of fields\n   *\n   * @param fields a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public Query limitFields(String... fields) {\n    this._fields = fields;\n    return this;\n  }\n\n  /**\n   * Limit the query to results that are limited to a specific set of keys\n   *\n   * @param keys a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public Query limitKeys(String... keys) {\n    this._keys = keys;\n    return this;\n  }\n\n  /**\n   * Result's projection - the fields to return by the query\n   *\n   * @param fields a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public Query returnFields(String... fields) {\n    this._returnFields = fields;\n    this.returnFieldNames = null;\n    return this;\n  }\n\n  /**\n   * Result's projection - the fields to return by the query\n   *\n   * @param fields a list of TEXT fields in the schemas\n   * @return the query object itself\n   */\n  public Query returnFields(FieldName... fields) {\n    this.returnFieldNames = fields;\n    this._returnFields = null;\n    return this;\n  }\n\n  public Query highlightFields(HighlightTags tags, String... fields) {\n    if (fields == null || fields.length > 0) {\n      highlightFields = fields;\n    }\n    if (tags != null) {\n      highlightTags = new String[]{tags.open, tags.close};\n    } else {\n      highlightTags = null;\n    }\n    wantsHighlight = true;\n    return this;\n  }\n\n  public Query highlightFields(String... fields) {\n    return highlightFields(null, fields);\n  }\n\n  public Query summarizeFields(int contextLen, int fragmentCount, String separator, String... fields) {\n    if (fields == null || fields.length > 0) {\n      summarizeFields = fields;\n    }\n    summarizeFragmentLen = contextLen;\n    summarizeNumFragments = fragmentCount;\n    summarizeSeparator = separator;\n    wantsSummarize = true;\n    return this;\n  }\n\n  public Query summarizeFields(String... fields) {\n    return summarizeFields(-1, -1, null, fields);\n  }\n\n  /**\n   * Set the query to be sorted by a Sortable field defined in the schema\n   *\n   * @param field the sorting field's name\n   * @param ascending if set to true, the sorting order is ascending, else descending\n   * @return the query object itself\n   */\n  public Query setSortBy(String field, boolean ascending) {\n    _sortBy = field;\n    _sortAsc = ascending;\n    return this;\n  }\n\n  /**\n   * Parameters can be referenced in the query string by a $ , followed by the parameter name,\n   * e.g., $user , and each such reference in the search query to a parameter name is substituted\n   * by the corresponding parameter value.\n   *\n   * @param name\n   * @param value can be String, long or float\n   * @return the query object itself\n   */\n  public Query addParam(String name, Object value) {\n    if (_params == null) {\n      _params = new HashMap<>();\n    }\n    _params.put(name, value);\n    return this;\n  }\n\n  /**\n   * Set the dialect version to execute the query accordingly\n   *\n   * @param dialect integer\n   * @return the query object itself\n   */\n  public Query dialect(int dialect) {\n    _dialect = dialect;\n    return this;\n  }\n\n  /**\n   * This method will not replace the dialect if it has been already set.\n   * @param dialect dialect\n   * @return this\n   */\n  public Query dialectOptional(int dialect) {\n    if (dialect != 0 && this._dialect == null) {\n      this._dialect = dialect;\n    }\n    return this;\n  }\n\n  /**\n   * Set the slop to execute the query accordingly\n   *\n   * @param slop integer\n   * @return the query object itself\n   */\n  public Query slop(int slop) {\n    _slop = slop;\n    return this;\n  }\n\n  /**\n   * Set the timeout to execute the query accordingly\n   *\n   * @param timeout long\n   * @return the query object itself\n   */\n  public Query timeout(long timeout) {\n    _timeout = timeout;\n    return this;\n  }\n\n  /**\n   * Set the query terms appear in the same order in the document as in the query, regardless of the offsets between them\n   *\n   * @return the query object\n   */\n  public Query setInOrder() {\n    this._inOrder = true;\n    return this;\n  }\n\n  /**\n   * Set the query to use a custom query expander instead of the stemmer\n   *\n   * @param field the expander field's name\n   * @return the query object itself\n   */\n  public Query setExpander(String field) {\n    _expander = field;\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/RediSearchCommands.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.commands.ConfigCommands;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.hybrid.FTHybridParams;\nimport redis.clients.jedis.search.hybrid.HybridResult;\nimport redis.clients.jedis.search.schemafields.SchemaField;\n\npublic interface RediSearchCommands {\n\n  String ftCreate(String indexName, IndexOptions indexOptions, Schema schema);\n\n  default String ftCreate(String indexName, SchemaField... schemaFields) {\n    return ftCreate(indexName, Arrays.asList(schemaFields));\n  }\n\n  default String ftCreate(String indexName, FTCreateParams createParams, SchemaField... schemaFields) {\n    return ftCreate(indexName, createParams, Arrays.asList(schemaFields));\n  }\n\n  default String ftCreate(String indexName, Iterable<SchemaField> schemaFields) {\n    return ftCreate(indexName, FTCreateParams.createParams(), schemaFields);\n  }\n\n  String ftCreate(String indexName, FTCreateParams createParams, Iterable<SchemaField> schemaFields);\n\n  default String ftAlter(String indexName, Schema.Field... fields) {\n    return ftAlter(indexName, Schema.from(fields));\n  }\n\n  String ftAlter(String indexName, Schema schema);\n\n  default String ftAlter(String indexName, SchemaField... schemaFields) {\n    return ftAlter(indexName, Arrays.asList(schemaFields));\n  }\n\n  String ftAlter(String indexName, Iterable<SchemaField> schemaFields);\n\n  String ftAliasAdd(String aliasName, String indexName);\n\n  String ftAliasUpdate(String aliasName, String indexName);\n\n  String ftAliasDel(String aliasName);\n\n  String ftDropIndex(String indexName);\n\n  String ftDropIndexDD(String indexName);\n\n  default SearchResult ftSearch(String indexName) {\n    return ftSearch(indexName, \"*\");\n  }\n\n  SearchResult ftSearch(String indexName, String query);\n\n  SearchResult ftSearch(String indexName, String query, FTSearchParams params);\n\n  SearchResult ftSearch(String indexName, Query query);\n\n  @Deprecated\n  SearchResult ftSearch(byte[] indexName, Query query);\n\n  String ftExplain(String indexName, Query query);\n\n  List<String> ftExplainCLI(String indexName, Query query);\n\n  AggregationResult ftAggregate(String indexName, AggregationBuilder aggr);\n\n  AggregationResult ftCursorRead(String indexName, long cursorId, int count);\n\n  String ftCursorDel(String indexName, long cursorId);\n\n  Map.Entry<AggregationResult, ProfilingInfo> ftProfileAggregate(String indexName,\n      FTProfileParams profileParams, AggregationBuilder aggr);\n\n  Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,\n      FTProfileParams profileParams, Query query);\n\n  Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,\n      FTProfileParams profileParams, String query, FTSearchParams searchParams);\n\n  String ftSynUpdate(String indexName, String synonymGroupId, String... terms);\n\n  Map<String, List<String>> ftSynDump(String indexName);\n\n  long ftDictAdd(String dictionary, String... terms);\n\n  long ftDictDel(String dictionary, String... terms);\n\n  Set<String> ftDictDump(String dictionary);\n\n  long ftDictAddBySampleKey(String indexName, String dictionary, String... terms);\n\n  long ftDictDelBySampleKey(String indexName, String dictionary, String... terms);\n\n  Set<String> ftDictDumpBySampleKey(String indexName, String dictionary);\n\n  Map<String, Map<String, Double>> ftSpellCheck(String index, String query);\n\n  Map<String, Map<String, Double>> ftSpellCheck(String index, String query,\n      FTSpellCheckParams spellCheckParams);\n\n  Map<String, Object> ftInfo(String indexName);\n\n  Set<String> ftTagVals(String indexName, String fieldName);\n\n  /**\n   * @deprecated {@link ConfigCommands#configGet(java.lang.String)} is used since Redis 8.\n   */\n  @Deprecated\n  Map<String, Object> ftConfigGet(String option);\n\n  @Deprecated\n  Map<String, Object> ftConfigGet(String indexName, String option);\n\n  /**\n   * @deprecated {@link ConfigCommands#configSet(java.lang.String, java.lang.String)} is used since Redis 8.\n   */\n  @Deprecated\n  String ftConfigSet(String option, String value);\n\n  @Deprecated\n  String ftConfigSet(String indexName, String option, String value);\n\n  long ftSugAdd(String key, String string, double score);\n\n  long ftSugAddIncr(String key, String string, double score);\n\n  List<String> ftSugGet(String key, String prefix);\n\n  List<String> ftSugGet(String key, String prefix, boolean fuzzy, int max);\n\n  List<Tuple> ftSugGetWithScores(String key, String prefix);\n\n  List<Tuple> ftSugGetWithScores(String key, String prefix, boolean fuzzy, int max);\n\n  boolean ftSugDel(String key, String string);\n\n  long ftSugLen(String key);\n\n  Set<String> ftList();\n\n  /**\n   * Execute a hybrid query combining text search and vector similarity.\n   *\n   * @param indexName the index name\n   * @param hybridParams the hybrid query arguments\n   * @return the hybrid search results\n   * @see FTHybridParams\n   * @see HybridResult\n   */\n  @Experimental\n  HybridResult ftHybrid(String indexName, FTHybridParams hybridParams);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.hybrid.FTHybridParams;\nimport redis.clients.jedis.search.hybrid.HybridResult;\nimport redis.clients.jedis.search.schemafields.SchemaField;\n\npublic interface RediSearchPipelineCommands {\n\n  Response<String> ftCreate(String indexName, IndexOptions indexOptions, Schema schema);\n\n  default Response<String> ftCreate(String indexName, SchemaField... schemaFields) {\n    return ftCreate(indexName, Arrays.asList(schemaFields));\n  }\n\n  default Response<String> ftCreate(String indexName, FTCreateParams createParams, SchemaField... schemaFields) {\n    return ftCreate(indexName, createParams, Arrays.asList(schemaFields));\n  }\n\n  default Response<String> ftCreate(String indexName, Iterable<SchemaField> schemaFields) {\n    return ftCreate(indexName, FTCreateParams.createParams(), schemaFields);\n  }\n\n  Response<String> ftCreate(String indexName, FTCreateParams createParams, Iterable<SchemaField> schemaFields);\n\n  default Response<String> ftAlter(String indexName, Schema.Field... fields) {\n    return ftAlter(indexName, Schema.from(fields));\n  }\n\n  Response<String> ftAlter(String indexName, Schema schema);\n\n  default Response<String> ftAlter(String indexName, SchemaField... schemaFields) {\n    return ftAlter(indexName, Arrays.asList(schemaFields));\n  }\n\n  Response<String> ftAlter(String indexName, Iterable<SchemaField> schemaFields);\n\n  Response<String> ftAliasAdd(String aliasName, String indexName);\n\n  Response<String> ftAliasUpdate(String aliasName, String indexName);\n\n  Response<String> ftAliasDel(String aliasName);\n\n  Response<String> ftDropIndex(String indexName);\n\n  Response<String> ftDropIndexDD(String indexName);\n\n  default Response<SearchResult> ftSearch(String indexName) {\n    return ftSearch(indexName, \"*\");\n  }\n\n  Response<SearchResult> ftSearch(String indexName, String query);\n\n  Response<SearchResult> ftSearch(String indexName, String query, FTSearchParams searchParams);\n\n  Response<SearchResult> ftSearch(String indexName, Query query);\n\n  @Deprecated\n  Response<SearchResult> ftSearch(byte[] indexName, Query query);\n\n  Response<String> ftExplain(String indexName, Query query);\n\n  Response<List<String>> ftExplainCLI(String indexName, Query query);\n\n  Response<AggregationResult> ftAggregate(String indexName, AggregationBuilder aggr);\n\n  Response<String> ftSynUpdate(String indexName, String synonymGroupId, String... terms);\n\n  Response<Map<String, List<String>>> ftSynDump(String indexName);\n\n  Response<Long> ftDictAdd(String dictionary, String... terms);\n\n  Response<Long> ftDictDel(String dictionary, String... terms);\n\n  Response<Set<String>> ftDictDump(String dictionary);\n\n  Response<Long> ftDictAddBySampleKey(String indexName, String dictionary, String... terms);\n\n  Response<Long> ftDictDelBySampleKey(String indexName, String dictionary, String... terms);\n\n  Response<Set<String>> ftDictDumpBySampleKey(String indexName, String dictionary);\n\n  Response<Map<String, Map<String, Double>>> ftSpellCheck(String index, String query);\n\n  Response<Map<String, Map<String, Double>>> ftSpellCheck(String index, String query,\n      FTSpellCheckParams spellCheckParams);\n\n  Response<Map<String, Object>> ftInfo(String indexName);\n\n  Response<Set<String>> ftTagVals(String indexName, String fieldName);\n\n  @Deprecated\n  Response<Map<String, Object>> ftConfigGet(String option);\n\n  @Deprecated\n  Response<Map<String, Object>> ftConfigGet(String indexName, String option);\n\n  @Deprecated\n  Response<String> ftConfigSet(String option, String value);\n\n  @Deprecated\n  Response<String> ftConfigSet(String indexName, String option, String value);\n\n  Response<Long> ftSugAdd(String key, String string, double score);\n\n  Response<Long> ftSugAddIncr(String key, String string, double score);\n\n  Response<List<String>> ftSugGet(String key, String prefix);\n\n  Response<List<String>> ftSugGet(String key, String prefix, boolean fuzzy, int max);\n\n  Response<List<Tuple>> ftSugGetWithScores(String key, String prefix);\n\n  Response<List<Tuple>> ftSugGetWithScores(String key, String prefix, boolean fuzzy, int max);\n\n  Response<Boolean> ftSugDel(String key, String string);\n\n  Response<Long> ftSugLen(String key);\n\n  /**\n   * Execute a hybrid search combining text and vector search.\n   *\n   * @param indexName the index name\n   * @param hybridParams the hybrid search arguments\n   * @return the hybrid search results\n   */\n  @Experimental\n  Response<HybridResult> ftHybrid(String indexName, FTHybridParams hybridParams);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/RediSearchUtil.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class RediSearchUtil {\n\n  /**\n   * Jedis' {@code hset} methods do not support {@link Object}s as values. This method eases process\n   * of converting a {@link Map} with Objects as values so that the returning Map can be set to a\n   * {@code hset} method.\n   * @param input map with object value\n   * @return map with string value\n   */\n  public static Map<String, String> toStringMap(Map<String, Object> input) {\n    return toStringMap(input, false);\n  }\n\n  /**\n   * Jedis' {@code hset} methods do not support {@link Object}s as values. This method eases process\n   * of converting a {@link Map} with Objects as values so that the returning Map can be set to a\n   * {@code hset} method.\n   * @param input map with object value\n   * @param stringEscape whether to escape the String objects\n   * @return map with string value\n   */\n  public static Map<String, String> toStringMap(Map<String, Object> input, boolean stringEscape) {\n    Map<String, String> output = new HashMap<>(input.size());\n    for (Map.Entry<String, Object> entry : input.entrySet()) {\n      String key = entry.getKey();\n      Object obj = entry.getValue();\n      if (key == null || obj == null) {\n        throw new NullPointerException(\"A null argument cannot be sent to Redis.\");\n      }\n      String str;\n      if (obj instanceof byte[]) {\n        str = SafeEncoder.encode((byte[]) obj);\n      } else if (obj instanceof redis.clients.jedis.GeoCoordinate) {\n        redis.clients.jedis.GeoCoordinate geo = (redis.clients.jedis.GeoCoordinate) obj;\n        str = geo.getLongitude() + \",\" + geo.getLatitude();\n      } else if (obj instanceof String) {\n        str = stringEscape ? escape((String) obj) : (String) obj;\n      } else {\n        str = String.valueOf(obj);\n      }\n      output.put(key, str);\n    }\n    return output;\n  }\n\n  /**\n   * x86 systems are little-endian and Java defaults to big-endian. This causes mismatching query\n   * results when RediSearch is running in a x86 system. This method helps to convert concerned\n   * arrays.\n   * @param input float array\n   * @return byte array\n   */\n  public static byte[] toByteArray(float[] input) {\n    byte[] bytes = new byte[Float.BYTES * input.length];\n    ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(input);\n    return bytes;\n  }\n\n  /**\n   * @deprecated Use {@link RediSearchUtil#toByteArray(float[])}.\n   */\n  @Deprecated\n  public static byte[] ToByteArray(float[] input) {\n    return toByteArray(input);\n  }\n\n  private static final Set<Character> ESCAPE_CHARS = new HashSet<>(Arrays.asList(//\n      ',', '.', '<', '>', '{', '}', '[', //\n      ']', '\"', '\\'', ':', ';', '!', '@', //\n      '#', '$', '%', '^', '&', '*', '(', //\n      ')', '-', '+', '=', '~', '|' //\n  ));\n\n  public static String escape(String text) {\n    return escape(text, false);\n  }\n\n  public static String escapeQuery(String query) {\n    return escape(query, true);\n  }\n\n  public static String escape(String text, boolean querying) {\n    char[] chars = text.toCharArray();\n\n    StringBuilder sb = new StringBuilder();\n    for (char ch : chars) {\n      if (ESCAPE_CHARS.contains(ch)\n          || (querying && ch == ' ')) {\n        sb.append(\"\\\\\");\n      }\n      sb.append(ch);\n    }\n    return sb.toString();\n  }\n\n  public static String unescape(String text) {\n    return text.replace(\"\\\\\", \"\");\n  }\n\n  private RediSearchUtil() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Schema.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Schema abstracts the schema definition when creating an index. Documents can contain fields not\n * mentioned in the schema, but the index will only index pre-defined fields\n */\npublic class Schema {\n\n  public enum FieldType {\n    TAG,\n    TEXT,\n    GEO,\n    NUMERIC,\n    VECTOR\n  }\n\n  // public for CommandObjects\n  public final List<Field> fields;\n\n  public Schema() {\n    this.fields = new ArrayList<>();\n  }\n\n  public static Schema from(Field... fields) {\n    Schema schema = new Schema();\n    for (Field field : fields) {\n      schema.addField(field);\n    }\n    return schema;\n  }\n\n  /**\n   * Add a text field to the schema with a given weight\n   *\n   * @param name the field's name\n   * @param weight its weight, a positive floating point number\n   * @return the schema object\n   */\n  public Schema addTextField(String name, double weight) {\n    fields.add(new TextField(name, weight));\n    return this;\n  }\n\n  /**\n   * Add a text field that can be sorted on\n   *\n   * @param name the field's name\n   * @param weight its weight, a positive floating point number\n   * @return the schema object\n   */\n  public Schema addSortableTextField(String name, double weight) {\n    fields.add(new TextField(name, weight, true));\n    return this;\n  }\n\n  /**\n   * Add a geo filtering field to the schema.\n   *\n   * @param name the field's name\n   * @return the schema object\n   */\n  public Schema addGeoField(String name) {\n    fields.add(new Field(name, FieldType.GEO, false));\n    return this;\n  }\n\n  /**\n   * Add a numeric field to the schema\n   *\n   * @param name the fields's nam e\n   * @return the schema object\n   */\n  public Schema addNumericField(String name) {\n    fields.add(new Field(name, FieldType.NUMERIC, false));\n    return this;\n  }\n\n  /* Add a numeric field that can be sorted on */\n  public Schema addSortableNumericField(String name) {\n    fields.add(new Field(name, FieldType.NUMERIC, true));\n    return this;\n  }\n\n  public Schema addTagField(String name) {\n    fields.add(new TagField(name));\n    return this;\n  }\n\n  public Schema addTagField(String name, String separator) {\n    fields.add(new TagField(name, separator));\n    return this;\n  }\n\n  public Schema addTagField(String name, boolean caseSensitive) {\n    fields.add(new TagField(name, caseSensitive, false));\n    return this;\n  }\n\n  public Schema addTagField(String name, String separator, boolean caseSensitive) {\n    fields.add(new TagField(name, separator, caseSensitive, false));\n    return this;\n  }\n\n  public Schema addSortableTagField(String name, String separator) {\n    fields.add(new TagField(name, separator, true));\n    return this;\n  }\n\n  public Schema addSortableTagField(String name, boolean caseSensitive) {\n    fields.add(new TagField(name, caseSensitive, true));\n    return this;\n  }\n\n  public Schema addSortableTagField(String name, String separator, boolean caseSensitive) {\n    fields.add(new TagField(name, separator, caseSensitive, true));\n    return this;\n  }\n\n  public Schema addVectorField(String name, VectorField.VectorAlgo algorithm, Map<String, Object> attributes) {\n    fields.add(new VectorField(name, algorithm, attributes));\n    return this;\n  }\n\n  public Schema addFlatVectorField(String name, Map<String, Object> attributes) {\n    fields.add(new VectorField(name, VectorField.VectorAlgo.FLAT, attributes));\n    return this;\n  }\n\n  public Schema addHNSWVectorField(String name, Map<String, Object> attributes) {\n    fields.add(new VectorField(name, VectorField.VectorAlgo.HNSW, attributes));\n    return this;\n  }\n\n  /**\n   * Add a Vamana vector field to the schema using the SVS-VAMANA algorithm.\n   * This method provides a convenient way to add SVS-VAMANA vector fields.\n   *\n   * @param name the field's name\n   * @param attributes the SVS-Vamana algorithm configuration attributes\n   * @return the schema object\n   */\n  public Schema addSvsVamanaVectorField(String name, Map<String, Object> attributes) {\n    // Use the existing VectorField with SVS_VAMANA algorithm\n    Map<String, Object> vamanaAttributes = new java.util.HashMap<>(attributes);\n    fields.add(new VectorField(name, VectorField.VectorAlgo.SVS_VAMANA, vamanaAttributes));\n    return this;\n  }\n\n  public Schema addField(Field field) {\n    fields.add(field);\n    return this;\n  }\n\n  /***\n   * Chain as name to the last filed added to the schema\n   * @param attribute\n   */\n  // TODO: Not sure about this pattern. May consider removing later.\n  public Schema as(String attribute) {\n    fields.get(fields.size() - 1).as(attribute);\n    return this;\n  }\n\n  @Override\n  public String toString() {\n    return \"Schema{fields=\" + fields + \"}\";\n  }\n\n  public static class Field implements IParams {\n\n    protected final FieldName fieldName;\n    protected final FieldType type;\n    protected final boolean sortable;\n    protected final boolean noIndex;\n\n    public Field(String name, FieldType type) {\n      this(name, type, false, false);\n    }\n\n    public Field(String name, FieldType type, boolean sortable) {\n      this(name, type, sortable, false);\n    }\n\n    public Field(String name, FieldType type, boolean sortable, boolean noindex) {\n      this(FieldName.of(name), type, sortable, noindex);\n    }\n\n    public Field(FieldName name, FieldType type) {\n      this(name, type, false, false);\n    }\n\n    public Field(FieldName name, FieldType type, boolean sortable, boolean noIndex) {\n      this.fieldName = name;\n      this.type = type;\n      this.sortable = sortable;\n      this.noIndex = noIndex;\n    }\n\n    public void as(String attribute){\n      this.fieldName.as(attribute);\n    }\n\n    @Override\n    public final void addParams(CommandArguments args) {\n      this.fieldName.addParams(args);\n      args.add(type.name());\n      addTypeArgs(args);\n      if (sortable) {\n        args.add(\"SORTABLE\");\n      }\n      if (noIndex) {\n        args.add(\"NOINDEX\");\n      }\n    }\n\n    /**\n     * Subclasses should override this method.\n     *\n     * @param args\n     */\n    protected void addTypeArgs(CommandArguments args) { }\n\n    @Override\n    public String toString() {\n      return \"Field{name='\" + fieldName + \"', type=\" + type + \", sortable=\" + sortable + \", noindex=\" + noIndex + \"}\";\n    }\n  }\n\n  /**\n   * FullText field spec.\n   */\n  public static class TextField extends Field {\n\n    private final double weight;\n    private final boolean nostem;\n    private final String phonetic;\n\n    public TextField(String name) {\n      this(name, 1.0);\n    }\n\n    public TextField(FieldName name) {\n      this(name, 1.0, false, false, false, null);\n    }\n\n    public TextField(String name, double weight) {\n      this(name, weight, false);\n    }\n\n    public TextField(String name, double weight, boolean sortable) {\n      this(name, weight, sortable, false);\n    }\n\n    public TextField(String name, double weight, boolean sortable, boolean nostem) {\n      this(name, weight, sortable, nostem, false);\n    }\n\n    public TextField(String name, double weight, boolean sortable, boolean nostem, boolean noindex) {\n      this(name, weight, sortable, nostem, noindex, null);\n    }\n\n    public TextField(String name, double weight, boolean sortable, boolean nostem, boolean noindex, String phonetic) {\n      super(name, FieldType.TEXT, sortable, noindex);\n      this.weight = weight;\n      this.nostem = nostem;\n      this.phonetic = phonetic;\n    }\n\n    public TextField(FieldName name, double weight, boolean sortable, boolean nostem, boolean noindex, String phonetic) {\n      super(name, FieldType.TEXT, sortable, noindex);\n      this.weight = weight;\n      this.nostem = nostem;\n      this.phonetic = phonetic;\n    }\n\n    @Override\n    protected void addTypeArgs(CommandArguments args) {\n      if (weight != 1.0) {\n        args.add(\"WEIGHT\");\n        args.add(Double.toString(weight));\n      }\n      if (nostem) {\n        args.add(\"NOSTEM\");\n      }\n      if (phonetic != null) {\n        args.add(\"PHONETIC\");\n        args.add(this.phonetic);\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"TextField{name='\" + fieldName + \"', type=\" + type + \", sortable=\" + sortable + \", noindex=\" + noIndex\n          + \", weight=\" + weight + \", nostem=\" + nostem + \", phonetic='\" + phonetic + \"'}\";\n    }\n  }\n\n  public static class TagField extends Field {\n\n    private final String separator;\n    private final boolean caseSensitive;\n\n    public TagField(String name) {\n      this(name, null);\n    }\n\n    public TagField(String name, String separator) {\n      this(name, separator, false);\n    }\n\n    public TagField(String name, boolean sortable) {\n      this(name, null, sortable);\n    }\n\n    public TagField(String name, String separator, boolean sortable) {\n      this(name, separator, false, sortable);\n    }\n\n    public TagField(String name, boolean caseSensitive, boolean sortable) {\n      this(name, null, caseSensitive, sortable);\n    }\n\n    public TagField(String name, String separator, boolean caseSensitive, boolean sortable) {\n      super(name, FieldType.TAG, sortable);\n      this.separator = separator;\n      this.caseSensitive = caseSensitive;\n    }\n\n    public TagField(FieldName name, String separator, boolean sortable) {\n      this(name, separator, false, sortable);\n    }\n\n    public TagField(FieldName name, String separator, boolean caseSensitive, boolean sortable) {\n      super(name, FieldType.TAG, sortable, false);\n      this.separator = separator;\n      this.caseSensitive = caseSensitive;\n    }\n\n    @Override\n    public void addTypeArgs(CommandArguments args) {\n      if (separator != null) {\n        args.add(\"SEPARATOR\");\n        args.add(separator);\n      }\n      if (caseSensitive) {\n        args.add(\"CASESENSITIVE\");\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"TagField{name='\" + fieldName + \"', type=\" + type + \", sortable=\" + sortable + \", noindex=\" + noIndex\n          + \", separator='\" + separator + \"', caseSensitive='\" + caseSensitive + \"'}\";\n    }\n  }\n\n  public static class VectorField extends Field {\n\n\n    /**\n     * Enumeration of supported vector indexing algorithms in Redis.\n     * Each algorithm has different performance characteristics and use cases.\n     */\n    public enum VectorAlgo implements Rawable {\n\n      /**\n       * FLAT algorithm provides exact vector search with perfect accuracy.\n       * Best suited for smaller datasets (&lt; 1M vectors) where search accuracy\n       * is more important than search latency.\n       */\n      FLAT(\"FLAT\"),\n\n      /**\n       * HNSW (Hierarchical Navigable Small World) algorithm provides approximate\n       * vector search with configurable accuracy-performance trade-offs.\n       * Best suited for larger datasets (&gt; 1M vectors) where search performance\n       * and scalability are more important than perfect accuracy.\n       */\n      HNSW(\"HNSW\"),\n\n      /**\n       * SVS_VAMANA algorithm provides high-performance approximate vector search\n       * optimized for specific use cases with advanced compression and optimization features.\n       *\n       * <p>Characteristics:\n       * <ul>\n       *   <li>High-performance approximate search</li>\n       *   <li>Support for vector compression (LVQ, LeanVec)</li>\n       *   <li>Configurable graph construction and search parameters</li>\n       *   <li>Optimized for Intel platforms with fallback support</li>\n       * </ul>\n       *\n       * <p>Note: This algorithm may have specific requirements and limitations.\n       * Consult the Redis documentation for detailed usage guidelines.\n       */\n      SVS_VAMANA(\"SVS-VAMANA\");\n\n      private final byte[] raw;\n\n      /**\n       * Creates a VectorAlgorithm enum value.\n       *\n       * @param redisParamName the Redis parameter name for this algorithm\n       */\n      VectorAlgo(String redisParamName) {\n        raw = SafeEncoder.encode(redisParamName);\n      }\n\n      /**\n       * Returns the raw byte representation of the algorithm name for Redis commands.\n       *\n       * @return the raw bytes of the algorithm name\n       */\n      @Override\n      public byte[] getRaw() {\n        return raw;\n      }\n    }\n\n    private final VectorAlgo algorithm;\n    private final Map<String, Object> attributes;\n\n    public VectorField(String name, VectorAlgo algorithm, Map<String, Object> attributes) {\n      super(name, FieldType.VECTOR);\n      this.algorithm = algorithm;\n      this.attributes = attributes;\n    }\n\n    @Override\n    public void addTypeArgs(CommandArguments args) {\n\n      args.add(algorithm);\n\n      args.add(attributes.size() << 1);\n      for (Map.Entry<String, Object> entry : attributes.entrySet()) {\n        args.add(entry.getKey());\n        args.add(entry.getValue());\n      }\n    }\n\n    @Override\n    public String toString() {\n      return \"VectorField{name='\" + fieldName + \"', type=\" + type + \", algorithm=\" + algorithm + \", attributes=\" + attributes + \"}\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Scorer.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\n\nimport java.util.List;\n\n/**\n * Abstract scorer for text search. Instances are created via {@link Scorers}.\n * @see Scorers\n */\n@Experimental\npublic abstract class Scorer implements IParams {\n\n  private final String name;\n\n  protected Scorer(String name) {\n    this.name = name;\n  }\n\n  public final String getName() {\n    return name;\n  }\n\n  @Override\n  public final void addParams(CommandArguments args) {\n    args.add(name);\n  }\n\n  @Override\n  public String toString() {\n    return name;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/Scorers.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.annots.Experimental;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Factory class for creating {@link Scorer} instances for text search.\n * @see Scorer\n */\n@Experimental\npublic class Scorers {\n\n  // Predefined Scorer instances\n  private static final Scorer TFIDF = scorer(\"TFIDF\");\n  private static final Scorer TFIDF_DOCNORM = scorer(\"TFIDF.DOCNORM\");\n  private static final Scorer BM25STD = scorer(\"BM25STD\");\n  private static final Scorer BM25STD_NORM = scorer(\"BM25STD.NORM\");\n  private static final Scorer DISMAX = scorer(\"DISMAX\");\n  private static final Scorer DOCSCORE = scorer(\"DOCSCORE\");\n  private static final Scorer HAMMING = scorer(\"HAMMING\");\n\n  private Scorers() {\n  }\n\n  private static Scorer scorer(String name) {\n    return new Scorer(name) {\n    };\n  }\n\n  public static Scorer tfidf() {\n    return TFIDF;\n  }\n\n  public static Scorer tfidfDocnorm() {\n    return TFIDF_DOCNORM;\n  }\n\n  public static Scorer bm25std() {\n    return BM25STD;\n  }\n\n  public static Scorer bm25stdNorm() {\n    return BM25STD_NORM;\n  }\n\n  public static Scorer dismax() {\n    return DISMAX;\n  }\n\n  public static Scorer docscore() {\n    return DOCSCORE;\n  }\n\n  public static Scorer hamming() {\n    return HAMMING;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/SearchBuilderFactory.java",
    "content": "package redis.clients.jedis.search;\n\nimport static redis.clients.jedis.BuilderFactory.STRING;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.util.KeyValue;\n\npublic final class SearchBuilderFactory {\n\n  public static final Builder<Map<String, List<String>>> SEARCH_SYNONYM_GROUPS = new Builder<Map<String, List<String>>>() {\n    @Override\n    public Map<String, List<String>> build(Object data) {\n      List list = (List) data;\n      if (list.isEmpty()) return Collections.emptyMap();\n\n      if (list.get(0) instanceof KeyValue) {\n        return ((List<KeyValue>) data).stream().collect(Collectors.toMap(\n            kv -> STRING.build(kv.getKey()), kv -> BuilderFactory.STRING_LIST.build(kv.getValue())));\n      }\n\n      Map<String, List<String>> dump = new HashMap<>(list.size() / 2, 1f);\n      for (int i = 0; i < list.size(); i += 2) {\n        dump.put(STRING.build(list.get(i)), BuilderFactory.STRING_LIST.build(list.get(i + 1)));\n      }\n      return dump;\n    }\n  };\n\n  public static final Builder<Map<String, Map<String, Double>>> SEARCH_SPELLCHECK_RESPONSE\n      = new Builder<Map<String, Map<String, Double>>>() {\n\n    private static final String TERM = \"TERM\";\n    private static final String RESULTS = \"results\";\n\n    @Override\n    public Map<String, Map<String, Double>> build(Object data) {\n      List rawDataList = (List) data;\n      if (rawDataList.isEmpty()) return Collections.emptyMap();\n\n      if (rawDataList.get(0) instanceof KeyValue) {\n        KeyValue rawData = (KeyValue) rawDataList.get(0);\n        String header = STRING.build(rawData.getKey());\n        if (!RESULTS.equals(header)) {\n          throw new IllegalStateException(\"Unrecognized header: \" + header);\n        }\n\n        return ((List<KeyValue>) rawData.getValue()).stream().collect(Collectors.toMap(\n            rawTerm -> STRING.build(rawTerm.getKey()),\n            rawTerm -> ((List<List<KeyValue>>) rawTerm.getValue()).stream()\n                .collect(Collectors.toMap(entry -> STRING.build(entry.get(0).getKey()),\n                      entry -> BuilderFactory.DOUBLE.build(entry.get(0).getValue()))),\n            (x, y) -> x, LinkedHashMap::new));\n      }\n\n      Map<String, Map<String, Double>> returnTerms = new LinkedHashMap<>(rawDataList.size());\n\n      for (Object rawData : rawDataList) {\n        List<Object> rawElements = (List<Object>) rawData;\n\n        String header = STRING.build(rawElements.get(0));\n        if (!TERM.equals(header)) {\n          throw new IllegalStateException(\"Unrecognized header: \" + header);\n        }\n        String term = STRING.build(rawElements.get(1));\n\n        List<List<Object>> list = (List<List<Object>>) rawElements.get(2);\n        Map<String, Double> entries = new LinkedHashMap<>(list.size());\n        list.forEach(entry -> entries.put(STRING.build(entry.get(1)), BuilderFactory.DOUBLE.build(entry.get(0))));\n\n        returnTerms.put(term, entries);\n      }\n      return returnTerms;\n    }\n  };\n\n  private SearchBuilderFactory() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/SearchProtocol.java",
    "content": "package redis.clients.jedis.search;\n\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class SearchProtocol {\n\n  public static final int DEFAULT_DIALECT = 2;\n\n  public enum SearchCommand implements ProtocolCommand {\n\n    CREATE(\"FT.CREATE\"),\n    ALTER(\"FT.ALTER\"),\n    INFO(\"FT.INFO\"),\n    SEARCH(\"FT.SEARCH\"),\n    EXPLAIN(\"FT.EXPLAIN\"),\n    EXPLAINCLI(\"FT.EXPLAINCLI\"),\n    AGGREGATE(\"FT.AGGREGATE\"),\n    CURSOR(\"FT.CURSOR\"),\n    @Deprecated CONFIG(\"FT.CONFIG\"),\n    ALIASADD(\"FT.ALIASADD\"),\n    ALIASUPDATE(\"FT.ALIASUPDATE\"),\n    ALIASDEL(\"FT.ALIASDEL\"),\n    SYNUPDATE(\"FT.SYNUPDATE\"),\n    SYNDUMP(\"FT.SYNDUMP\"),\n    SUGADD(\"FT.SUGADD\"),\n    SUGGET(\"FT.SUGGET\"),\n    SUGDEL(\"FT.SUGDEL\"),\n    SUGLEN(\"FT.SUGLEN\"),\n    DROPINDEX(\"FT.DROPINDEX\"),\n    DICTADD(\"FT.DICTADD\"),\n    DICTDEL(\"FT.DICTDEL\"),\n    DICTDUMP(\"FT.DICTDUMP\"),\n    SPELLCHECK(\"FT.SPELLCHECK\"),\n    TAGVALS(\"FT.TAGVALS\"),\n    PROFILE(\"FT.PROFILE\"),\n    _LIST(\"FT._LIST\"),\n    HYBRID(\"FT.HYBRID\");\n\n    private final byte[] raw;\n\n    private SearchCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum SearchKeyword implements Rawable {\n\n    SCHEMA, TEXT, TAG, NUMERIC, GEO, GEOSHAPE, VECTOR, VERBATIM, NOCONTENT, NOSTOPWORDS, WITHSCORES,\n    LANGUAGE, INFIELDS, SORTBY, ASC, DESC, LIMIT, HIGHLIGHT, FIELDS, TAGS, SUMMARIZE, FRAGS, LEN,\n    SEPARATOR, INKEYS, RETURN, FILTER, GEOFILTER, ADD, INCR, MAX, FUZZY, READ, DEL, DD, TEMPORARY,\n    STOPWORDS, NOFREQS, NOFIELDS, NOOFFSETS, NOHL, ON, SORTABLE, UNF, PREFIX,\n    LANGUAGE_FIELD, SCORE, SCORE_FIELD, SCORER, PARAMS, AS, DIALECT, SLOP, TIMEOUT, INORDER,\n    EXPANDER, MAXTEXTFIELDS, SKIPINITIALSCAN, WITHSUFFIXTRIE, NOSTEM, NOINDEX, PHONETIC, WEIGHT,\n    CASESENSITIVE, LOAD, APPLY, GROUPBY, MAXIDLE, WITHCURSOR, DISTANCE, TERMS, INCLUDE, EXCLUDE,\n    SEARCH, AGGREGATE, QUERY, LIMITED, COUNT, REDUCE, INDEXMISSING, INDEXEMPTY, ADDSCORES,\n    // FT.HYBRID keywords\n    VSIM, COMBINE, RRF, LINEAR, WINDOW, CONSTANT, ALPHA, BETA, KNN, RANGE, RADIUS, EPSILON,\n    YIELD_SCORE_AS, K, EF_RUNTIME, NOSORT,\n    @Deprecated SET, @Deprecated GET;\n\n    private final byte[] raw;\n\n    private SearchKeyword() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/SearchResult.java",
    "content": "package redis.clients.jedis.search;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.annots.Internal;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * SearchResult encapsulates the returned result from a search query. It contains publicly\n * accessible fields for the total number of results, and an array of {@link Document} objects\n * containing the actual returned documents.\n */\npublic class SearchResult {\n\n  private final long totalResults;\n  private final List<Document> documents;\n  private final List<String> warnings;\n\n  private SearchResult(long totalResults, List<Document> documents) {\n    this(totalResults, documents, (List<String>) null);\n  }\n\n  private SearchResult(long totalResults, List<Document> documents, List<String> warnings) {\n    this.totalResults = totalResults;\n    this.documents = documents;\n    this.warnings = warnings;\n  }\n\n  public long getTotalResults() {\n    return totalResults;\n  }\n\n  public List<Document> getDocuments() {\n    return Collections.unmodifiableList(documents);\n  }\n\n  public List<String> getWarnings() {\n    return warnings;\n  }\n\n  @Override\n  public String toString() {\n    return getClass().getSimpleName() + \"{Total results:\" + totalResults\n        + \", Documents:\" + documents\n        + (warnings != null ? \", Warnings:\" + warnings : \"\")\n        + \"}\";\n  }\n\n  public static class SearchResultBuilder extends Builder<SearchResult> {\n\n    private final boolean hasContent;\n    private final boolean hasScores;\n    private final boolean decode;\n\n    private final Map<String, Boolean> isFieldDecode;\n\n    public SearchResultBuilder(boolean hasContent, boolean hasScores, boolean decode) {\n      this(hasContent, hasScores, decode, null);\n    }\n\n    public SearchResultBuilder(boolean hasContent, boolean hasScores, boolean decode,\n        Map<String, Boolean> isFieldDecode) {\n      this.hasContent = hasContent;\n      this.hasScores = hasScores;\n      this.decode = decode;\n      this.isFieldDecode = isFieldDecode;\n    }\n\n    @Override\n    public SearchResult build(Object data) {\n      List<Object> resp = (List<Object>) data;\n\n      int step = 1;\n      int scoreOffset = 0;\n      int contentOffset = 1;\n      if (hasScores) {\n        step += 1;\n        scoreOffset = 1;\n        contentOffset += 1;\n      }\n      if (hasContent) {\n        step += 1;\n      }\n\n      // the first element is always the number of results\n      long totalResults = (Long) resp.get(0);\n      List<Document> documents = new ArrayList<>(resp.size() - 1);\n\n      for (int i = 1; i < resp.size(); i += step) {\n\n        String id = BuilderFactory.STRING.build(resp.get(i));\n        double score = hasScores ? BuilderFactory.DOUBLE.build(resp.get(i + scoreOffset)) : 1.0;\n        List<byte[]> fields = hasContent ? (List<byte[]>) resp.get(i + contentOffset) : null;\n\n        documents.add(Document.load(id, score, fields, decode, isFieldDecode));\n      }\n\n      return new SearchResult(totalResults, documents);\n    }\n  }\n\n  /// RESP3 -->\n  // TODO: final\n  public static Builder<SearchResult> SEARCH_RESULT_BUILDER\n      = new PerFieldDecoderSearchResultBuilder(Document.SEARCH_DOCUMENT);\n\n  @Internal\n  public static final class PerFieldDecoderSearchResultBuilder extends Builder<SearchResult> {\n\n    private static final String TOTAL_RESULTS_STR = \"total_results\";\n    private static final String RESULTS_STR = \"results\";\n    private static final String WARNINGS_STR = \"warning\";\n\n    private final Builder<Document> documentBuilder;\n\n    public PerFieldDecoderSearchResultBuilder(Map<String, Boolean> isFieldDecode) {\n      this(new Document.PerFieldDecoderDocumentBuilder(isFieldDecode));\n    }\n\n    private PerFieldDecoderSearchResultBuilder(Builder<Document> builder) {\n      this.documentBuilder = Objects.requireNonNull(builder);\n    }\n\n    @Override\n    public SearchResult build(Object data) {\n      List<KeyValue> list = (List<KeyValue>) data;\n      long totalResults = -1;\n      List<Document> results = null;\n      List<String> warnings = null;\n      for (KeyValue kv : list) {\n        String key = BuilderFactory.STRING.build(kv.getKey());\n        Object rawVal = kv.getValue();\n        switch (key) {\n          case TOTAL_RESULTS_STR:\n            totalResults = BuilderFactory.LONG.build(rawVal);\n            break;\n          case RESULTS_STR:\n            results = ((List<Object>) rawVal).stream()\n                .map(documentBuilder::build)\n                .collect(Collectors.toList());\n            break;\n          case WARNINGS_STR:\n            warnings = BuilderFactory.STRING_LIST.build(rawVal);\n            break;\n        }\n      }\n      return new SearchResult(totalResults, results, warnings);\n    }\n  };\n  /// <-- RESP3\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.FieldName;\nimport redis.clients.jedis.search.SearchProtocol.SearchKeyword;\nimport redis.clients.jedis.util.LazyRawable;\n\n/**\n * @author Guy Korland\n */\npublic class AggregationBuilder implements IParams {\n\n  private final List<Object> aggrArgs = new ArrayList<>();\n  private Integer dialect;\n  private boolean isWithCursor = false;\n\n  public AggregationBuilder(String query) {\n    aggrArgs.add(query);\n  }\n\n  public AggregationBuilder() {\n    this(\"*\");\n  }\n\n  public AggregationBuilder load(String... fields) {\n    return load(FieldName.convert(fields));\n  }\n\n  public AggregationBuilder load(FieldName... fields) {\n    aggrArgs.add(SearchKeyword.LOAD);\n    LazyRawable rawLoadCount = new LazyRawable();\n    aggrArgs.add(rawLoadCount);\n    int loadCount = 0;\n    for (FieldName fn : fields) {\n      loadCount += fn.addCommandArguments(aggrArgs);\n    }\n    rawLoadCount.setRaw(Protocol.toByteArray(loadCount));\n    return this;\n  }\n\n  public AggregationBuilder loadAll() {\n    aggrArgs.add(SearchKeyword.LOAD);\n    aggrArgs.add(Protocol.BYTES_ASTERISK);\n    return this;\n  }\n\n  public AggregationBuilder limit(int offset, int count) {\n    aggrArgs.add(SearchKeyword.LIMIT);\n    aggrArgs.add(offset);\n    aggrArgs.add(count);\n    return this;\n  }\n\n  public AggregationBuilder limit(int count) {\n    return limit(0, count);\n  }\n\n  public AggregationBuilder sortBy(SortedField... fields) {\n    aggrArgs.add(SearchKeyword.SORTBY);\n    aggrArgs.add(fields.length << 1);\n    for (SortedField field : fields) {\n      aggrArgs.add(field.getField());\n      aggrArgs.add(field.getOrder());\n    }\n    return this;\n  }\n\n  public AggregationBuilder sortByAsc(String field) {\n    return sortBy(SortedField.asc(field));\n  }\n\n  public AggregationBuilder sortByDesc(String field) {\n    return sortBy(SortedField.desc(field));\n  }\n\n  /**\n   * {@link AggregationBuilder#sortBy(redis.clients.jedis.search.aggr.SortedField...)}\n   * (or {@link AggregationBuilder#sortByAsc(java.lang.String)}\n   * or {@link AggregationBuilder#sortByDesc(java.lang.String)})\n   * MUST BE called JUST BEFORE this.\n   * @param max limit\n   * @return this\n   */\n  public AggregationBuilder sortByMax(int max) {\n    aggrArgs.add(SearchKeyword.MAX);\n    aggrArgs.add(max);\n    return this;\n  }\n\n  /**\n   * Shortcut to {@link AggregationBuilder#sortBy(redis.clients.jedis.search.aggr.SortedField...)}\n   * and {@link AggregationBuilder#sortByMax(int)}.\n   * @param max limit\n   * @param fields sorted fields\n   * @return this\n   */\n  public AggregationBuilder sortBy(int max, SortedField... fields) {\n    sortBy(fields);\n    sortByMax(max);\n    return this;\n  }\n\n  public AggregationBuilder apply(String projection, String alias) {\n    aggrArgs.add(SearchKeyword.APPLY);\n    aggrArgs.add(projection);\n    aggrArgs.add(SearchKeyword.AS);\n    aggrArgs.add(alias);\n    return this;\n  }\n\n  public AggregationBuilder groupBy(Group group) {\n    aggrArgs.add(SearchKeyword.GROUPBY);\n    group.addArgs(aggrArgs);\n    return this;\n  }\n\n  public AggregationBuilder groupBy(Collection<String> fields, Collection<Reducer> reducers) {\n    String[] fieldsArr = new String[fields.size()];\n    Group g = new Group(fields.toArray(fieldsArr));\n    reducers.forEach((r) -> g.reduce(r));\n    groupBy(g);\n    return this;\n  }\n\n  public AggregationBuilder groupBy(String field, Reducer... reducers) {\n    return groupBy(Collections.singletonList(field), Arrays.asList(reducers));\n  }\n\n  public AggregationBuilder filter(String expression) {\n    aggrArgs.add(SearchKeyword.FILTER);\n    aggrArgs.add(expression);\n    return this;\n  }\n\n  public AggregationBuilder cursor(int count) {\n    isWithCursor = true;\n    aggrArgs.add(SearchKeyword.WITHCURSOR);\n    aggrArgs.add(SearchKeyword.COUNT);\n    aggrArgs.add(count);\n    return this;\n  }\n\n  public AggregationBuilder cursor(int count, long maxIdle) {\n    isWithCursor = true;\n    aggrArgs.add(SearchKeyword.WITHCURSOR);\n    aggrArgs.add(SearchKeyword.COUNT);\n    aggrArgs.add(count);\n    aggrArgs.add(SearchKeyword.MAXIDLE);\n    aggrArgs.add(maxIdle);\n    return this;\n  }\n\n  public AggregationBuilder verbatim() {\n    aggrArgs.add(SearchKeyword.VERBATIM);\n    return this;\n  }\n\n  public AggregationBuilder timeout(long timeout) {\n    aggrArgs.add(SearchKeyword.TIMEOUT);\n    aggrArgs.add(timeout);\n    return this;\n  }\n\n  public AggregationBuilder addScores() {\n    aggrArgs.add(SearchKeyword.ADDSCORES);\n    return this;\n  }\n\n  public AggregationBuilder params(Map<String, Object> params) {\n    aggrArgs.add(SearchKeyword.PARAMS);\n    aggrArgs.add(params.size() << 1);\n    params.forEach((k, v) -> {\n      aggrArgs.add(k);\n      aggrArgs.add(v);\n    });\n    return this;\n  }\n\n  public AggregationBuilder dialect(int dialect) {\n    this.dialect = dialect;\n    return this;\n  }\n\n  /**\n   * This method will not replace the dialect if it has been already set.\n   * @param dialect dialect\n   * @return this\n   */\n  public AggregationBuilder dialectOptional(int dialect) {\n    if (dialect != 0 && this.dialect == null) {\n      this.dialect = dialect;\n    }\n    return this;\n  }\n\n  public boolean isWithCursor() {\n    return isWithCursor;\n  }\n\n  @Override\n  public void addParams(CommandArguments commArgs) {\n    commArgs.addObjects(aggrArgs);\n    if (dialect != null) {\n      commArgs.add(SearchKeyword.DIALECT).add(dialect);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class AggregationResult {\n\n  private final long totalResults;\n\n  private final List<Map<String, Object>> results;\n\n  private final List<String> warnings;\n\n  private Long cursorId = -1L;\n\n  private AggregationResult(long totalResults, List<Map<String, Object>> results) {\n    this(totalResults, results, (List<String>) null);\n  }\n\n  private AggregationResult(long totalResults, List<Map<String, Object>> results, List<String> warnings) {\n    this.totalResults = totalResults;\n    this.results = results;\n    this.warnings = warnings;\n  }\n\n  private void setCursorId(Long cursorId) {\n    this.cursorId = cursorId;\n  }\n\n  public Long getCursorId() {\n    return cursorId;\n  }\n\n  public long getTotalResults() {\n    return totalResults;\n  }\n\n  public List<Map<String, Object>> getResults() {\n    return Collections.unmodifiableList(results);\n  }\n\n  /**\n   * @return results as {@link Row}s.\n   * @see #getResults()\n   */\n  public List<Row> getRows() {\n    return results.stream().map(Row::new).collect(Collectors.toList());\n  }\n\n  public Row getRow(int index) {\n    return new Row(results.get(index));\n  }\n\n  public List<String> getWarnings() {\n    return warnings;\n  }\n\n  public static final Builder<AggregationResult> SEARCH_AGGREGATION_RESULT = new Builder<AggregationResult>() {\n\n    private static final String TOTAL_RESULTS_STR = \"total_results\";\n    private static final String RESULTS_STR = \"results\";\n    // private static final String FIELDS_STR = \"fields\";\n    private static final String FIELDS_STR = \"extra_attributes\";\n    private static final String WARNINGS_STR = \"warning\";\n\n    @Override\n    public AggregationResult build(Object data) {\n      // return new AggregationResult(data);\n      List list = (List) data;\n\n      if (list.get(0) instanceof KeyValue) {\n        List<KeyValue> kvList = (List<KeyValue>) data;\n        long totalResults = -1;\n        List<Map<String, Object>> results = null;\n        List<String> warnings = null;\n        for (KeyValue kv : kvList) {\n          String key = BuilderFactory.STRING.build(kv.getKey());\n          Object rawVal = kv.getValue();\n          switch (key) {\n            case TOTAL_RESULTS_STR:\n              totalResults = BuilderFactory.LONG.build(rawVal);\n              break;\n            case RESULTS_STR:\n              List<List<KeyValue>> resList = (List<List<KeyValue>>) rawVal;\n              results = new ArrayList<>(resList.size());\n              for (List<KeyValue> rikv : resList) {\n                for (KeyValue ikv : rikv) {\n                  if (FIELDS_STR.equals(BuilderFactory.STRING.build(ikv.getKey()))) {\n                    results.add(BuilderFactory.ENCODED_OBJECT_MAP.build(ikv.getValue()));\n                    break;\n                  }\n                }\n              }\n              break;\n            case WARNINGS_STR:\n              warnings = BuilderFactory.STRING_LIST.build(rawVal);\n              break;\n          }\n        }\n        return new AggregationResult(totalResults, results, warnings);\n      }\n\n      list = (List<Object>) SafeEncoder.encodeObject(data);\n\n      // the first element is always the number of results\n      long totalResults = (Long) list.get(0);\n      List<Map<String, Object>> results = new ArrayList<>(list.size() - 1);\n\n      for (int i = 1; i < list.size(); i++) {\n        List<Object> mapList = (List<Object>) list.get(i);\n        Map<String, Object> map = new HashMap<>(mapList.size() / 2, 1f);\n        for (int j = 0; j < mapList.size(); j += 2) {\n          Object r = mapList.get(j);\n          if (r instanceof JedisDataException) {\n            throw (JedisDataException) r;\n          }\n          map.put((String) r, mapList.get(j + 1));\n        }\n        results.add(map);\n      }\n      return new AggregationResult(totalResults, results);\n    }\n  };\n\n  public static final Builder<AggregationResult> SEARCH_AGGREGATION_RESULT_WITH_CURSOR = new Builder<AggregationResult>() {\n    @Override\n    public AggregationResult build(Object data) {\n      List<Object> list = (List<Object>) data;\n      // return new AggregationResult(list.get(0), (long) list.get(1));\n      AggregationResult r = SEARCH_AGGREGATION_RESULT.build(list.get(0));\n      r.setCursorId((Long) list.get(1));\n      return r;\n    }\n  };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.Collection;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.search.SearchProtocol;\nimport redis.clients.jedis.util.JedisCommandIterationBase;\n\npublic class FtAggregateIteration extends JedisCommandIterationBase<AggregationResult, Row> {\n\n  private final String indexName;\n  private final CommandArguments args;\n\n  /**\n   * {@link AggregationBuilder#cursor(int, long) CURSOR} must be set.\n   * @param connectionProvider connection provider\n   * @param indexName index name\n   * @param aggr cursor must be set\n   */\n  public FtAggregateIteration(ConnectionProvider connectionProvider, String indexName, AggregationBuilder aggr) {\n    super(connectionProvider, AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR);\n    if (!aggr.isWithCursor()) throw new IllegalArgumentException(\"cursor must be set\");\n    this.indexName = indexName;\n    this.args = new CommandArguments(SearchProtocol.SearchCommand.AGGREGATE).add(this.indexName).addParams(aggr);\n  }\n\n  @Override\n  protected boolean isNodeCompleted(AggregationResult reply) {\n    return reply.getCursorId() == 0L;\n  }\n\n  @Override\n  protected CommandArguments initCommandArguments() {\n    return args;\n  }\n\n  @Override\n  protected CommandArguments nextCommandArguments(AggregationResult lastReply) {\n    return new CommandArguments(SearchProtocol.SearchCommand.CURSOR).add(SearchProtocol.SearchKeyword.READ)\n        .add(indexName).add(lastReply.getCursorId());\n  }\n\n  @Override\n  protected Collection<Row> convertBatchToData(AggregationResult batch) {\n    return batch.getRows();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/Group.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Created by mnunberg on 2/22/18.\n */\npublic class Group {\n\n  private final List<String> fields = new ArrayList<>();\n  private final List<Reducer> reducers = new ArrayList<>();\n\n  public Group(String... fields) {\n    this.fields.addAll(Arrays.asList(fields));\n  }\n\n  public Group reduce(Reducer r) {\n    reducers.add(r);\n    return this;\n  }\n\n  public void addArgs(List<Object> args) {\n\n    args.add(fields.size());\n    args.addAll(fields);\n\n    reducers.forEach((r) -> r.addArgs(args));\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/Reducer.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.List;\nimport redis.clients.jedis.search.SearchProtocol.SearchKeyword;\n\n/**\n * Created by mnunberg on 2/22/18.\n *\n * This class is normally received via one of the subclasses or via Reducers\n */\npublic abstract class Reducer {\n\n  private final String name;\n  private final String field;\n  private String alias;\n\n  protected Reducer(String name) {\n    this.name = name;\n    this.field = null;\n  }\n\n  protected Reducer(String name, String field) {\n    this.name = name;\n    this.field = field;\n  }\n\n  public final Reducer as(String alias) {\n    this.alias = alias;\n    return this;\n  }\n\n  public final String getName() {\n    return name;\n  }\n\n  public final String getField() {\n    return field;\n  }\n\n  public final String getAlias() {\n    return alias;\n  }\n\n  protected abstract List<Object> getOwnArgs();\n\n  public final void addArgs(List<Object> args) {\n\n    args.add(SearchKeyword.REDUCE);\n    args.add(name);\n\n    List<Object> ownArgs = getOwnArgs();\n    if (field != null) {\n      args.add(1 + ownArgs.size());\n      args.add(field);\n    } else {\n      args.add(ownArgs.size());\n    }\n    args.addAll(ownArgs);\n\n    if (alias != null) {\n      args.add(SearchKeyword.AS);\n      args.add(alias);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/Reducers.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Created by mnunberg on 2/22/18.\n */\npublic class Reducers {\n\n  public static Reducer count() {\n    return new Reducer(\"COUNT\") {\n      @Override protected List<Object> getOwnArgs() {\n        return Collections.emptyList();\n      }\n    };\n  }\n\n  private static Reducer singleFieldReducer(String name, String field) {\n    return new Reducer(name, field) {\n      @Override protected List<Object> getOwnArgs() {\n        return Collections.emptyList();\n      }\n    };\n  }\n\n  public static Reducer count_distinct(String field) {\n    return singleFieldReducer(\"COUNT_DISTINCT\", field);\n  }\n\n  public static Reducer count_distinctish(String field) {\n    return singleFieldReducer(\"COUNT_DISTINCTISH\", field);\n  }\n\n  public static Reducer sum(String field) {\n    return singleFieldReducer(\"SUM\", field);\n  }\n\n  public static Reducer min(String field) {\n    return singleFieldReducer(\"MIN\", field);\n  }\n\n  public static Reducer max(String field) {\n    return singleFieldReducer(\"MAX\", field);\n  }\n\n  public static Reducer avg(String field) {\n    return singleFieldReducer(\"AVG\", field);\n  }\n\n  public static Reducer stddev(String field) {\n    return singleFieldReducer(\"STDDEV\", field);\n  }\n\n  public static Reducer quantile(String field, double percentile) {\n    return new Reducer(\"QUANTILE\", field) {\n      @Override protected List<Object> getOwnArgs() {\n        return Arrays.asList(percentile);\n      }\n    };\n  }\n\n  public static Reducer first_value(String field) {\n    return singleFieldReducer(\"FIRST_VALUE\", field);\n  }\n\n  /**\n   * REDUCE FIRST_VALUE {nargs} {property} [BY {property} [ASC|DESC]]\n   *\n   * @param field\n   * @param sortBy\n   * @return Reducer\n   */\n  public static Reducer first_value(String field, SortedField sortBy) {\n    return new Reducer(\"FIRST_VALUE\", field) {\n      @Override protected List<Object> getOwnArgs() {\n        return Arrays.asList(\"BY\", sortBy.getField(), sortBy.getOrder());\n      }\n    };\n  }\n\n  public static Reducer to_list(String field) {\n    return singleFieldReducer(\"TOLIST\", field);\n  }\n\n  public static Reducer random_sample(String field, int size) {\n    return new Reducer(\"RANDOM_SAMPLE\", field) {\n      @Override protected List<Object> getOwnArgs() {\n        return Arrays.asList(size);\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/Row.java",
    "content": "package redis.clients.jedis.search.aggr;\n\nimport java.util.Map;\nimport redis.clients.jedis.util.DoublePrecision;\n\npublic class Row {\n\n  private final Map<String, Object> fields;\n\n  public Row(Map<String, Object> fields) {\n    this.fields = fields;\n  }\n\n  public boolean containsKey(String key) {\n    return fields.containsKey(key);\n  }\n\n  public Object get(String key) {\n    return fields.get(key);\n  }\n\n  public String getString(String key) {\n    if (!containsKey(key)) {\n      return \"\";\n    }\n    return (String) fields.get(key);\n  }\n\n  public long getLong(String key) {\n    if (!containsKey(key)) {\n      return 0;\n    }\n    return Long.parseLong((String) fields.get(key));\n  }\n\n  public double getDouble(String key) {\n    if (!containsKey(key)) {\n      return 0;\n    }\n    return DoublePrecision.parseFloatingPointNumber((String) fields.get(key));\n  }\n\n  @Override\n  public String toString() {\n    return String.valueOf(fields);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/SortedField.java",
    "content": "package redis.clients.jedis.search.aggr;\n\n/**\n * Created by mnunberg on 2/22/18.\n */\npublic class SortedField {\n\n  public enum SortOrder {\n    ASC, DESC\n  }\n\n  private final String fieldName;\n  private final SortOrder sortOrder;\n\n  public SortedField(String fieldName, SortOrder order) {\n    this.fieldName = fieldName;\n    this.sortOrder = order;\n  }\n\n  public final String getOrder() {\n    return sortOrder.toString();\n  }\n\n  public final String getField() {\n    return fieldName;\n  }\n\n  public static SortedField asc(String field) {\n    return new SortedField(field, SortOrder.ASC);\n  }\n\n  public static SortedField desc(String field) {\n    return new SortedField(field, SortOrder.DESC);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/aggr/package-info.java",
    "content": "/**\n * This package contains the classes related to Aggregation commands in RediSearch module.\n */\npackage redis.clients.jedis.search.aggr;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/hybrid/FTHybridParams.java",
    "content": "package redis.clients.jedis.search.hybrid;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.Combiner;\nimport redis.clients.jedis.search.Combiners;\nimport redis.clients.jedis.util.JedisAsserts;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\n/**\n * Argument list builder for the Redis {@code FT.HYBRID} command. Combines text search and vector\n * similarity search with configurable combination strategies and post-processing operations.\n * <p>\n * <strong>Basic Usage:</strong>\n * </p>\n *\n * <pre>\n * FTHybridParams params = FTHybridParams.builder()\n *     .search(FTHybridSearchParams.builder().query(\"comfortable shoes\").build())\n *     .vectorSearch(FTHybridVectorParams.builder().field(\"@embedding\").vector(\"vec\")\n *         .method(FTHybridVectorParams.Knn.of(10)).build())\n *     .combine(Combiners.rrf()).param(\"vec\", vectorBlob).build();\n * </pre>\n *\n * @see FTHybridSearchParams\n * @see FTHybridVectorParams\n * @see Combiner\n * @see Combiners\n * @see FTHybridPostProcessingParams\n */\n@Experimental\npublic class FTHybridParams implements IParams {\n\n  private final List<FTHybridSearchParams> searchArgs = new ArrayList<>();\n  private final List<FTHybridVectorParams> vectorArgs = new ArrayList<>();\n  private Combiner combiner;\n  private FTHybridPostProcessingParams postProcessingArgs;\n  private final Map<String, Object> params = new HashMap<>();\n  private Long timeout;\n\n  private FTHybridParams() {\n  }\n\n  /**\n   * @return a new {@link Builder} for {@link FTHybridParams}.\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder for {@link FTHybridParams}.\n   */\n  public static class Builder {\n    private final FTHybridParams instance = new FTHybridParams();\n\n    /**\n     * Build the {@link FTHybridParams} instance.\n     * @return the configured arguments\n     */\n    public FTHybridParams build() {\n      // Validate that both SEARCH and VSIM are configured (per FT.HYBRID requirements)\n      JedisAsserts.isTrue(!instance.searchArgs.isEmpty(),\n        \"At least one SEARCH clause must be configured\");\n      JedisAsserts.isTrue(!instance.vectorArgs.isEmpty(),\n        \"At least one VSIM clause must be configured\");\n\n      return instance;\n    }\n\n    /**\n     * Configure the SEARCH clause using {@link FTHybridSearchParams}.\n     * @param searchArgs the search arguments\n     * @return this builder\n     */\n    public Builder search(FTHybridSearchParams searchArgs) {\n      JedisAsserts.notNull(searchArgs, \"Search args must not be null\");\n\n      instance.searchArgs.add(searchArgs);\n      return this;\n    }\n\n    /**\n     * Configure the VSIM clause using {@link FTHybridVectorParams}.\n     * @param vectorArgs the vector search arguments\n     * @return this builder\n     */\n    public Builder vectorSearch(FTHybridVectorParams vectorArgs) {\n      JedisAsserts.notNull(vectorArgs, \"Vector args must not be null\");\n\n      instance.vectorArgs.add(vectorArgs);\n      return this;\n    }\n\n    /**\n     * Configure the COMBINE clause using a {@link Combiner}.\n     * @param combiner the combiner (e.g., {@code Combiners.rrf()} or {@code Combiners.linear()})\n     * @return this builder\n     * @see Combiners\n     */\n    public Builder combine(Combiner combiner) {\n      JedisAsserts.notNull(combiner, \"Combiner must not be null\");\n\n      instance.combiner = combiner;\n      return this;\n    }\n\n    /**\n     * Set the post-processing arguments.\n     * @param postProcessingArgs the post-processing configuration\n     * @return this builder\n     */\n    public Builder postProcessing(FTHybridPostProcessingParams postProcessingArgs) {\n      JedisAsserts.notNull(postProcessingArgs, \"PostProcessingParams must not be null\");\n\n      instance.postProcessingArgs = postProcessingArgs;\n      return this;\n    }\n\n    /**\n     * Add a parameter for parameterized queries.\n     * <p>\n     * Parameters can be referenced in queries using {@code $name} syntax.\n     * </p>\n     * @param name the parameter name\n     * @param value the parameter value\n     * @return this builder\n     */\n    public Builder param(String name, Object value) {\n      JedisAsserts.notNull(name, \"Parameter name must not be null\");\n      JedisAsserts.notNull(value, \"Parameter value must not be null\");\n\n      instance.params.put(name, value);\n      return this;\n    }\n\n    /**\n     * Set the maximum time to wait for the query to complete (in milliseconds).\n     * @param timeout the timeout in milliseconds\n     * @return this builder\n     */\n    public Builder timeout(long timeout) {\n      instance.timeout = timeout;\n      return this;\n    }\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    // SEARCH clause(s)\n    for (FTHybridSearchParams searchArg : searchArgs) {\n      searchArg.addParams(args);\n    }\n\n    // VSIM clause(s)\n    for (FTHybridVectorParams vectorArg : vectorArgs) {\n      vectorArg.addParams(args);\n    }\n\n    // COMBINE clause\n    if (combiner != null) {\n      args.add(COMBINE);\n      combiner.addParams(args);\n    }\n\n    // Post-processing operations (LOAD, GROUPBY, APPLY, SORTBY, FILTER, LIMIT)\n    if (postProcessingArgs != null) {\n      postProcessingArgs.addParams(args);\n    }\n\n    // PARAMS clause\n    if (!params.isEmpty()) {\n      args.add(PARAMS);\n      args.add(params.size() * 2);\n      for (Map.Entry<String, Object> entry : params.entrySet()) {\n        args.add(entry.getKey());\n        args.add(entry.getValue());\n      }\n    }\n\n    // TIMEOUT clause\n    if (timeout != null) {\n      args.add(TIMEOUT);\n      args.add(timeout);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/hybrid/FTHybridPostProcessingParams.java",
    "content": "package redis.clients.jedis.search.hybrid;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.Apply;\nimport redis.clients.jedis.search.Filter;\nimport redis.clients.jedis.search.Limit;\nimport redis.clients.jedis.search.aggr.Group;\nimport redis.clients.jedis.search.aggr.SortedField;\nimport redis.clients.jedis.util.JedisAsserts;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.GROUPBY;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.LOAD;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.NOSORT;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.SORTBY;\n\n/**\n * Arguments for post-processing operations in FT.HYBRID command. Supports LOAD, GROUPBY, APPLY,\n * SORTBY, FILTER, and LIMIT operations.\n * <p>\n * Operations are applied in a specific order:\n * <ol>\n * <li>LOAD - fields to load</li>\n * <li>GROUPBY - grouping with reducers</li>\n * <li>APPLY - computed fields</li>\n * <li>SORTBY - sorting</li>\n * <li>FILTER - filtering results</li>\n * <li>LIMIT - pagination</li>\n * </ol>\n */\n@Experimental\npublic class FTHybridPostProcessingParams implements IParams {\n\n  private List<String> loadFields;\n  private boolean loadAll;\n  private Group groupBy;\n  private final List<Apply> applies = new ArrayList<>();\n  private SortedField[] sortByFields;\n  private boolean noSort;\n  private Filter filter;\n  private Limit limit;\n\n  private static final String LOAD_ALL = \"*\";\n\n  private FTHybridPostProcessingParams() {\n  }\n\n  /**\n   * @return a new {@link Builder} for {@link FTHybridPostProcessingParams}.\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder for {@link FTHybridPostProcessingParams}.\n   */\n  public static class Builder {\n    private final FTHybridPostProcessingParams instance = new FTHybridPostProcessingParams();\n\n    /**\n     * Build the {@link FTHybridPostProcessingParams} instance.\n     * @return the configured arguments\n     */\n    public FTHybridPostProcessingParams build() {\n      return instance;\n    }\n\n    /**\n     * Set the fields to load in the results.\n     * <p>\n     * This method replaces any previous load configuration (including loadAll()). To load all\n     * fields, use {@link #loadAll()} instead.\n     * @param fields the field names to load (must not be empty)\n     * @return this builder\n     * @throws IllegalArgumentException if fields is null, empty, or contains \"*\"\n     */\n    public Builder load(String... fields) {\n      JedisAsserts.notNull(fields, \"Fields must not be null\");\n      JedisAsserts.isTrue(fields.length > 0, \"At least one field is required\");\n\n      // Validate no wildcards in specific field list\n      for (String field : fields) {\n        JedisAsserts.notNull(field, \"Field names cannot be null\");\n        JedisAsserts.isFalse(LOAD_ALL.equals(field),\n          \"Cannot use '*' in load(). Use loadAll() instead to load all fields.\");\n      }\n\n      // Clear previous state and set new values\n      instance.loadAll = false;\n      instance.loadFields = Arrays.asList(fields);\n      return this;\n    }\n\n    /**\n     * Set to load all fields in the results using LOAD *.\n     * <p>\n     * This method replaces any previous load configuration (including specific fields).\n     * <p>\n     * Note: requires Redis version &gt;= 8.6.0\n     * @return this builder\n     */\n    public Builder loadAll() {\n      instance.loadAll = true;\n      instance.loadFields = null;\n      return this;\n    }\n\n    /**\n     * Add a GROUPBY operation using {@link Group} from the aggregation package.\n     * @param group the group operation with reducers\n     * @return this builder\n     */\n    public Builder groupBy(Group group) {\n      instance.groupBy = group;\n      return this;\n    }\n\n    /**\n     * Add an APPLY operation.\n     * @param apply the apply operation\n     * @return this builder\n     */\n    public Builder apply(Apply apply) {\n      instance.applies.add(apply);\n      return this;\n    }\n\n    /**\n     * Add a SORTBY operation using {@link SortedField} from the aggregation package.\n     * <p>\n     * Last call to {@link #sortBy(SortedField...)}/{@link #noSort()} wins.\n     * @param fields the sorted fields\n     * @return this builder\n     * @see Builder#noSort()\n     */\n    public Builder sortBy(SortedField... fields) {\n      JedisAsserts.notNull(fields, \"Sort by fields must not be null\");\n      JedisAsserts.isTrue(fields.length > 0, \"At least one field is required\");\n\n      instance.sortByFields = fields;\n      instance.noSort = false;\n      return this;\n    }\n\n    /**\n     * Disable the default sorting by score. This adds the NOSORT keyword to the command.\n     * <p>\n     * Last call to {@link #sortBy(SortedField...)}/{@link #noSort()} wins.\n     * @return this builder\n     * @see Builder#sortBy(SortedField...)\n     */\n    public Builder noSort() {\n      instance.noSort = true;\n      instance.sortByFields = null;\n      return this;\n    }\n\n    /**\n     * Add a FILTER operation.\n     * @param filter the filter operation\n     * @return this builder\n     */\n    public Builder filter(Filter filter) {\n      instance.filter = filter;\n      return this;\n    }\n\n    /**\n     * Add a LIMIT operation.\n     * @param limit the limit operation\n     * @return this builder\n     */\n    public Builder limit(Limit limit) {\n      instance.limit = limit;\n      return this;\n    }\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    // LOAD clause\n    if (loadAll || (loadFields != null && !loadFields.isEmpty())) {\n      args.add(LOAD);\n      if (loadAll) {\n        // Special case for LOAD *\n        args.add(LOAD_ALL);\n      } else {\n        args.add(loadFields.size());\n        for (String field : loadFields) {\n          // Add @ prefix if not already present\n          if (!field.startsWith(\"@\")) {\n            args.add(\"@\" + field);\n          } else {\n            args.add(field);\n          }\n        }\n      }\n    }\n\n    // GROUPBY - convert aggr.Group to CommandArguments\n    if (groupBy != null) {\n      List<Object> groupArgs = new ArrayList<>();\n      groupBy.addArgs(groupArgs);\n      args.add(GROUPBY);\n      args.addObjects(groupArgs);\n    }\n\n    for (Apply apply : applies) {\n      apply.addParams(args);\n    }\n\n    // SORTBY or NOSORT - mutually exclusive\n    if (noSort) {\n      args.add(NOSORT);\n    } else if (sortByFields != null && sortByFields.length > 0) {\n      args.add(SORTBY);\n      args.add(sortByFields.length * 2);\n      for (SortedField field : sortByFields) {\n        args.add(field.getField());\n        args.add(field.getOrder());\n      }\n    }\n\n    if (filter != null) {\n      filter.addParams(args);\n    }\n\n    if (limit != null) {\n      limit.addParams(args);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    FTHybridPostProcessingParams that = (FTHybridPostProcessingParams) o;\n    return loadAll == that.loadAll && noSort == that.noSort\n        && Objects.equals(loadFields, that.loadFields) && Objects.equals(groupBy, that.groupBy)\n        && Objects.equals(applies, that.applies) && Arrays.equals(sortByFields, that.sortByFields)\n        && Objects.equals(filter, that.filter) && Objects.equals(limit, that.limit);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hash(loadFields, loadAll, groupBy, applies, noSort, filter, limit);\n    result = 31 * result + Arrays.hashCode(sortByFields);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/hybrid/FTHybridSearchParams.java",
    "content": "package redis.clients.jedis.search.hybrid;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.Scorer;\nimport redis.clients.jedis.util.JedisAsserts;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\n/**\n * Arguments for the SEARCH clause in FT.HYBRID command. Configures text search with optional scorer\n * and score aliasing.\n */\n@Experimental\npublic class FTHybridSearchParams implements IParams {\n\n  private String query;\n  private Scorer scorer;\n  private String scoreAlias;\n\n  private FTHybridSearchParams() {\n  }\n\n  /**\n   * @return a new {@link Builder} for {@link FTHybridSearchParams}.\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder for {@link FTHybridSearchParams}.\n   */\n  public static class Builder {\n    private final FTHybridSearchParams instance = new FTHybridSearchParams();\n\n    /**\n     * Build the {@link FTHybridSearchParams} instance.\n     * @return the configured arguments\n     */\n    public FTHybridSearchParams build() {\n      JedisAsserts.notNull(instance.query, \"Query must not be null\");\n\n      return instance;\n    }\n\n    /**\n     * Set the search query string.\n     * @param query the query string\n     * @return this builder\n     */\n    public Builder query(String query) {\n      instance.query = query;\n      return this;\n    }\n\n    /**\n     * Set the scorer for text search.\n     * @param scorer the scorer configuration\n     * @return this builder\n     */\n    public Builder scorer(Scorer scorer) {\n      instance.scorer = scorer;\n      return this;\n    }\n\n    /**\n     * Set an alias for the text search score in the results.\n     * @param scoreAlias the score alias name\n     * @return this builder\n     */\n    public Builder scoreAlias(String scoreAlias) {\n      instance.scoreAlias = scoreAlias;\n      return this;\n    }\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(SEARCH);\n    args.add(query);\n\n    if (scorer != null) {\n      args.add(SCORER);\n      scorer.addParams(args);\n    }\n\n    if (scoreAlias != null) {\n      args.add(YIELD_SCORE_AS);\n      args.add(scoreAlias);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/hybrid/FTHybridVectorParams.java",
    "content": "package redis.clients.jedis.search.hybrid;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.util.JedisAsserts;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\n/**\n * Arguments for the VSIM (Vector Similarity) clause in FT.HYBRID command. Configures vector search\n * with KNN or RANGE methods.\n */\n@Experimental\npublic class FTHybridVectorParams implements IParams {\n\n  private String field;\n  private String vector;\n  private VectorMethod method;\n  private final List<String> filters = new ArrayList<>();\n  private String scoreAlias;\n\n  private FTHybridVectorParams() {\n  }\n\n  /**\n   * @return a new {@link Builder} for {@link FTHybridVectorParams}.\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder for {@link FTHybridVectorParams}.\n   */\n  public static class Builder {\n    private final FTHybridVectorParams instance = new FTHybridVectorParams();\n\n    /**\n     * Build the {@link FTHybridVectorParams} instance.\n     * @return the configured arguments\n     */\n    public FTHybridVectorParams build() {\n      JedisAsserts.notNull(instance.field, \"Field is required for VSIM clause\");\n      JedisAsserts.notNull(instance.vector, \"Vector is required for VSIM clause\");\n      JedisAsserts.notNull(instance.method, \"Method (KNN or RANGE) is required for VSIM clause\");\n\n      return instance;\n    }\n\n    /**\n     * Set the vector field name.\n     * @param field the field name (e.g., \"@embedding\")\n     * @return this builder\n     */\n    public Builder field(String field) {\n      instance.field = field;\n      return this;\n    }\n\n    /**\n     * Set the param name to reference the query vector BLOB.\n     * @param vector the vector param name\n     * @return this builder\n     */\n    public Builder vector(String vector) {\n      instance.vector = vector;\n      return this;\n    }\n\n    /**\n     * Set the vector search method (KNN or RANGE).\n     * @param method the vector search method\n     * @return this builder\n     */\n    public Builder method(VectorMethod method) {\n      instance.method = method;\n      return this;\n    }\n\n    /**\n     * Add a FILTER expression for pre-filtering documents before vector scoring. Can be called\n     * multiple times to add multiple filters.\n     * @param filter the filter expression\n     * @return this builder\n     */\n    public Builder filter(String filter) {\n      JedisAsserts.notNull(filter, \"Filter expression must not be null\");\n\n      instance.filters.add(filter);\n      return this;\n    }\n\n    /**\n     * Set an alias for the vector distance score in the results.\n     * @param scoreAlias the score alias name\n     * @return this builder\n     */\n    public Builder scoreAlias(String scoreAlias) {\n      instance.scoreAlias = scoreAlias;\n      return this;\n    }\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.add(VSIM);\n    args.add(field);\n    if (vector.startsWith(\"$\")) {\n      args.add(vector);\n    } else {\n      args.add(String.format(\"$%s\", vector));\n    }\n\n    method.addParams(args);\n\n    // FILTER inside VSIM - can have multiple filters\n    for (String filter : filters) {\n      args.add(FILTER);\n      args.add(filter);\n    }\n\n    if (scoreAlias != null) {\n      args.add(YIELD_SCORE_AS);\n      args.add(scoreAlias);\n    }\n  }\n\n  /**\n   * Base interface for vector search methods.\n   */\n  public interface VectorMethod extends IParams {\n  }\n\n  /**\n   * KNN (K-Nearest Neighbors) vector search method.\n   */\n  public static class Knn implements VectorMethod {\n    private final int k;\n    private Integer efRuntime;\n\n    private Knn(int k) {\n      this.k = k;\n    }\n\n    /**\n     * Create a KNN method with the specified K value.\n     * @param k the number of nearest neighbors to return\n     * @return a new Knn instance\n     */\n    public static Knn of(int k) {\n      return new Knn(k);\n    }\n\n    /**\n     * Set the EF_RUNTIME parameter for HNSW algorithm.\n     * @param efRuntime the EF_RUNTIME value\n     * @return this Knn instance\n     */\n    public Knn efRuntime(int efRuntime) {\n      this.efRuntime = efRuntime;\n      return this;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(KNN);\n      int paramCount = efRuntime != null ? 4 : 2;\n      args.add(paramCount);\n      args.add(K);\n      args.add(k);\n      if (efRuntime != null) {\n        args.add(EF_RUNTIME);\n        args.add(efRuntime);\n      }\n    }\n  }\n\n  /**\n   * RANGE vector search method.\n   */\n  public static class Range implements VectorMethod {\n    private final double radius;\n    private Double epsilon;\n\n    private Range(double radius) {\n      this.radius = radius;\n    }\n\n    /**\n     * Create a RANGE method with the specified radius.\n     * @param radius the search radius\n     * @return a new Range instance\n     */\n    public static Range of(double radius) {\n      return new Range(radius);\n    }\n\n    /**\n     * Set the epsilon parameter for range search.\n     * @param epsilon the epsilon value\n     * @return this Range instance\n     */\n    public Range epsilon(double epsilon) {\n      this.epsilon = epsilon;\n      return this;\n    }\n\n    @Override\n    public void addParams(CommandArguments args) {\n      args.add(RANGE);\n      int paramCount = epsilon != null ? 4 : 2;\n      args.add(paramCount);\n      args.add(RADIUS);\n      args.add(radius);\n      if (epsilon != null) {\n        args.add(EPSILON);\n        args.add(epsilon);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/hybrid/HybridResult.java",
    "content": "package redis.clients.jedis.search.hybrid;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.search.Document;\nimport redis.clients.jedis.util.KeyValue;\n\n/**\n * Represents the results of an {@code FT.HYBRID} command. Extends the concept of search results\n * with additional hybrid-specific fields like execution time. Results are returned as\n * {@link Document} objects.\n */\n@Experimental\npublic class HybridResult {\n\n  private static final String KEY_FIELD = \"__key\";\n  private static final String SCORE_FIELD = \"__score\";\n\n  private final long totalResults;\n  private final double executionTime;\n  private final List<Document> documents;\n  private final List<String> warnings;\n\n  private HybridResult(long totalResults, double executionTime, List<Document> documents,\n      List<String> warnings) {\n    this.totalResults = totalResults;\n    this.executionTime = executionTime;\n    this.documents = documents != null ? documents : Collections.emptyList();\n    this.warnings = warnings != null ? warnings : Collections.emptyList();\n  }\n\n  /**\n   * @return the total number of matching documents reported by the server\n   */\n  public long getTotalResults() {\n    return totalResults;\n  }\n\n  /**\n   * @return the execution time reported by the server in seconds (or {@code 0.0} if not available)\n   */\n  public double getExecutionTime() {\n    return executionTime;\n  }\n\n  /**\n   * @return an unmodifiable view of all documents returned by the command\n   */\n  public List<Document> getDocuments() {\n    return Collections.unmodifiableList(documents);\n  }\n\n  /**\n   * @return a read-only view of all warnings reported by the server\n   */\n  public List<String> getWarnings() {\n    return Collections.unmodifiableList(warnings);\n  }\n\n  @Override\n  public String toString() {\n    return getClass().getSimpleName() + \"{Total results:\" + totalResults + \", Execution time:\"\n        + executionTime + \", Documents:\" + documents\n        + (warnings != null ? \", Warnings:\" + warnings : \"\") + \"}\";\n  }\n\n  /**\n   * Converts a flat map result to a Document. The map may contain __key and __score fields which\n   * are extracted as the document id and score respectively.\n   */\n  private static Document mapToDocument(Map<String, Object> map) {\n    String id = null;\n    Double score = null;\n    Map<String, Object> fields = new HashMap<>();\n\n    for (Map.Entry<String, Object> entry : map.entrySet()) {\n      String key = entry.getKey();\n      Object value = entry.getValue();\n      if (KEY_FIELD.equals(key)) {\n        id = value != null ? value.toString() : null;\n      } else if (SCORE_FIELD.equals(key)) {\n        score = value != null ? Double.parseDouble(value.toString()) : null;\n      } else {\n        fields.put(key, value);\n      }\n    }\n\n    return new Document(id, fields, score != null ? score : 1.0);\n  }\n\n  // RESP2/RESP3 Builder\n  public static final Builder<HybridResult> HYBRID_RESULT_BUILDER = new Builder<HybridResult>() {\n    private static final String TOTAL_RESULTS_STR = \"total_results\";\n    private static final String EXECUTION_TIME_STR = \"execution_time\";\n    private static final String RESULTS_STR = \"results\";\n    private static final String WARNINGS_STR = \"warnings\";\n\n    @Override\n    public HybridResult build(Object data) {\n      List list = (List) data;\n\n      // Check if RESP3 (KeyValue) or RESP2 (flat list)\n      if (!list.isEmpty() && list.get(0) instanceof KeyValue) {\n        return buildResp3((List<KeyValue>) list);\n      } else {\n        return buildResp2(list);\n      }\n    }\n\n    private HybridResult buildResp3(List<KeyValue> list) {\n      long totalResults = -1;\n      double executionTime = 0;\n      List<Document> documents = null;\n      List<String> warnings = null;\n\n      for (KeyValue kv : list) {\n        String key = BuilderFactory.STRING.build(kv.getKey());\n        Object rawVal = kv.getValue();\n        switch (key) {\n          case TOTAL_RESULTS_STR:\n            totalResults = BuilderFactory.LONG.build(rawVal);\n            break;\n          case EXECUTION_TIME_STR:\n            executionTime = BuilderFactory.DOUBLE.build(rawVal);\n            break;\n          case RESULTS_STR:\n            documents = new ArrayList<>();\n            List<Object> resultsList = (List<Object>) rawVal;\n            for (Object resultObj : resultsList) {\n              Map<String, Object> resultMap = BuilderFactory.ENCODED_OBJECT_MAP.build(resultObj);\n              documents.add(mapToDocument(resultMap));\n            }\n            break;\n          case WARNINGS_STR:\n            warnings = BuilderFactory.STRING_LIST.build(rawVal);\n            break;\n        }\n      }\n\n      return new HybridResult(totalResults, executionTime, documents, warnings);\n    }\n\n    private HybridResult buildResp2(List list) {\n      // RESP2 format: [\"key1\", value1, \"key2\", value2, ...]\n      long totalResults = -1;\n      double executionTime = 0;\n      List<Document> documents = null;\n      List<String> warnings = null;\n\n      for (int i = 0; i + 1 < list.size(); i += 2) {\n        String key = BuilderFactory.STRING.build(list.get(i));\n        Object rawVal = list.get(i + 1);\n\n        switch (key) {\n          case TOTAL_RESULTS_STR:\n            totalResults = BuilderFactory.LONG.build(rawVal);\n            break;\n          case EXECUTION_TIME_STR:\n            executionTime = BuilderFactory.DOUBLE.build(rawVal);\n            break;\n          case RESULTS_STR:\n            documents = new ArrayList<>();\n            List<Object> resultsList = (List<Object>) rawVal;\n            for (Object resultObj : resultsList) {\n              Map<String, Object> resultMap = BuilderFactory.ENCODED_OBJECT_MAP.build(resultObj);\n              documents.add(mapToDocument(resultMap));\n            }\n            break;\n          case WARNINGS_STR:\n            warnings = BuilderFactory.STRING_LIST.build(rawVal);\n            break;\n        }\n      }\n\n      return new HybridResult(totalResults, executionTime, documents, warnings);\n    }\n  };\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/package-info.java",
    "content": "/**\n * This package contains the classes and interfaces related to RediSearch module.\n */\npackage redis.clients.jedis.search;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * A disjunct node. evaluates to true if any of its children are false. Conversely, this node\n * evaluates to false only iff <b>all</b> of its children are true, making it the exact inverse of\n * {@link IntersectNode}\n *\n * In RS, it looks like:\n *\n * {@code -(@f1:v1 @f2:v2)}\n *\n * @see DisjunctUnionNode which evalutes to true if <b>all</b> its children are false.\n */\npublic class DisjunctNode extends IntersectNode {\n  @Override\n  public String toString(Parenthesize mode) {\n    String ret = super.toString(Parenthesize.NEVER);\n    if (shouldParenthesize(mode)) {\n      return \"-(\" + ret + \")\";\n    } else {\n      return \"-\" + ret;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * A disjunct union node is the inverse of a {@link UnionNode}. It evaluates to true only iff\n * <b>all</b> its children are false. Conversely, it evaluates to false if <b>any</b> of its\n * children are true.\n *\n * As an RS query it looks like {@code -(@f1:v1|@f2:v2)}\n *\n * @see DisjunctNode which evaluates to true if <b>any</b> of its children are false.\n */\npublic class DisjunctUnionNode extends DisjunctNode {\n  @Override\n  protected String getJoinString() {\n    return \"|\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * @author mnunberg on 2/23/18.\n */\npublic class DoubleRangeValue extends RangeValue {\n\n  private final double from;\n  private final double to;\n\n  private static void appendNum(StringBuilder sb, double n, boolean inclusive) {\n    if (!inclusive) {\n      sb.append(\"(\");\n    }\n    if (n == Double.NEGATIVE_INFINITY) {\n      sb.append(\"-inf\");\n    } else if (n == Double.POSITIVE_INFINITY) {\n      sb.append(\"inf\");\n    } else {\n      sb.append(n);\n    }\n  }\n\n  public DoubleRangeValue(double from, double to) {\n    this.from = from;\n    this.to = to;\n  }\n\n  @Override\n  protected void appendFrom(StringBuilder sb, boolean inclusive) {\n    appendNum(sb, from, inclusive);\n  }\n\n  @Override\n  protected void appendTo(StringBuilder sb, boolean inclusive) {\n    appendNum(sb, to, inclusive);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\nimport java.util.Locale;\nimport redis.clients.jedis.args.GeoUnit;\n\n/**\n * Created by mnunberg on 2/23/18.\n */\npublic class GeoValue extends Value {\n\n  private final GeoUnit unit;\n  private final double lon;\n  private final double lat;\n  private final double radius;\n\n  public GeoValue(double lon, double lat, double radius, GeoUnit unit) {\n    this.lon = lon;\n    this.lat = lat;\n    this.radius = radius;\n    this.unit = unit;\n  }\n\n  @Override\n  public String toString() {\n    return \"[\" + lon + \" \" + lat + \" \" + radius\n        + \" \" + unit.name().toLowerCase(Locale.ENGLISH) + \"]\";\n  }\n\n  @Override\n  public boolean isCombinable() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/IntersectNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * The intersection node evaluates to true if any of its children are true.\n *\n * In RS: {@code @f1:v1 @f2:v2}\n */\npublic class IntersectNode extends QueryNode {\n  @Override\n  protected String getJoinString() {\n    return \" \";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\npublic class LongRangeValue extends RangeValue {\n\n  private final long from;\n  private final long to;\n\n  @Override\n  public boolean isCombinable() {\n    return false;\n  }\n\n  private static void appendNum(StringBuilder sb, long n, boolean inclusive) {\n    if (!inclusive) {\n      sb.append(\"(\");\n    }\n    if (n == Long.MIN_VALUE) {\n      sb.append(\"-inf\");\n    } else if (n == Long.MAX_VALUE) {\n      sb.append(\"inf\");\n    } else {\n      sb.append(Long.toString(n));\n    }\n  }\n\n  public LongRangeValue(long from, long to) {\n    this.from = from;\n    this.to = to;\n  }\n\n  @Override\n  protected void appendFrom(StringBuilder sb, boolean inclusive) {\n    appendNum(sb, from, inclusive);\n  }\n\n  @Override\n  protected void appendTo(StringBuilder sb, boolean inclusive) {\n    appendNum(sb, to, inclusive);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/Node.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\nimport redis.clients.jedis.search.Query;\n\n/**\n * Created by mnunberg on 2/23/18.\n *\n * Base node interface\n */\npublic interface Node {\n\n  enum Parenthesize {\n\n    /**\n     * Always encapsulate\n     */\n    ALWAYS,\n\n    /**\n     * Never encapsulate. Note that this may be ignored if parentheses are semantically required\n     * (e.g. {@code @foo:(val1|val2)}. However, something like {@code @foo:v1 @bar:v2} need not be\n     * parenthesized.\n     */\n\n    NEVER,\n    /**\n     * Determine encapsulation based on number of children. If the node only has one child, it is\n     * not parenthesized, if it has more than one child, it is parenthesized\n     */\n\n    DEFAULT\n  }\n\n  /**\n   * Returns the string form of this node.\n   *\n   * @param mode Whether the string should be encapsulated in parentheses {@code (...)}\n   * @return The string query.\n   */\n  String toString(Parenthesize mode);\n\n  /**\n   * Returns the string form of this node. This may be passed to\n   * {@link redis.clients.jedis.UnifiedJedis#ftSearch(String, Query)}\n   *\n   * @return The query string.\n   */\n  @Override\n  String toString();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * Created by mnunberg on 2/23/18.\n *\n * The optional node affects scoring and ordering. If it evaluates to true, the result is ranked\n * higher. It is helpful to combine it with a {@link UnionNode} to rank a document higher if it\n * meets one of several criteria.\n *\n * In RS: {@code ~(@lang:en @country:us)}.\n */\npublic class OptionalNode extends IntersectNode {\n\n  @Override\n  public String toString(Parenthesize mode) {\n    String ret = super.toString(Parenthesize.NEVER);\n    if (shouldParenthesize(mode)) {\n      return \"~(\" + ret + \")\";\n    }\n    return \"~\" + ret;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilders.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\nimport java.util.Arrays;\n\nimport static redis.clients.jedis.search.querybuilder.Values.value;\n\n/**\n * Created by mnunberg on 2/23/18.\n *\n * This class contains methods to construct query nodes. These query nodes can be added to parent\n * query nodes (building a chain) or used as the root query node.\n */\npublic class QueryBuilders {\n  private QueryBuilders() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  /**\n   * Create a new intersection node with child nodes. An intersection node is true if all its\n   * children are also true\n   *\n   * @param n sub-condition to add\n   * @return The node\n   */\n  public static QueryNode intersect(Node... n) {\n    return new IntersectNode().add(n);\n  }\n\n  /**\n   * Create a new intersection node with a field-value pair.\n   *\n   * @param field The field that should contain this value. If this value is empty, then any field\n   * will be checked.\n   * @param values Value to check for. The node will be true only if the field (or any field)\n   * contains <i>all</i> of the values\n   * @return The node\n   */\n  public static QueryNode intersect(String field, Value... values) {\n    return new IntersectNode().add(field, values);\n  }\n\n  /**\n   * Helper method to create a new intersection node with a string value.\n   *\n   * @param field The field to check. If left null or empty, all fields will be checked.\n   * @param stringValue The value to check\n   * @return The node\n   */\n  public static QueryNode intersect(String field, String stringValue) {\n    return intersect(field, value(stringValue));\n  }\n\n  /**\n   * Create a union node. Union nodes evaluate to true if <i>any</i> of its children are true\n   *\n   * @param n Child node\n   * @return The union node\n   */\n  public static QueryNode union(Node... n) {\n    return new UnionNode().add(n);\n  }\n\n  /**\n   * Create a union node which can match an one or more values\n   *\n   * @param field Field to check. If empty, all fields are checked\n   * @param values Values to search for. The node evaluates to true if {@code field} matches any of\n   * the values\n   * @return The union node\n   */\n  public static QueryNode union(String field, Value... values) {\n    return new UnionNode().add(field, values);\n  }\n\n  /**\n   * Convenience method to match one or more strings. This is equivalent to\n   * {@code union(field, value(v1), value(v2), value(v3)) ...}\n   *\n   * @param field Field to match\n   * @param values Strings to check for\n   * @return The union node\n   */\n  public static QueryNode union(String field, String... values) {\n    return union(field, (Value[]) Arrays.stream(values).map(Values::value).toArray());\n  }\n\n  /**\n   * Create a disjunct node. Disjunct nodes are true iff <b>any</b> of its children are <b>not</b>\n   * true. Conversely, this node evaluates to false if <b>all</b> its children are true.\n   *\n   * @param n Child nodes to add\n   * @return The disjunct node\n   */\n  public static QueryNode disjunct(Node... n) {\n    return new DisjunctNode().add(n);\n  }\n\n  /**\n   * Create a disjunct node using one or more values. The node will evaluate to true iff the field\n   * does not match <b>any</b> of the values.\n   *\n   * @param field Field to check for (empty or null for any field)\n   * @param values The values to check for\n   * @return The node\n   */\n  public static QueryNode disjunct(String field, Value... values) {\n    return new DisjunctNode().add(field, values);\n  }\n\n  /**\n   * Create a disjunct node using one or more values. The node will evaluate to true iff the field\n   * does not match <b>any</b> of the values.\n   *\n   * @param field Field to check for (empty or null for any field)\n   * @param values The values to check for\n   * @return The node\n   */\n  public static QueryNode disjunct(String field, String... values) {\n    return disjunct(field, (Value[]) Arrays.stream(values).map(Values::value).toArray());\n  }\n\n  /**\n   * Create a disjunct union node. This node evaluates to true if <b>all</b> of its children are not\n   * true. Conversely, this node evaluates as false if <b>any</b> of its children are true.\n   *\n   * @param n\n   * @return The node\n   */\n  public static QueryNode disjunctUnion(Node... n) {\n    return new DisjunctUnionNode().add(n);\n  }\n\n  public static QueryNode disjunctUnion(String field, Value... values) {\n    return new DisjunctUnionNode().add(field, values);\n  }\n\n  public static QueryNode disjunctUnion(String field, String... values) {\n    return disjunctUnion(field, (Value[]) Arrays.stream(values).map(Values::value).toArray());\n  }\n\n  /**\n   * Create an optional node. Optional nodes do not affect which results are returned but they\n   * influence ordering and scoring.\n   *\n   * @param n The node to evaluate as optional\n   * @return The new node\n   */\n  public static QueryNode optional(Node... n) {\n    return new OptionalNode().add(n);\n  }\n\n  public static QueryNode optional(String field, Value... values) {\n    return new OptionalNode().add(field, values);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.StringJoiner;\n\npublic abstract class QueryNode implements Node {\n\n  private final List<Node> children = new ArrayList<>();\n\n  protected abstract String getJoinString();\n\n  /**\n   * Add a match criteria to this node\n   *\n   * @param field The field to check. If null or empty, then any field is checked\n   * @param values Values to check for.\n   * @return The current node, for chaining.\n   */\n  public QueryNode add(String field, Value... values) {\n    children.add(new ValueNode(field, getJoinString(), values));\n    return this;\n  }\n\n  /**\n   * Convenience method to add a list of string values\n   *\n   * @param field Field to check for\n   * @param values One or more string values.\n   * @return The current node, for chaining.\n   */\n  public QueryNode add(String field, String... values) {\n    children.add(new ValueNode(field, getJoinString(), values));\n    return this;\n  }\n\n  /**\n   * Add a list of values from a collection\n   *\n   * @param field The field to check\n   * @param values Collection of values to match\n   * @return The current node for chaining.\n   */\n  public QueryNode add(String field, Collection<Value> values) {\n    return add(field, values.toArray(new Value[0]));\n  }\n\n  /**\n   * Add children nodes to this node.\n   *\n   * @param nodes Children nodes to add\n   * @return The current node, for chaining.\n   */\n  public QueryNode add(Node... nodes) {\n    children.addAll(Arrays.asList(nodes));\n    return this;\n  }\n\n  protected boolean shouldParenthesize(Parenthesize mode) {\n    if (mode == Parenthesize.ALWAYS) {\n      return true;\n    } else if (mode == Parenthesize.NEVER) {\n      return false;\n    } else {\n      return children.size() > 1;\n    }\n  }\n\n  @Override\n  public String toString(Parenthesize parenMode) {\n    StringBuilder sb = new StringBuilder();\n    StringJoiner sj = new StringJoiner(getJoinString());\n    if (shouldParenthesize(parenMode)) {\n      sb.append('(');\n    }\n    for (Node n : children) {\n      sj.add(n.toString(parenMode));\n    }\n    sb.append(sj.toString());\n    if (shouldParenthesize(parenMode)) {\n      sb.append(')');\n    }\n    return sb.toString();\n  }\n\n  @Override\n  public String toString() {\n    return toString(Parenthesize.DEFAULT);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * @author mnunberg on 2/23/18.\n */\npublic abstract class RangeValue extends Value {\n\n  private boolean inclusiveMin = true;\n  private boolean inclusiveMax = true;\n\n  @Override\n  public boolean isCombinable() {\n    return false;\n  }\n\n  protected abstract void appendFrom(StringBuilder sb, boolean inclusive);\n\n  protected abstract void appendTo(StringBuilder sb, boolean inclusive);\n\n  @Override\n  public String toString() {\n    StringBuilder sb = new StringBuilder();\n    sb.append('[');\n    appendFrom(sb, inclusiveMin);\n    sb.append(' ');\n    appendTo(sb, inclusiveMax);\n    sb.append(']');\n    return sb.toString();\n  }\n\n  public RangeValue inclusiveMin(boolean val) {\n    inclusiveMin = val;\n    return this;\n  }\n\n  public RangeValue inclusiveMax(boolean val) {\n    inclusiveMax = val;\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/UnionNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * Created by mnunberg on 2/23/18.\n */\npublic class UnionNode extends QueryNode {\n  @Override\n  protected String getJoinString() {\n    return \"|\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/Value.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\n/**\n * Created by mnunberg on 2/23/18.\n */\npublic abstract class Value {\n  public boolean isCombinable() {\n    return false;\n  }\n\n  @Override\n  public abstract String toString();\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\nimport java.util.StringJoiner;\n\n/**\n * Created by mnunberg on 2/23/18.\n */\npublic class ValueNode implements Node {\n\n  private final Value[] values;\n  private final String field;\n  private final String joinString;\n\n  public ValueNode(String field, String joinstr, Value... values) {\n    this.field = field;\n    this.values = values;\n    this.joinString = joinstr;\n  }\n\n  private static Value[] fromStrings(String[] values) {\n    Value[] objs = new Value[values.length];\n    for (int i = 0; i < values.length; i++) {\n      objs[i] = Values.value(values[i]);\n    }\n    return objs;\n  }\n\n  public ValueNode(String field, String joinstr, String... values) {\n    this(field, joinstr, fromStrings(values));\n  }\n\n  private String formatField() {\n    if (field == null || field.isEmpty()) {\n      return \"\";\n    }\n    return '@' + field + ':';\n  }\n\n  private String toStringCombinable(Parenthesize mode) {\n    StringBuilder sb = new StringBuilder(formatField());\n    if (values.length > 1 || mode == Parenthesize.ALWAYS) {\n      sb.append('(');\n    }\n    StringJoiner sj = new StringJoiner(joinString);\n    for (Value v : values) {\n      sj.add(v.toString());\n    }\n    sb.append(sj.toString());\n    if (values.length > 1 || mode == Parenthesize.ALWAYS) {\n      sb.append(')');\n    }\n    return sb.toString();\n  }\n\n  private String toStringDefault(Parenthesize mode) {\n    boolean useParen = mode == Parenthesize.ALWAYS;\n    if (!useParen) {\n      useParen = mode != Parenthesize.NEVER && values.length > 1;\n    }\n    StringBuilder sb = new StringBuilder();\n    if (useParen) {\n      sb.append('(');\n    }\n    StringJoiner sj = new StringJoiner(joinString);\n    for (Value v : values) {\n      sj.add(formatField() + v.toString());\n    }\n    sb.append(sj.toString());\n    if (useParen) {\n      sb.append(')');\n    }\n    return sb.toString();\n  }\n\n  @Override\n  public String toString(Parenthesize mode) {\n    if (values[0].isCombinable()) {\n      return toStringCombinable(mode);\n    }\n    return toStringDefault(mode);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/querybuilder/Values.java",
    "content": "package redis.clients.jedis.search.querybuilder;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.args.GeoUnit;\n\nimport java.util.StringJoiner;\n\n/**\n * Created by mnunberg on 2/23/18.\n */\npublic class Values {\n  private Values() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  private abstract static class ScalableValue extends Value {\n    @Override\n    public boolean isCombinable() {\n      return true;\n    }\n  }\n\n  public static Value value(String s) {\n    return new ScalableValue() {\n      @Override\n      public String toString() {\n        return s;\n      }\n    };\n  }\n\n  public static GeoValue geo(GeoCoordinate coord, double radius, GeoUnit unit) {\n    return new GeoValue(coord.getLongitude(), coord.getLatitude(), radius, unit);\n  }\n\n  public static RangeValue between(double from, double to) {\n    return new DoubleRangeValue(from, to);\n  }\n\n  public static RangeValue between(int from, int to) {\n    return new LongRangeValue(from, to);\n  }\n\n  // TODO: change to simpler [d] available since RedisStack 7.4.0-rc1;\n  // currently kept for backward compatibility\n  public static RangeValue eq(double d) {\n    return new DoubleRangeValue(d, d);\n  }\n\n  // TODO: change to simpler [i] available since RedisStack 7.4.0-rc1;\n  // currently kept for backward compatibility\n  public static RangeValue eq(int i) {\n    return new LongRangeValue(i, i);\n  }\n\n  public static RangeValue lt(double d) {\n    return new DoubleRangeValue(Double.NEGATIVE_INFINITY, d).inclusiveMax(false);\n  }\n\n  public static RangeValue lt(int d) {\n    return new LongRangeValue(Long.MIN_VALUE, d).inclusiveMax(false);\n  }\n\n  public static RangeValue gt(double d) {\n    return new DoubleRangeValue(d, Double.POSITIVE_INFINITY).inclusiveMin(false);\n  }\n\n  public static RangeValue gt(int d) {\n    return new LongRangeValue(d, Long.MAX_VALUE).inclusiveMin(false);\n  }\n\n  public static RangeValue le(double d) {\n    return lt(d).inclusiveMax(true);\n  }\n\n  public static RangeValue le(int d) {\n    return lt(d).inclusiveMax(true);\n  }\n\n  public static RangeValue ge(double d) {\n    return gt(d).inclusiveMin(true);\n  }\n\n  public static RangeValue ge(int d) {\n    return gt(d).inclusiveMin(true);\n  }\n\n  public static Value tags(String... tags) {\n    if (tags.length == 0) {\n      throw new IllegalArgumentException(\"Must have at least one tag\");\n    }\n    StringJoiner sj = new StringJoiner(\" | \");\n    for (String s : tags) {\n      sj.add(s);\n    }\n    return new Value() {\n      @Override\n      public String toString() {\n        return \"{\" + sj.toString() + \"}\";\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/GeoField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.search.FieldName;\n\npublic class GeoField extends SchemaField {\n\n  private boolean indexMissing;\n  private boolean sortable;\n  private boolean noIndex;\n\n  public GeoField(String fieldName) {\n    super(fieldName);\n  }\n\n  public GeoField(FieldName fieldName) {\n    super(fieldName);\n  }\n\n  public static GeoField of(String fieldName) {\n    return new GeoField(fieldName);\n  }\n\n  public static GeoField of(FieldName fieldName) {\n    return new GeoField(fieldName);\n  }\n\n  @Override\n  public GeoField as(String attribute) {\n    super.as(attribute);\n    return this;\n  }\n\n  public GeoField indexMissing() {\n    this.indexMissing = true;\n    return this;\n  }\n\n  public GeoField sortable() {\n    this.sortable = true;\n    return this;\n  }\n\n  public GeoField noIndex() {\n    this.noIndex = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addParams(fieldName);\n    args.add(GEO);\n\n    if (indexMissing) {\n      args.add(INDEXMISSING);\n    }\n\n    if (sortable) {\n      args.add(SORTABLE);\n    }\n\n    if (noIndex) {\n      args.add(NOINDEX);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.search.FieldName;\n\npublic class GeoShapeField extends SchemaField {\n\n  public enum CoordinateSystem {\n\n    /**\n     * For cartesian (X,Y).\n     */\n    FLAT,\n\n    /**\n     * For geographic (lon, lat).\n     */\n    SPHERICAL\n  }\n\n  private final CoordinateSystem system;\n\n  private boolean indexMissing;\n  private boolean noIndex;\n\n  public GeoShapeField(String fieldName, CoordinateSystem system) {\n    super(fieldName);\n    this.system = system;\n  }\n\n  public GeoShapeField(FieldName fieldName, CoordinateSystem system) {\n    super(fieldName);\n    this.system = system;\n  }\n\n  public static GeoShapeField of(String fieldName, CoordinateSystem system) {\n    return new GeoShapeField(fieldName, system);\n  }\n\n  @Override\n  public GeoShapeField as(String attribute) {\n    super.as(attribute);\n    return this;\n  }\n\n  public GeoShapeField indexMissing() {\n    this.indexMissing = true;\n    return this;\n  }\n\n  public GeoShapeField noIndex() {\n    this.noIndex = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addParams(fieldName).add(GEOSHAPE).add(system);\n\n    if (indexMissing) {\n      args.add(INDEXMISSING);\n    }\n\n    if (noIndex) {\n      args.add(NOINDEX);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/NumericField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.search.FieldName;\n\npublic class NumericField extends SchemaField {\n\n  private boolean indexMissing;\n  private boolean sortable;\n  private boolean noIndex;\n\n  public NumericField(String fieldName) {\n    super(fieldName);\n  }\n\n  public NumericField(FieldName fieldName) {\n    super(fieldName);\n  }\n\n  public static NumericField of(String fieldName) {\n    return new NumericField(fieldName);\n  }\n\n  public static NumericField of(FieldName fieldName) {\n    return new NumericField(fieldName);\n  }\n\n  @Override\n  public NumericField as(String attribute) {\n    super.as(attribute);\n    return this;\n  }\n\n  public NumericField indexMissing() {\n    this.indexMissing = true;\n    return this;\n  }\n\n  /**\n   * Sorts the results by the value of this field.\n   */\n  public NumericField sortable() {\n    this.sortable = true;\n    return this;\n  }\n\n  /**\n   * Avoid indexing.\n   */\n  public NumericField noIndex() {\n    this.noIndex = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addParams(fieldName);\n    args.add(NUMERIC);\n\n    if (indexMissing) {\n      args.add(INDEXMISSING);\n    }\n\n    if (sortable) {\n      args.add(SORTABLE);\n    }\n\n    if (noIndex) {\n      args.add(NOINDEX);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/SchemaField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport redis.clients.jedis.params.IParams;\nimport redis.clients.jedis.search.FieldName;\n\npublic abstract class SchemaField implements IParams {\n\n  protected final FieldName fieldName;\n\n  public SchemaField(String fieldName) {\n    this.fieldName = new FieldName(fieldName);\n  }\n\n  public SchemaField(FieldName fieldName) {\n    this.fieldName = fieldName;\n  }\n\n  public SchemaField as(String attribute) {\n    fieldName.as(attribute);\n    return this;\n  }\n\n  public final FieldName getFieldName() {\n    return fieldName;\n  }\n\n  public final String getName() {\n    return fieldName.getName();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/TagField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.search.FieldName;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class TagField extends SchemaField {\n\n  private boolean indexMissing;\n  private boolean indexEmpty;\n  private byte[] separator;\n  private boolean caseSensitive;\n  private boolean withSuffixTrie;\n  private boolean sortable;\n  private boolean sortableUNF;\n  private boolean noIndex;\n\n  public TagField(String fieldName) {\n    super(fieldName);\n  }\n\n  public TagField(FieldName fieldName) {\n    super(fieldName);\n  }\n\n  public static TagField of(String fieldName) {\n    return new TagField(fieldName);\n  }\n\n  public static TagField of(FieldName fieldName) {\n    return new TagField(fieldName);\n  }\n\n  @Override\n  public TagField as(String attribute) {\n    super.as(attribute);\n    return this;\n  }\n\n  public TagField indexMissing() {\n    this.indexMissing = true;\n    return this;\n  }\n\n  public TagField indexEmpty() {\n    this.indexEmpty = true;\n    return this;\n  }\n\n  /**\n   * Indicates how the text contained in the attribute is to be split into individual tags.\n   * @param separator\n   */\n  public TagField separator(char separator) {\n    if (separator < 128) {\n      this.separator = new byte[]{(byte) separator};\n    } else {\n      this.separator = SafeEncoder.encode(String.valueOf(separator));\n    }\n    return this;\n  }\n\n  /**\n   * Keeps the original letter cases of the tags.\n   */\n  public TagField caseSensitive() {\n    this.caseSensitive = true;\n    return this;\n  }\n\n  /**\n   * Keeps a suffix trie with all terms which match the suffix. It is used to optimize\n   * <i>contains</i> and <i>suffix</i> queries.\n   */\n  public TagField withSuffixTrie() {\n    this.withSuffixTrie = true;\n    return this;\n  }\n\n  /**\n   * Sorts the results by the value of this field.\n   */\n  public TagField sortable() {\n    this.sortable = true;\n    return this;\n  }\n\n  /**\n   * Sorts the results by the value of this field without normalization.\n   */\n  public TagField sortableUNF() {\n    this.sortableUNF = true;\n    return this;\n  }\n\n  /**\n   * @deprecated Use {@code TagField#sortableUNF()}.\n   * @see TagField#sortableUNF()\n   */\n  @Deprecated\n  public TagField sortableUnNormalizedForm() {\n    return sortableUNF();\n  }\n\n  /**\n   * Avoid indexing.\n   */\n  public TagField noIndex() {\n    this.noIndex = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addParams(fieldName);\n    args.add(TAG);\n\n    if (indexMissing) {\n      args.add(INDEXMISSING);\n    }\n    if (indexEmpty) {\n      args.add(INDEXEMPTY);\n    }\n\n    if (separator != null) {\n      args.add(SEPARATOR).add(separator);\n    }\n\n    if (caseSensitive) {\n      args.add(CASESENSITIVE);\n    }\n\n    if (withSuffixTrie) {\n      args.add(WITHSUFFIXTRIE);\n    }\n\n    if (sortableUNF) {\n      args.add(SORTABLE).add(UNF);\n    } else if (sortable) {\n      args.add(SORTABLE);\n    }\n\n    if (noIndex) {\n      args.add(NOINDEX);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/TextField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.search.FieldName;\n\npublic class TextField extends SchemaField {\n\n  private boolean indexMissing;\n  private boolean indexEmpty;\n  private Double weight;\n  private boolean noStem;\n  private String phoneticMatcher;\n  private boolean withSuffixTrie;\n  private boolean sortable;\n  private boolean sortableUNF;\n  private boolean noIndex;\n\n  public TextField(String fieldName) {\n    super(fieldName);\n  }\n\n  public TextField(FieldName fieldName) {\n    super(fieldName);\n  }\n\n  public static TextField of(String fieldName) {\n    return new TextField(fieldName);\n  }\n\n  public static TextField of(FieldName fieldName) {\n    return new TextField(fieldName);\n  }\n\n  @Override\n  public TextField as(String attribute) {\n    super.as(attribute);\n    return this;\n  }\n\n  public TextField indexMissing() {\n    this.indexMissing = true;\n    return this;\n  }\n\n  public TextField indexEmpty() {\n    this.indexEmpty = true;\n    return this;\n  }\n\n  /**\n   * Declares the importance of this attribute when calculating result accuracy. This is a\n   * multiplication factor.\n   * @param weight\n   */\n  public TextField weight(double weight) {\n    this.weight = weight;\n    return this;\n  }\n\n  /**\n   * Disable stemming when indexing.\n   */\n  public TextField noStem() {\n    this.noStem = true;\n    return this;\n  }\n\n  /**\n   * Perform phonetic matching.\n   * @param matcher\n   */\n  public TextField phonetic(String matcher) {\n    this.phoneticMatcher = matcher;\n    return this;\n  }\n\n  /**\n   * Keeps a suffix trie with all terms which match the suffix. It is used to optimize\n   * <i>contains</i> and <i>suffix</i> queries.\n   */\n  public TextField withSuffixTrie() {\n    this.withSuffixTrie = true;\n    return this;\n  }\n\n  /**\n   * Sorts the results by the value of this field.\n   */\n  public TextField sortable() {\n    this.sortable = true;\n    return this;\n  }\n\n  /**\n   * Sorts the results by the value of this field without normalization.\n   */\n  public TextField sortableUNF() {\n    this.sortableUNF = true;\n    return this;\n  }\n\n  /**\n   * @deprecated Use {@code TextField#sortableUNF()}.\n   * @see TextField#sortableUNF()\n   */\n  @Deprecated\n  public TextField sortableUnNormalizedForm() {\n    return sortableUNF();\n  }\n\n  /**\n   * Avoid indexing.\n   */\n  public TextField noIndex() {\n    this.noIndex = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addParams(fieldName);\n    args.add(TEXT);\n\n    if (indexMissing) {\n      args.add(INDEXMISSING);\n    }\n    if (indexEmpty) {\n      args.add(INDEXEMPTY);\n    }\n\n    if (weight != null) {\n      args.add(WEIGHT).add(weight);\n    }\n\n    if (noStem) {\n      args.add(NOSTEM);\n    }\n\n    if (phoneticMatcher != null) {\n      args.add(PHONETIC).add(phoneticMatcher);\n    }\n\n    if (withSuffixTrie) {\n      args.add(WITHSUFFIXTRIE);\n    }\n\n    if (sortableUNF) {\n      args.add(SORTABLE).add(UNF);\n    } else if (sortable) {\n      args.add(SORTABLE);\n    }\n\n    if (noIndex) {\n      args.add(NOINDEX);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/search/schemafields/VectorField.java",
    "content": "package redis.clients.jedis.search.schemafields;\n\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.INDEXMISSING;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.VECTOR;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.search.FieldName;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Represents a vector field in a Redis search index schema for performing semantic vector searches.\n * Vector fields enable high-performance similarity searches over vector embeddings using various\n * algorithms and distance metrics.\n *\n * @see <a href=\"https://redis.io/docs/latest/develop/ai/search-and-query/vectors/\">Redis Vector Search Documentation</a>\n */\npublic class VectorField extends SchemaField {\n\n  /**\n   * Enumeration of supported vector indexing algorithms in Redis.\n   * Each algorithm has different performance characteristics and use cases.\n   */\n  public enum VectorAlgorithm implements Rawable {\n\n    /**\n     * FLAT algorithm provides exact vector search with perfect accuracy.\n     * Best suited for smaller datasets (&lt; 1M vectors) where search accuracy\n     * is more important than search latency.\n     */\n    FLAT(\"FLAT\"),\n\n    /**\n     * HNSW (Hierarchical Navigable Small World) algorithm provides approximate\n     * vector search with configurable accuracy-performance trade-offs.\n     * Best suited for larger datasets (&gt; 1M vectors) where search performance\n     * and scalability are more important than perfect accuracy.\n     */\n    HNSW(\"HNSW\"),\n\n    /**\n     * SVS_VAMANA algorithm provides high-performance approximate vector search\n     * optimized for specific use cases with advanced compression and optimization features.\n     *\n     * <p>Characteristics:\n     * <ul>\n     *   <li>High-performance approximate search</li>\n     *   <li>Support for vector compression (LVQ, LeanVec)</li>\n     *   <li>Configurable graph construction and search parameters</li>\n     *   <li>Optimized for Intel platforms with fallback support</li>\n     * </ul>\n     *\n     * <p>Note: This algorithm may have specific requirements and limitations.\n     * Consult the Redis documentation for detailed usage guidelines.\n     */\n    SVS_VAMANA(\"SVS-VAMANA\");\n\n    private final byte[] raw;\n\n    /**\n     * Creates a VectorAlgorithm enum value.\n     *\n     * @param redisParamName the Redis parameter name for this algorithm\n     */\n    VectorAlgorithm(String redisParamName) {\n      raw = SafeEncoder.encode(redisParamName);\n    }\n\n    /**\n     * Returns the raw byte representation of the algorithm name for Redis commands.\n     *\n     * @return the raw bytes of the algorithm name\n     */\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  private final VectorAlgorithm algorithm;\n  private final Map<String, Object> attributes;\n\n  private boolean indexMissing;\n  // private boolean noIndex; // throws Field `NOINDEX` does not have a type\n\n  /**\n   * Creates a new VectorField with the specified field name, algorithm, and attributes.\n   *\n   * @param fieldName the name of the vector field in the index\n   * @param algorithm the vector indexing algorithm to use\n   * @param attributes the algorithm-specific configuration attributes\n   * @throws IllegalArgumentException if required attributes are missing or invalid\n   */\n  public VectorField(String fieldName, VectorAlgorithm algorithm, Map<String, Object> attributes) {\n    super(fieldName);\n    this.algorithm = algorithm;\n    this.attributes = attributes;\n  }\n\n  /**\n   * Creates a new VectorField with the specified field name, algorithm, and attributes.\n   *\n   * @param fieldName the field name object containing the field name and optional alias\n   * @param algorithm the vector indexing algorithm to use\n   * @param attributes the algorithm-specific configuration attributes\n   * @throws IllegalArgumentException if required attributes are missing or invalid\n   * @see #VectorField(String, VectorAlgorithm, Map) for detailed attribute documentation\n   */\n  public VectorField(FieldName fieldName, VectorAlgorithm algorithm, Map<String, Object> attributes) {\n    super(fieldName);\n    this.algorithm = algorithm;\n    this.attributes = attributes;\n  }\n\n  /**\n   * Sets an alias for this field that can be used in queries instead of the field name.\n   * This is useful when the field name contains special characters or when you want\n   * to use a shorter name in queries.\n   *\n   * @param attribute the alias name to use for this field in queries\n   * @return this VectorField instance for method chaining\n   */\n  @Override\n  public VectorField as(String attribute) {\n    super.as(attribute);\n    return this;\n  }\n\n  /**\n   * Configures the field to handle missing values during indexing.\n   * When enabled, documents that don't contain this vector field will still be indexed,\n   * but won't participate in vector searches.\n   *\n   * <p>This is useful when not all documents in your dataset contain vector embeddings,\n   * but you still want to index them for other types of searches.\n   *\n   * @return this VectorField instance for method chaining\n   */\n  public VectorField indexMissing() {\n    this.indexMissing = true;\n    return this;\n  }\n\n  /**\n   * Adds the vector field parameters to the Redis command arguments.\n   * This method is used internally when creating the search index.\n   *\n   * @param args the command arguments to add parameters to\n   */\n  @Override\n  public void addParams(CommandArguments args) {\n    args.addParams(fieldName);\n    args.add(VECTOR);\n\n    args.add(algorithm);\n    args.add(attributes.size() << 1);\n    attributes.forEach((name, value) -> args.add(name).add(value));\n\n    if (indexMissing) {\n      args.add(INDEXMISSING);\n    }\n  }\n\n  /**\n   * Creates a new Builder instance for constructing VectorField objects using the builder pattern.\n   * The builder pattern provides a fluent interface for setting field properties and is especially\n   * useful when dealing with complex vector field configurations.\n   *\n   * @return a new Builder instance\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder class for constructing VectorField instances using the builder pattern.\n   * Provides a fluent interface for setting vector field properties and attributes.\n   *\n   * <p>Example usage:\n   * <pre>{@code\n   * VectorField field = VectorField.builder()\n   *     .fieldName(\"product_embedding\")\n   *     .algorithm(VectorAlgorithm.HNSW)\n   *     .addAttribute(\"TYPE\", \"FLOAT32\")\n   *     .addAttribute(\"DIM\", 768)\n   *     .addAttribute(\"DISTANCE_METRIC\", \"COSINE\")\n   *     .addAttribute(\"M\", 32)\n   *     .addAttribute(\"EF_CONSTRUCTION\", 200)\n   *     .build();\n   * }</pre>\n   */\n  public static class Builder {\n\n    private FieldName fieldName;\n    private VectorAlgorithm algorithm;\n    private Map<String, Object> attributes;\n\n    /**\n     * Private constructor to enforce use of the static builder() method.\n     */\n    private Builder() {\n    }\n\n    /**\n     * Builds and returns a new VectorField instance with the configured properties.\n     *\n     * @return a new VectorField instance\n     * @throws IllegalArgumentException if required parameters (fieldName, algorithm, or attributes) are not set\n     */\n    public VectorField build() {\n      if (fieldName == null || algorithm == null || attributes == null || attributes.isEmpty()) {\n        throw new IllegalArgumentException(\"All required VectorField parameters are not set.\");\n      }\n      return new VectorField(fieldName, algorithm, attributes);\n    }\n\n    /**\n     * Sets the field name for the vector field.\n     *\n     * @param fieldName the name of the vector field in the index\n     * @return this Builder instance for method chaining\n     */\n    public Builder fieldName(String fieldName) {\n      this.fieldName = FieldName.of(fieldName);\n      return this;\n    }\n\n    /**\n     * Sets the field name using a FieldName object.\n     *\n     * @param fieldName the FieldName object containing the field name and optional alias\n     * @return this Builder instance for method chaining\n     */\n    public Builder fieldName(FieldName fieldName) {\n      this.fieldName = fieldName;\n      return this;\n    }\n\n    /**\n     * Sets an alias for the field that can be used in queries.\n     *\n     * @param attribute the alias name to use for this field in queries\n     * @return this Builder instance for method chaining\n     */\n    public Builder as(String attribute) {\n      this.fieldName.as(attribute);\n      return this;\n    }\n\n    /**\n     * Sets the vector indexing algorithm to use.\n     *\n     * @param algorithm the vector algorithm (FLAT, HNSW, or SVS_VAMANA)\n     * @return this Builder instance for method chaining\n     */\n    public Builder algorithm(VectorAlgorithm algorithm) {\n      this.algorithm = algorithm;\n      return this;\n    }\n\n    /**\n     * Sets all vector field attributes at once, replacing any previously set attributes.\n     *\n     * @param attributes a map of attribute names to values\n     * @return this Builder instance for method chaining\n     */\n    public Builder attributes(Map<String, Object> attributes) {\n      this.attributes = attributes;\n      return this;\n    }\n\n    /**\n     * Adds a single attribute to the vector field configuration.\n     * If this is the first attribute added, initializes the attributes map.\n     *\n     * @param name the attribute name\n     * @param value the attribute value\n     * @return this Builder instance for method chaining\n     */\n    public Builder addAttribute(String name, Object value) {\n      if (this.attributes == null) {\n        this.attributes = new LinkedHashMap<>();\n      }\n      this.attributes.put(name, value);\n      return this;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/AggregationType.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.Locale;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic enum AggregationType implements Rawable {\n\n  AVG, SUM, MIN, MAX,\n  RANGE, COUNT, FIRST, LAST,\n  STD_P(\"STD.P\"), STD_S(\"STD.S\"),\n  VAR_P(\"VAR.P\"), VAR_S(\"VAR.S\"),\n  TWA,\n  /**\n   * Count the number of NaN values in the bucket.\n   * @since RedisTimeSeries 8.6.0\n   */\n  COUNTNAN,\n  /**\n   * Count all values in the bucket, including NaN values.\n   * @since RedisTimeSeries 8.6.0\n   */\n  COUNTALL;\n\n  private final byte[] raw;\n\n  private AggregationType() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  private AggregationType(String alt) {\n    raw = SafeEncoder.encode(alt);\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n\n  public static AggregationType safeValueOf(String str) {\n    try {\n      return AggregationType.valueOf(str.replace('.', '_').toUpperCase(Locale.ENGLISH));\n    } catch (IllegalArgumentException iae) {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/DuplicatePolicy.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Policy that will define handling of duplicate samples.\n */\npublic enum DuplicatePolicy implements Rawable {\n\n  /**\n   * An error will occur for any out of order sample\n   */\n  BLOCK,\n  /**\n   * Ignore the new value\n   */\n  FIRST,\n  /**\n   * Override with latest value\n   */\n  LAST,\n  /**\n   * Only override if the value is lower than the existing value\n   */\n  MIN,\n  /**\n   * Only override if the value is higher than the existing value\n   */\n  MAX,\n  /**\n   * If a previous sample exists, add the new sample to it so that the updated value is\n   */\n  SUM;\n\n  private final byte[] raw;\n\n  private DuplicatePolicy() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/EncodingFormat.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Specifies the series samples encoding format.\n */\npublic enum EncodingFormat implements Rawable {\n\n  COMPRESSED,\n  UNCOMPRESSED;\n\n  private final byte[] raw;\n\n  private EncodingFormat() {\n    raw = SafeEncoder.encode(name());\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic interface RedisTimeSeriesCommands {\n\n  /**\n   * {@code TS.CREATE key}\n   *\n   * @param key\n   */\n  String tsCreate(String key);\n\n  /**\n   * {@code TS.CREATE key [RETENTION retentionTime] [ENCODING [UNCOMPRESSED|COMPRESSED]] [CHUNK_SIZE size] [DUPLICATE_POLICY policy] [LABELS label value..]}\n   *\n   * @param key\n   * @param createParams\n   */\n  String tsCreate(String key, TSCreateParams createParams);\n\n  /**\n   * {@code TS.DEL key fromTimestamp toTimestamp}\n   *\n   * @param key\n   * @param fromTimestamp\n   * @param toTimestamp\n   * @return The number of samples that were removed\n   */\n  long tsDel(String key, long fromTimestamp, long toTimestamp);\n\n  /**\n   * {@code TS.ALTER key [RETENTION retentionTime] [LABELS label value..]}\n   *\n   * @param key\n   * @param alterParams\n   * @return OK\n   */\n  String tsAlter(String key, TSAlterParams alterParams);\n\n  /**\n   * {@code TS.ADD key * value}\n   *\n   * @param key\n   * @param value\n   * @return timestamp\n   */\n  long tsAdd(String key, double value);\n\n  /**\n   * {@code TS.ADD key timestamp value}\n   *\n   * @param key\n   * @param timestamp\n   * @param value\n   * @return timestamp\n   */\n  long tsAdd(String key, long timestamp, double value);\n\n  /**\n   * @param key\n   * @param timestamp\n   * @param value\n   * @param createParams\n   * @return timestamp\n   * @deprecated Use {@link RedisTimeSeriesCommands#tsAdd(java.lang.String, long, double, redis.clients.jedis.timeseries.TSAddParams)}.\n   */\n  @Deprecated\n  long tsAdd(String key, long timestamp, double value, TSCreateParams createParams);\n\n  /**\n   * {@code TS.ADD key timestamp value\n   * [RETENTION retentionTime]\n   * [ENCODING <COMPRESSED|UNCOMPRESSED>]\n   * [CHUNK_SIZE size]\n   * [DUPLICATE_POLICY policy]\n   * [ON_DUPLICATE policy_ovr]\n   * [LABELS label value..]}\n   *\n   * @param key\n   * @param timestamp\n   * @param value\n   * @param addParams\n   * @return timestamp\n   */\n  long tsAdd(String key, long timestamp, double value, TSAddParams addParams);\n\n  /**\n   * {@code TS.MADD key timestamp value [key timestamp value ...]}\n   *\n   * @param entries key, timestamp, value\n   * @return timestamps\n   */\n  List<Long> tsMAdd(Map.Entry<String, TSElement>... entries);\n\n  long tsIncrBy(String key, double value);\n\n  long tsIncrBy(String key, double value, long timestamp);\n\n  /**\n   * {@code TS.INCRBY key addend\n   * [TIMESTAMP timestamp]\n   * [RETENTION retentionPeriod]\n   * [ENCODING <COMPRESSED|UNCOMPRESSED>]\n   * [CHUNK_SIZE size]\n   * [DUPLICATE_POLICY policy]\n   * [IGNORE ignoreMaxTimediff ignoreMaxValDiff]\n   * [LABELS [label value ...]]}\n   *\n   * @param key\n   * @param addend\n   * @param incrByParams\n   * @return timestamp\n   */\n  long tsIncrBy(String key, double addend, TSIncrByParams incrByParams);\n\n  long tsDecrBy(String key, double value);\n\n  long tsDecrBy(String key, double value, long timestamp);\n\n  /**\n   * {@code TS.DECRBY key subtrahend\n   * [TIMESTAMP timestamp]\n   * [RETENTION retentionPeriod]\n   * [ENCODING <COMPRESSED|UNCOMPRESSED>]\n   * [CHUNK_SIZE size]\n   * [DUPLICATE_POLICY policy]\n   * [IGNORE ignoreMaxTimediff ignoreMaxValDiff]\n   * [LABELS [label value ...]]}\n   *\n   * @param key\n   * @param subtrahend\n   * @param decrByParams\n   * @return timestamp\n   */\n  long tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams);\n\n  /**\n   * {@code TS.RANGE key fromTimestamp toTimestamp}\n   *\n   * @param key\n   * @param fromTimestamp\n   * @param toTimestamp\n   * @return range elements\n   */\n  List<TSElement> tsRange(String key, long fromTimestamp, long toTimestamp);\n\n  /**\n   * {@code TS.RANGE key fromTimestamp toTimestamp\n   * [LATEST]\n   * [FILTER_BY_TS ts...]\n   * [FILTER_BY_VALUE min max]\n   * [COUNT count] \n   * [[ALIGN value] AGGREGATION aggregator bucketDuration [BUCKETTIMESTAMP bt] [EMPTY]]}\n   *\n   * @param key\n   * @param rangeParams\n   * @return range elements\n   */\n  List<TSElement> tsRange(String key, TSRangeParams rangeParams);\n\n  /**\n   * {@code TS.REVRANGE key fromTimestamp toTimestamp}\n   *\n   * @param key\n   * @param fromTimestamp\n   * @param toTimestamp\n   * @return range elements\n   */\n  List<TSElement> tsRevRange(String key, long fromTimestamp, long toTimestamp);\n\n  /**\n   * {@code TS.REVRANGE key fromTimestamp toTimestamp\n   * [LATEST]\n   * [FILTER_BY_TS TS...]\n   * [FILTER_BY_VALUE min max]\n   * [COUNT count]\n   * [[ALIGN value] AGGREGATION aggregator bucketDuration [BUCKETTIMESTAMP bt] [EMPTY]]}\n   *\n   * @param key\n   * @param rangeParams\n   * @return range elements\n   */\n  List<TSElement> tsRevRange(String key, TSRangeParams rangeParams);\n\n  /**\n   * {@code TS.MRANGE fromTimestamp toTimestamp FILTER filter...}\n   *\n   * @param fromTimestamp\n   * @param toTimestamp\n   * @param filters\n   * @return multi range elements\n   */\n  Map<String, TSMRangeElements> tsMRange(long fromTimestamp, long toTimestamp, String... filters);\n\n  /**\n   * {@code TS.MRANGE fromTimestamp toTimestamp\n   * [LATEST]\n   * [FILTER_BY_TS ts...]\n   * [FILTER_BY_VALUE min max]\n   * [WITHLABELS | SELECTED_LABELS label...]\n   * [COUNT count]\n   * [[ALIGN value] AGGREGATION aggregator bucketDuration [BUCKETTIMESTAMP bt] [EMPTY]]\n   * FILTER filter...\n   * [GROUPBY label REDUCE reducer]}\n   *\n   * @param multiRangeParams\n   * @return multi range elements\n   */\n  Map<String, TSMRangeElements> tsMRange(TSMRangeParams multiRangeParams);\n\n  /**\n   * {@code TS.MREVRANGE fromTimestamp toTimestamp FILTER filter...}\n   *\n   * @param fromTimestamp\n   * @param toTimestamp\n   * @param filters\n   * @return multi range elements\n   */\n  Map<String, TSMRangeElements> tsMRevRange(long fromTimestamp, long toTimestamp, String... filters);\n\n  /**\n   * {@code TS.MREVRANGE fromTimestamp toTimestamp\n   * [LATEST]\n   * [FILTER_BY_TS TS...]\n   * [FILTER_BY_VALUE min max]\n   * [WITHLABELS | SELECTED_LABELS label...]\n   * [COUNT count]\n   * [[ALIGN value] AGGREGATION aggregator bucketDuration [BUCKETTIMESTAMP bt] [EMPTY]]\n   * FILTER filter...\n   * [GROUPBY label REDUCE reducer]}\n   *\n   * @param multiRangeParams\n   * @return multi range elements\n   */\n  Map<String, TSMRangeElements> tsMRevRange(TSMRangeParams multiRangeParams);\n\n  /**\n   * {@code TS.GET key}\n   *\n   * @param key the key\n   * @return the element\n   */\n  TSElement tsGet(String key);\n\n  /**\n   * {@code TS.GET key [LATEST]}\n   *\n   * @param key the key\n   * @param getParams optional arguments\n   * @return the element\n   */\n  TSElement tsGet(String key, TSGetParams getParams);\n\n  /**\n   * {@code TS.MGET [LATEST] [ WITHLABELS | SELECTED_LABELS label...] FILTER filter...}\n   *\n   * @param multiGetParams optional arguments\n   * @param filters secondary indexes\n   * @return multi get elements\n   */\n  Map<String, TSMGetElement> tsMGet(TSMGetParams multiGetParams, String... filters);\n\n  /**\n   * {@code TS.CREATERULE sourceKey destKey AGGREGATION aggregationType timeBucket}\n   *\n   * @param sourceKey\n   * @param destKey\n   * @param aggregationType\n   * @param timeBucket\n   */\n  String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long timeBucket);\n\n  /**\n   * {@code TS.CREATERULE sourceKey destKey AGGREGATION aggregationType bucketDuration [alignTimestamp]}\n   *\n   * @param sourceKey\n   * @param destKey\n   * @param aggregationType\n   * @param bucketDuration\n   * @param alignTimestamp\n   */\n  String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, long alignTimestamp);\n\n  /**\n   * {@code TS.DELETERULE sourceKey destKey}\n   *\n   * @param sourceKey\n   * @param destKey\n   */\n  String tsDeleteRule(String sourceKey, String destKey);\n\n  /**\n   * {@code TS.QUERYINDEX filter...}\n   *\n   * @param filters\n   * @return list of timeseries keys\n   */\n  List<String> tsQueryIndex(String... filters);\n\n  TSInfo tsInfo(String key);\n\n  TSInfo tsInfoDebug(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.Response;\n\npublic interface RedisTimeSeriesPipelineCommands {\n\n  Response<String> tsCreate(String key);\n\n  Response<String> tsCreate(String key, TSCreateParams createParams);\n\n  Response<Long> tsDel(String key, long fromTimestamp, long toTimestamp);\n\n  Response<String> tsAlter(String key, TSAlterParams alterParams);\n\n  Response<Long> tsAdd(String key, double value);\n\n  Response<Long> tsAdd(String key, long timestamp, double value);\n\n  @Deprecated\n  Response<Long> tsAdd(String key, long timestamp, double value, TSCreateParams createParams);\n\n  Response<Long> tsAdd(String key, long timestamp, double value, TSAddParams addParams);\n\n  Response<List<Long>> tsMAdd(Map.Entry<String, TSElement>... entries);\n\n  Response<Long> tsIncrBy(String key, double value);\n\n  Response<Long> tsIncrBy(String key, double value, long timestamp);\n\n  Response<Long> tsIncrBy(String key, double addend, TSIncrByParams incrByParams);\n\n  Response<Long> tsDecrBy(String key, double value);\n\n  Response<Long> tsDecrBy(String key, double value, long timestamp);\n\n  Response<Long> tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams);\n\n  Response<List<TSElement>> tsRange(String key, long fromTimestamp, long toTimestamp);\n\n  Response<List<TSElement>> tsRange(String key, TSRangeParams rangeParams);\n\n  Response<List<TSElement>> tsRevRange(String key, long fromTimestamp, long toTimestamp);\n\n  Response<List<TSElement>> tsRevRange(String key, TSRangeParams rangeParams);\n\n  Response<Map<String, TSMRangeElements>> tsMRange(long fromTimestamp, long toTimestamp, String... filters);\n\n  Response<Map<String, TSMRangeElements>> tsMRange(TSMRangeParams multiRangeParams);\n\n  Response<Map<String, TSMRangeElements>> tsMRevRange(long fromTimestamp, long toTimestamp, String... filters);\n\n  Response<Map<String, TSMRangeElements>> tsMRevRange(TSMRangeParams multiRangeParams);\n\n  Response<TSElement> tsGet(String key);\n\n  Response<TSElement> tsGet(String key, TSGetParams getParams);\n\n  Response<Map<String, TSMGetElement>> tsMGet(TSMGetParams multiGetParams, String... filters);\n\n  Response<String> tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long timeBucket);\n\n  Response<String> tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, long alignTimestamp);\n\n  Response<String> tsDeleteRule(String sourceKey, String destKey);\n\n  Response<List<String>> tsQueryIndex(String... filters);\n\n  Response<TSInfo> tsInfo(String key);\n\n  Response<TSInfo> tsInfoDebug(String key);\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSAddParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.ADD command.\n */\npublic class TSAddParams implements IParams {\n\n  private Long retentionPeriod;\n  private EncodingFormat encoding;\n  private Long chunkSize;\n  private DuplicatePolicy duplicatePolicy;\n  private DuplicatePolicy onDuplicate;\n\n  private boolean ignore;\n  private long ignoreMaxTimediff;\n  private double ignoreMaxValDiff;\n\n  private Map<String, String> labels;\n\n  public TSAddParams() {\n  }\n\n  public static TSAddParams addParams() {\n    return new TSAddParams();\n  }\n\n  public TSAddParams retention(long retentionPeriod) {\n    this.retentionPeriod = retentionPeriod;\n    return this;\n  }\n\n  public TSAddParams encoding(EncodingFormat encoding) {\n    this.encoding = encoding;\n    return this;\n  }\n\n  public TSAddParams chunkSize(long chunkSize) {\n    this.chunkSize = chunkSize;\n    return this;\n  }\n\n  public TSAddParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {\n    this.duplicatePolicy = duplicatePolicy;\n    return this;\n  }\n\n  public TSAddParams onDuplicate(DuplicatePolicy onDuplicate) {\n    this.onDuplicate = onDuplicate;\n    return this;\n  }\n\n  public TSAddParams ignore(long maxTimediff, double maxValDiff) {\n    this.ignore = true;\n    this.ignoreMaxTimediff = maxTimediff;\n    this.ignoreMaxValDiff = maxValDiff;\n    return this;\n  }\n\n  /**\n   * Set label-value pairs\n   *\n   * @param labels label-value pairs\n   * @return the object itself\n   */\n  public TSAddParams labels(Map<String, String> labels) {\n    this.labels = labels;\n    return this;\n  }\n\n  /**\n   * Add label-value pair. Multiple pairs can be added through chaining.\n   * @param label\n   * @param value\n   * @return the object itself\n   */\n  public TSAddParams label(String label, String value) {\n    if (this.labels == null) {\n      this.labels = new LinkedHashMap<>();\n    }\n    this.labels.put(label, value);\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (retentionPeriod != null) {\n      args.add(RETENTION).add(toByteArray(retentionPeriod));\n    }\n\n    if (encoding != null) {\n      args.add(ENCODING).add(encoding);\n    }\n\n    if (chunkSize != null) {\n      args.add(CHUNK_SIZE).add(toByteArray(chunkSize));\n    }\n\n    if (duplicatePolicy != null) {\n      args.add(DUPLICATE_POLICY).add(duplicatePolicy);\n    }\n\n    if (duplicatePolicy != null) {\n      args.add(DUPLICATE_POLICY).add(duplicatePolicy);\n    }\n\n    if (onDuplicate != null) {\n      args.add(ON_DUPLICATE).add(onDuplicate);\n    }\n\n    if (ignore) {\n      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);\n    }\n\n    if (labels != null) {\n      args.add(LABELS);\n      labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSAddParams that = (TSAddParams) o;\n    return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff &&\n        Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 &&\n        Objects.equals(retentionPeriod, that.retentionPeriod) &&\n        encoding == that.encoding && Objects.equals(chunkSize, that.chunkSize) &&\n        duplicatePolicy == that.duplicatePolicy && onDuplicate == that.onDuplicate &&\n        Objects.equals(labels, that.labels);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hashCode(retentionPeriod);\n    result = 31 * result + Objects.hashCode(encoding);\n    result = 31 * result + Objects.hashCode(chunkSize);\n    result = 31 * result + Objects.hashCode(duplicatePolicy);\n    result = 31 * result + Objects.hashCode(onDuplicate);\n    result = 31 * result + Boolean.hashCode(ignore);\n    result = 31 * result + Long.hashCode(ignoreMaxTimediff);\n    result = 31 * result + Double.hashCode(ignoreMaxValDiff);\n    result = 31 * result + Objects.hashCode(labels);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;\n\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.ALTER command.\n */\npublic class TSAlterParams implements IParams {\n\n  private Long retentionPeriod;\n  private Long chunkSize;\n  private DuplicatePolicy duplicatePolicy;\n\n  private boolean ignore;\n  private long ignoreMaxTimediff;\n  private double ignoreMaxValDiff;\n\n  private Map<String, String> labels;\n\n  public TSAlterParams() {\n  }\n\n  public static TSAlterParams alterParams() {\n    return new TSAlterParams();\n  }\n\n  public TSAlterParams retention(long retentionPeriod) {\n    this.retentionPeriod = retentionPeriod;\n    return this;\n  }\n\n  public TSAlterParams chunkSize(long chunkSize) {\n    this.chunkSize = chunkSize;\n    return this;\n  }\n\n  public TSAlterParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {\n    this.duplicatePolicy = duplicatePolicy;\n    return this;\n  }\n\n  public TSAlterParams ignore(long maxTimediff, double maxValDiff) {\n    this.ignore = true;\n    this.ignoreMaxTimediff = maxTimediff;\n    this.ignoreMaxValDiff = maxValDiff;\n    return this;\n  }\n\n  /**\n   * Set label-value pairs\n   *\n   * @param labels label-value pairs\n   * @return the object itself\n   */\n  public TSAlterParams labels(Map<String, String> labels) {\n    this.labels = labels;\n    return this;\n  }\n\n  /**\n   * Add label-value pair. Multiple pairs can be added through chaining.\n   * @param label\n   * @param value\n   * @return the object itself\n   */\n  public TSAlterParams label(String label, String value) {\n    if (this.labels == null) {\n      this.labels = new LinkedHashMap<>();\n    }\n    this.labels.put(label, value);\n    return this;\n  }\n\n  public TSAlterParams labelsReset() {\n    return this.labels(Collections.emptyMap());\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (retentionPeriod != null) {\n      args.add(RETENTION).add(toByteArray(retentionPeriod));\n    }\n\n    if (chunkSize != null) {\n      args.add(CHUNK_SIZE).add(toByteArray(chunkSize));\n    }\n\n    if (duplicatePolicy != null) {\n      args.add(DUPLICATE_POLICY).add(duplicatePolicy);\n    }\n\n    if (ignore) {\n      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);\n    }\n\n    if (labels != null) {\n      args.add(LABELS);\n      labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSAlterParams that = (TSAlterParams) o;\n    return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff &&\n        Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 &&\n        Objects.equals(retentionPeriod, that.retentionPeriod) &&\n        Objects.equals(chunkSize, that.chunkSize) &&\n        duplicatePolicy == that.duplicatePolicy && Objects.equals(labels, that.labels);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hashCode(retentionPeriod);\n    result = 31 * result + Objects.hashCode(chunkSize);\n    result = 31 * result + Objects.hashCode(duplicatePolicy);\n    result = 31 * result + Boolean.hashCode(ignore);\n    result = 31 * result + Long.hashCode(ignoreMaxTimediff);\n    result = 31 * result + Double.hashCode(ignoreMaxValDiff);\n    result = 31 * result + Objects.hashCode(labels);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.INCRBY or TS.DECRBY commands.\n */\nclass TSArithByParams<T extends TSArithByParams<?>> implements IParams {\n\n  private Long timestamp;\n  private Long retentionPeriod;\n  private EncodingFormat encoding;\n  private Long chunkSize;\n  private DuplicatePolicy duplicatePolicy;\n\n  private boolean ignore;\n  private long ignoreMaxTimediff;\n  private double ignoreMaxValDiff;\n\n  private Map<String, String> labels;\n\n  TSArithByParams() {\n  }\n\n  public T timestamp(long timestamp) {\n    this.timestamp = timestamp;\n    return (T) this;\n  }\n\n  public T retention(long retentionPeriod) {\n    this.retentionPeriod = retentionPeriod;\n    return (T) this;\n  }\n\n  public T encoding(EncodingFormat encoding) {\n    this.encoding = encoding;\n    return (T) this;\n  }\n\n  public T chunkSize(long chunkSize) {\n    this.chunkSize = chunkSize;\n    return (T) this;\n  }\n\n  public T duplicatePolicy(DuplicatePolicy duplicatePolicy) {\n    this.duplicatePolicy = duplicatePolicy;\n    return (T) this;\n  }\n\n  public T ignore(long maxTimediff, double maxValDiff) {\n    this.ignore = true;\n    this.ignoreMaxTimediff = maxTimediff;\n    this.ignoreMaxValDiff = maxValDiff;\n    return (T) this;\n  }\n\n  /**\n   * Set label-value pairs\n   *\n   * @param labels label-value pairs\n   * @return the object itself\n   */\n  public T labels(Map<String, String> labels) {\n    this.labels = labels;\n    return (T) this;\n  }\n\n  /**\n   * Add label-value pair. Multiple pairs can be added through chaining.\n   * @param label\n   * @param value\n   * @return the object itself\n   */\n  public T label(String label, String value) {\n    if (this.labels == null) {\n      this.labels = new LinkedHashMap<>();\n    }\n    this.labels.put(label, value);\n    return (T) this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (timestamp != null) {\n      args.add(TIMESTAMP).add(timestamp);\n    }\n\n    if (retentionPeriod != null) {\n      args.add(RETENTION).add(toByteArray(retentionPeriod));\n    }\n\n    if (encoding != null) {\n      args.add(ENCODING).add(encoding);\n    }\n\n    if (chunkSize != null) {\n      args.add(CHUNK_SIZE).add(toByteArray(chunkSize));\n    }\n\n    if (duplicatePolicy != null) {\n      args.add(DUPLICATE_POLICY).add(duplicatePolicy);\n    }\n\n    if (ignore) {\n      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);\n    }\n\n    if (labels != null) {\n      args.add(LABELS);\n      labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSArithByParams<?> that = (TSArithByParams<?>) o;\n    return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff &&\n        Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 &&\n        Objects.equals(timestamp, that.timestamp) &&\n        Objects.equals(retentionPeriod, that.retentionPeriod) &&\n        encoding == that.encoding && Objects.equals(chunkSize, that.chunkSize) &&\n        duplicatePolicy == that.duplicatePolicy && Objects.equals(labels, that.labels);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hashCode(timestamp);\n    result = 31 * result + Objects.hashCode(retentionPeriod);\n    result = 31 * result + Objects.hashCode(encoding);\n    result = 31 * result + Objects.hashCode(chunkSize);\n    result = 31 * result + Objects.hashCode(duplicatePolicy);\n    result = 31 * result + Boolean.hashCode(ignore);\n    result = 31 * result + Long.hashCode(ignoreMaxTimediff);\n    result = 31 * result + Double.hashCode(ignoreMaxValDiff);\n    result = 31 * result + Objects.hashCode(labels);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.CREATE command.\n */\npublic class TSCreateParams implements IParams {\n\n  private Long retentionPeriod;\n  private EncodingFormat encoding;\n  private Long chunkSize;\n  private DuplicatePolicy duplicatePolicy;\n\n  private boolean ignore;\n  private long ignoreMaxTimediff;\n  private double ignoreMaxValDiff;\n\n  private Map<String, String> labels;\n\n  public TSCreateParams() {\n  }\n\n  public static TSCreateParams createParams() {\n    return new TSCreateParams();\n  }\n\n  public TSCreateParams retention(long retentionPeriod) {\n    this.retentionPeriod = retentionPeriod;\n    return this;\n  }\n\n  // TODO: deprecate\n  public TSCreateParams uncompressed() {\n    return encoding(EncodingFormat.UNCOMPRESSED);\n  }\n\n  // TODO: deprecate\n  public TSCreateParams compressed() {\n    return encoding(EncodingFormat.COMPRESSED);\n  }\n\n  public TSCreateParams encoding(EncodingFormat encoding) {\n    this.encoding = encoding;\n    return this;\n  }\n\n  public TSCreateParams chunkSize(long chunkSize) {\n    this.chunkSize = chunkSize;\n    return this;\n  }\n\n  public TSCreateParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {\n    this.duplicatePolicy = duplicatePolicy;\n    return this;\n  }\n\n  public TSCreateParams ignore(long maxTimediff, double maxValDiff) {\n    this.ignore = true;\n    this.ignoreMaxTimediff = maxTimediff;\n    this.ignoreMaxValDiff = maxValDiff;\n    return this;\n  }\n\n  /**\n   * Set label-value pairs\n   *\n   * @param labels label-value pairs\n   * @return the object itself\n   */\n  public TSCreateParams labels(Map<String, String> labels) {\n    this.labels = labels;\n    return this;\n  }\n\n  /**\n   * Add label-value pair. Multiple pairs can be added through chaining.\n   * @param label\n   * @param value\n   * @return the object itself\n   */\n  public TSCreateParams label(String label, String value) {\n    if (this.labels == null) {\n      this.labels = new LinkedHashMap<>();\n    }\n    this.labels.put(label, value);\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (retentionPeriod != null) {\n      args.add(RETENTION).add(toByteArray(retentionPeriod));\n    }\n\n    if (encoding != null) {\n      args.add(ENCODING).add(encoding);\n    }\n\n    if (chunkSize != null) {\n      args.add(CHUNK_SIZE).add(toByteArray(chunkSize));\n    }\n\n    if (duplicatePolicy != null) {\n      args.add(DUPLICATE_POLICY).add(duplicatePolicy);\n    }\n\n    if (ignore) {\n      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);\n    }\n\n    if (labels != null) {\n      args.add(LABELS);\n      labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSCreateParams that = (TSCreateParams) o;\n    return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff &&\n        Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 &&\n        Objects.equals(retentionPeriod, that.retentionPeriod) &&\n        encoding == that.encoding && Objects.equals(chunkSize, that.chunkSize) &&\n        duplicatePolicy == that.duplicatePolicy && Objects.equals(labels, that.labels);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hashCode(retentionPeriod);\n    result = 31 * result + Objects.hashCode(encoding);\n    result = 31 * result + Objects.hashCode(chunkSize);\n    result = 31 * result + Objects.hashCode(duplicatePolicy);\n    result = 31 * result + Boolean.hashCode(ignore);\n    result = 31 * result + Long.hashCode(ignoreMaxTimediff);\n    result = 31 * result + Double.hashCode(ignoreMaxValDiff);\n    result = 31 * result + Objects.hashCode(labels);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSDecrByParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\n/**\n * Represents optional arguments of TS.DECRBY command.\n */\npublic class TSDecrByParams extends TSArithByParams<TSDecrByParams> {\n\n  public TSDecrByParams() {\n  }\n\n  public static TSDecrByParams decrByParams() {\n    return new TSDecrByParams();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSElement.java",
    "content": "package redis.clients.jedis.timeseries;\n\npublic class TSElement {\n\n  private final long timestamp;\n  private final double value;\n\n  public TSElement(long timestamp, double value) {\n    this.timestamp = timestamp;\n    this.value = value;\n  }\n\n  public long getTimestamp() {\n    return timestamp;\n  }\n\n  public double getValue() {\n    return value;\n  }\n\n  @Override\n  public int hashCode() {\n    return 31 * Long.hashCode(timestamp) + Long.hashCode(Double.doubleToLongBits(value));\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null) return false;\n    if (obj == this) return true;\n    if (!(obj instanceof TSElement)) return false;\n\n    TSElement other = (TSElement) obj;\n    return this.timestamp == other.timestamp\n        && this.value == other.value;\n  }\n\n  @Override\n  public String toString() {\n    return \"(\" + timestamp + \":\" + value + \")\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSGetParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.LATEST;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.GET command.\n */\npublic class TSGetParams implements IParams {\n\n  private boolean latest;\n\n  public static TSGetParams getParams() {\n    return new TSGetParams();\n  }\n\n  public TSGetParams latest() {\n    this.latest = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (latest) {\n      args.add(LATEST);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSGetParams that = (TSGetParams) o;\n    return latest == that.latest;\n  }\n\n  @Override\n  public int hashCode() {\n    return Boolean.hashCode(latest);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSIncrByParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\n/**\n * Represents optional arguments of TS.INCRBY command.\n */\npublic class TSIncrByParams extends TSArithByParams<TSIncrByParams> {\n\n  public TSIncrByParams() {\n  }\n\n  public static TSIncrByParams incrByParams() {\n    return new TSIncrByParams();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSInfo.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.util.DoublePrecision;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class TSInfo {\n\n  private static final String DUPLICATE_POLICY_PROPERTY = \"duplicatePolicy\";\n  private static final String LABELS_PROPERTY = \"labels\";\n  private static final String RULES_PROPERTY = \"rules\";\n  private static final String CHUNKS_PROPERTY = \"Chunks\";\n  private static final String CHUNKS_BYTES_PER_SAMPLE_PROPERTY = \"bytesPerSample\";\n\n  private final Map<String, Object> properties;\n  private final Map<String, String> labels;\n  private final Map<String, Rule> rules;\n  private final List<Map<String, Object>> chunks;\n\n  private TSInfo(Map<String, Object> properties, Map<String, String> labels, Map<String, Rule> rules, List<Map<String, Object>> chunks) {\n    this.properties = properties;\n    this.labels = labels;\n    this.rules = rules;\n    this.chunks = chunks;\n  }\n\n  public Map<String, Object> getProperties() {\n    return properties;\n  }\n\n  public Object getProperty(String property) {\n    return properties.get(property);\n  }\n\n  public Long getIntegerProperty(String property) {\n    return (Long) properties.get(property);\n  }\n\n  public Map<String, String> getLabels() {\n    return labels;\n  }\n\n  public String getLabel(String label) {\n    return labels.get(label);\n  }\n\n  public Map<String, Rule> getRules() {\n    return rules;\n  }\n\n  public Rule getRule(String rule) {\n    return rules.get(rule);\n  }\n\n  public List<Map<String, Object>> getChunks() {\n    return chunks;\n  }\n\n  public static Builder<TSInfo> TIMESERIES_INFO = new Builder<TSInfo>() {\n    @Override\n    public TSInfo build(Object data) {\n      List<Object> list = (List<Object>) data;\n      Map<String, Object> properties = new HashMap<>();\n      Map<String, String> labels = null;\n      Map<String, Rule> rules = null;\n      List<Map<String, Object>> chunks = null;\n\n      for (int i = 0; i < list.size(); i += 2) {\n        String prop = SafeEncoder.encode((byte[]) list.get(i));\n        Object value = list.get(i + 1);\n        if (value instanceof List) {\n          switch (prop) {\n            case LABELS_PROPERTY:\n              labels = BuilderFactory.STRING_MAP_FROM_PAIRS.build(value);\n              value = labels;\n              break;\n            case RULES_PROPERTY:\n              List<Object> rulesDataList = (List<Object>) value;\n              List<List<Object>> rulesValueList = new ArrayList<>(rulesDataList.size());\n              rules = new HashMap<>(rulesDataList.size());\n              for (Object ruleData : rulesDataList) {\n                List<Object> encodedRule = (List<Object>) SafeEncoder.encodeObject(ruleData);\n                rulesValueList.add(encodedRule);\n                rules.put((String) encodedRule.get(0), new Rule((String) encodedRule.get(0), (Long) encodedRule.get(1),\n                    AggregationType.safeValueOf((String) encodedRule.get(2)), (Long) encodedRule.get(3)));\n              }\n              value = rulesValueList;\n              break;\n            case CHUNKS_PROPERTY:\n              List<Object> chunksDataList = (List<Object>) value;\n              List<Map<String, Object>> chunksValueList = new ArrayList<>(chunksDataList.size());\n              chunks = new ArrayList<>(chunksDataList.size());\n              for (Object chunkData : chunksDataList) {\n                Map<String, Object> chunk = BuilderFactory.ENCODED_OBJECT_MAP.build(chunkData);\n                chunksValueList.add(new HashMap<>(chunk));\n                if (chunk.containsKey(CHUNKS_BYTES_PER_SAMPLE_PROPERTY)) {\n                  chunk.put(CHUNKS_BYTES_PER_SAMPLE_PROPERTY,\n                      DoublePrecision.parseEncodedFloatingPointNumber(chunk.get(CHUNKS_BYTES_PER_SAMPLE_PROPERTY)));\n                }\n                chunks.add(chunk);\n              }\n              value = chunksValueList;\n              break;\n            default:\n              value = SafeEncoder.encodeObject(value);\n              break;\n          }\n        } else if (value instanceof byte[]) {\n          value = SafeEncoder.encode((byte[]) value);\n          if (DUPLICATE_POLICY_PROPERTY.equals(prop)) {\n            try {\n              value = DuplicatePolicy.valueOf(((String) value).toUpperCase());\n            } catch (Exception e) { }\n          }\n        }\n        properties.put(prop, value);\n      }\n\n      return new TSInfo(properties, labels, rules, chunks);\n    }\n  };\n\n  public static Builder<TSInfo> TIMESERIES_INFO_RESP3 = new Builder<TSInfo>() {\n    @Override\n    public TSInfo build(Object data) {\n      List<KeyValue> list = (List<KeyValue>) data;\n      Map<String, Object> properties = new HashMap<>();\n      Map<String, String> labels = null;\n      Map<String, Rule> rules = null;\n      List<Map<String, Object>> chunks = null;\n\n      for (KeyValue propertyValue : list) {\n        String prop = BuilderFactory.STRING.build(propertyValue.getKey());\n        Object value = propertyValue.getValue();\n        if (value instanceof List) {\n          switch (prop) {\n            case LABELS_PROPERTY:\n              labels = BuilderFactory.STRING_MAP.build(value);\n              value = labels;\n              break;\n            case RULES_PROPERTY:\n              List<KeyValue> rulesDataList = (List<KeyValue>) value;\n              Map<String, List<Object>> rulesValueMap = new HashMap<>(rulesDataList.size(), 1f);\n              rules = new HashMap<>(rulesDataList.size());\n              for (KeyValue rkv : rulesDataList) {\n                String ruleName = BuilderFactory.STRING.build(rkv.getKey());\n                List<Object> ruleValueList = BuilderFactory.ENCODED_OBJECT_LIST.build(rkv.getValue());\n                rulesValueMap.put(ruleName, ruleValueList);\n                rules.put(ruleName, new Rule(ruleName, ruleValueList));\n              }\n              value = rulesValueMap;\n              break;\n            case CHUNKS_PROPERTY:\n              List<List<KeyValue>> chunksDataList = (List<List<KeyValue>>) value;\n              List<Map<String, Object>> chunksValueList = new ArrayList<>(chunksDataList.size());\n              chunks = new ArrayList<>(chunksDataList.size());\n              for (List<KeyValue> chunkDataAsList : chunksDataList) {\n                Map<String, Object> chunk = chunkDataAsList.stream()\n                    .collect(Collectors.toMap(kv -> BuilderFactory.STRING.build(kv.getKey()),\n                        kv -> BuilderFactory.ENCODED_OBJECT.build(kv.getValue())));\n                chunksValueList.add(chunk);\n                chunks.add(chunk);\n              }\n              value = chunksValueList;\n              break;\n            default:\n              value = SafeEncoder.encodeObject(value);\n              break;\n          }\n        } else if (value instanceof byte[]) {\n          value = BuilderFactory.STRING.build(value);\n          if (DUPLICATE_POLICY_PROPERTY.equals(prop)) {\n            try {\n              value = DuplicatePolicy.valueOf(((String) value).toUpperCase());\n            } catch (Exception e) { }\n          }\n        }\n        properties.put(prop, value);\n      }\n\n      return new TSInfo(properties, labels, rules, chunks);\n    }\n  };\n\n  public static class Rule {\n\n    private final String compactionKey;\n    private final long bucketDuration;\n    private final AggregationType aggregator;\n    private final long alignmentTimestamp;\n\n    private Rule(String compaction, List<Object> encodedValues) {\n      this(compaction, (Long) encodedValues.get(0),\n          AggregationType.safeValueOf((String) encodedValues.get(1)),\n          (Long) encodedValues.get(2));\n    }\n\n    private Rule(String compaction, long bucket, AggregationType aggregation, long alignment) {\n      this.compactionKey = compaction;\n      this.bucketDuration = bucket;\n      this.aggregator = aggregation;\n      this.alignmentTimestamp = alignment;\n    }\n\n    public String getCompactionKey() {\n      return compactionKey;\n    }\n\n    public long getBucketDuration() {\n      return bucketDuration;\n    }\n\n    public AggregationType getAggregator() {\n      return aggregator;\n    }\n\n    public long getAlignmentTimestamp() {\n      return alignmentTimestamp;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSMGetElement.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.Map;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class TSMGetElement extends KeyValue<String, TSElement> {\n\n  private final Map<String, String> labels;\n\n  public TSMGetElement(String key, Map<String, String> labels, TSElement value) {\n    super(key, value);\n    this.labels = labels;\n  }\n\n  public Map<String, String> getLabels() {\n    return labels;\n  }\n\n  public TSElement getElement() {\n    return getValue();\n  }\n\n  @Override\n  public String toString() {\n    return new StringBuilder().append(getClass().getSimpleName())\n        .append(\"{key=\").append(getKey())\n        .append(\", labels=\").append(labels)\n        .append(\", element=\").append(getElement())\n        .append('}').toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSMGetParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.LATEST;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.SELECTED_LABELS;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.WITHLABELS;\n\nimport java.util.Arrays;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.MGET command.\n */\npublic class TSMGetParams implements IParams {\n\n  private boolean latest;\n\n  private boolean withLabels;\n  private String[] selectedLabels;\n\n  public static TSMGetParams multiGetParams() {\n    return new TSMGetParams();\n  }\n\n  public TSMGetParams latest() {\n    this.latest = true;\n    return this;\n  }\n\n  public TSMGetParams withLabels(boolean withLabels) {\n    this.withLabels = withLabels;\n    return this;\n  }\n\n  public TSMGetParams withLabels() {\n    return withLabels(true);\n  }\n\n  public TSMGetParams selectedLabels(String... labels) {\n    this.selectedLabels = labels;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n    if (latest) {\n      args.add(LATEST);\n    }\n\n    if (withLabels) {\n      args.add(WITHLABELS);\n    } else if (selectedLabels != null) {\n      args.add(SELECTED_LABELS);\n      for (String label : selectedLabels) {\n        args.add(label);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSMGetParams that = (TSMGetParams) o;\n    return latest == that.latest && withLabels == that.withLabels &&\n        Arrays.equals(selectedLabels, that.selectedLabels);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Boolean.hashCode(latest);\n    result = 31 * result + Boolean.hashCode(withLabels);\n    result = 31 * result + Arrays.hashCode(selectedLabels);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSMRangeElements.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.List;\nimport java.util.Map;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class TSMRangeElements extends KeyValue<String, List<TSElement>> {\n\n  private final Map<String, String> labels;\n  private final List<AggregationType> aggregators;\n  private final List<String> reducers;\n  private final List<String> sources;\n\n  public TSMRangeElements(String key, Map<String, String> labels, List<TSElement> value) {\n    super(key, value);\n    this.labels = labels;\n    this.aggregators = null;\n    this.reducers = null;\n    this.sources = null;\n  }\n\n  public TSMRangeElements(String key, Map<String, String> labels, List<AggregationType> aggregators, List<TSElement> value) {\n    super(key, value);\n    this.labels = labels;\n    this.aggregators = aggregators;\n    this.reducers = null;\n    this.sources = null;\n  }\n\n  public TSMRangeElements(String key, Map<String, String> labels, List<String> reducers, List<String> sources, List<TSElement> value) {\n    super(key, value);\n    this.labels = labels;\n    this.aggregators = null;\n    this.reducers = reducers;\n    this.sources = sources;\n  }\n\n  public Map<String, String> getLabels() {\n    return labels;\n  }\n\n  public List<AggregationType> getAggregators() {\n    return aggregators;\n  }\n\n  public List<String> getReducers() {\n    return reducers;\n  }\n\n  public List<String> getSources() {\n    return sources;\n  }\n\n  public List<TSElement> getElements() {\n    return getValue();\n  }\n\n  @Override\n  public String toString() {\n    StringBuilder sb = new StringBuilder().append(getClass().getSimpleName())\n        .append(\"{key=\").append(getKey()).append(\", labels=\").append(labels);\n    if (aggregators != null) {\n      sb.append(\", aggregators=\").append(aggregators);\n    }\n    if (reducers != null && sources != null) {\n      sb.append(\", reducers\").append(reducers).append(\", sources\").append(sources);\n    }\n    return sb.append(\", elements=\").append(getElements()).append('}').toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.Protocol.BYTES_TILDE;\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.MINUS;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.PLUS;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;\nimport static redis.clients.jedis.util.SafeEncoder.encode;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.MRANGE and TS.MREVRANGE commands.\n */\npublic class TSMRangeParams implements IParams {\n\n  private Long fromTimestamp;\n  private Long toTimestamp;\n\n  private boolean latest;\n\n  private long[] filterByTimestamps;\n  private double[] filterByValues;\n\n  private boolean withLabels;\n  private String[] selectedLabels;\n\n  private Integer count;\n\n  private byte[] align;\n\n  private AggregationType aggregationType;\n  private long bucketDuration;\n  private byte[] bucketTimestamp;\n\n  private boolean empty;\n\n  private String[] filters;\n\n  private String groupByLabel;\n  private String groupByReduce;\n\n  public TSMRangeParams(long fromTimestamp, long toTimestamp) {\n    this.fromTimestamp = fromTimestamp;\n    this.toTimestamp = toTimestamp;\n  }\n\n  public static TSMRangeParams multiRangeParams(long fromTimestamp, long toTimestamp) {\n    return new TSMRangeParams(fromTimestamp, toTimestamp);\n  }\n\n  public TSMRangeParams() {\n  }\n\n  public static TSMRangeParams multiRangeParams() {\n    return new TSMRangeParams();\n  }\n\n  public TSMRangeParams fromTimestamp(long fromTimestamp) {\n    this.fromTimestamp = fromTimestamp;\n    return this;\n  }\n\n  public TSMRangeParams toTimestamp(long toTimestamp) {\n    this.toTimestamp = toTimestamp;\n    return this;\n  }\n\n  public TSMRangeParams latest() {\n    this.latest = true;\n    return this;\n  }\n\n  public TSMRangeParams filterByTS(long... timestamps) {\n    this.filterByTimestamps = timestamps;\n    return this;\n  }\n\n  public TSMRangeParams filterByValues(double min, double max) {\n    this.filterByValues = new double[] {min, max};\n    return this;\n  }\n\n  public TSMRangeParams withLabels(boolean withLabels) {\n    this.withLabels = withLabels;\n    return this;\n  }\n\n  public TSMRangeParams withLabels() {\n    return withLabels(true);\n  }\n\n  public TSMRangeParams selectedLabels(String... labels) {\n    this.selectedLabels = labels;\n    return this;\n  }\n\n  public TSMRangeParams count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  private TSMRangeParams align(byte[] raw) {\n    this.align = raw;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams align(long timestamp) {\n    return align(toByteArray(timestamp));\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams alignStart() {\n    return align(MINUS);\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams alignEnd() {\n    return align(PLUS);\n  }\n\n  public TSMRangeParams aggregation(AggregationType aggregationType, long bucketDuration) {\n    this.aggregationType = aggregationType;\n    this.bucketDuration = bucketDuration;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams bucketTimestamp(String bucketTimestamp) {\n    this.bucketTimestamp = encode(bucketTimestamp);\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams bucketTimestampLow() {\n    this.bucketTimestamp = MINUS;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams bucketTimestampHigh() {\n    this.bucketTimestamp = PLUS;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams bucketTimestampMid() {\n    this.bucketTimestamp = BYTES_TILDE;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSMRangeParams empty() {\n    this.empty = true;\n    return this;\n  }\n\n  public TSMRangeParams filter(String... filters) {\n    this.filters = filters;\n    return this;\n  }\n\n  public TSMRangeParams groupBy(String label, String reduce) {\n    this.groupByLabel = label;\n    this.groupByReduce = reduce;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (filters == null) {\n      throw new IllegalArgumentException(\"FILTER arguments must be set.\");\n    }\n\n    if (fromTimestamp == null) {\n      args.add(MINUS);\n    } else {\n      args.add(toByteArray(fromTimestamp));\n    }\n\n    if (toTimestamp == null) {\n      args.add(PLUS);\n    } else {\n      args.add(toByteArray(toTimestamp));\n    }\n\n    if (latest) {\n      args.add(LATEST);\n    }\n\n    if (filterByTimestamps != null) {\n      args.add(FILTER_BY_TS);\n      for (long ts : filterByTimestamps) {\n        args.add(toByteArray(ts));\n      }\n    }\n\n    if (filterByValues != null) {\n      args.add(FILTER_BY_VALUE);\n      for (double value : filterByValues) {\n        args.add(toByteArray(value));\n      }\n    }\n\n    if (withLabels) {\n      args.add(WITHLABELS);\n    } else if (selectedLabels != null) {\n      args.add(SELECTED_LABELS);\n      for (String label : selectedLabels) {\n        args.add(label);\n      }\n    }\n\n    if (count != null) {\n      args.add(COUNT).add(toByteArray(count));\n    }\n\n    if (aggregationType != null) {\n\n      if (align != null) {\n        args.add(ALIGN).add(align);\n      }\n\n      args.add(AGGREGATION).add(aggregationType).add(toByteArray(bucketDuration));\n\n      if (bucketTimestamp != null) {\n        args.add(BUCKETTIMESTAMP).add(bucketTimestamp);\n      }\n\n      if (empty) {\n        args.add(EMPTY);\n      }\n    }\n\n    args.add(FILTER);\n    for (String filter : filters) {\n      args.add(filter);\n    }\n\n    if (groupByLabel != null && groupByReduce != null) {\n      args.add(GROUPBY).add(groupByLabel).add(REDUCE).add(groupByReduce);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSMRangeParams that = (TSMRangeParams) o;\n    return latest == that.latest && withLabels == that.withLabels &&\n        bucketDuration == that.bucketDuration && empty == that.empty &&\n        Objects.equals(fromTimestamp, that.fromTimestamp) &&\n        Objects.equals(toTimestamp, that.toTimestamp) &&\n        Arrays.equals(filterByTimestamps, that.filterByTimestamps) &&\n        Arrays.equals(filterByValues, that.filterByValues) &&\n        Arrays.equals(selectedLabels, that.selectedLabels) &&\n        Objects.equals(count, that.count) && Arrays.equals(align, that.align) &&\n        aggregationType == that.aggregationType &&\n        Arrays.equals(bucketTimestamp, that.bucketTimestamp) &&\n        Arrays.equals(filters, that.filters) &&\n        Objects.equals(groupByLabel, that.groupByLabel) &&\n        Objects.equals(groupByReduce, that.groupByReduce);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hashCode(fromTimestamp);\n    result = 31 * result + Objects.hashCode(toTimestamp);\n    result = 31 * result + Boolean.hashCode(latest);\n    result = 31 * result + Arrays.hashCode(filterByTimestamps);\n    result = 31 * result + Arrays.hashCode(filterByValues);\n    result = 31 * result + Boolean.hashCode(withLabels);\n    result = 31 * result + Arrays.hashCode(selectedLabels);\n    result = 31 * result + Objects.hashCode(count);\n    result = 31 * result + Arrays.hashCode(align);\n    result = 31 * result + Objects.hashCode(aggregationType);\n    result = 31 * result + Long.hashCode(bucketDuration);\n    result = 31 * result + Arrays.hashCode(bucketTimestamp);\n    result = 31 * result + Boolean.hashCode(empty);\n    result = 31 * result + Arrays.hashCode(filters);\n    result = 31 * result + Objects.hashCode(groupByLabel);\n    result = 31 * result + Objects.hashCode(groupByReduce);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport static redis.clients.jedis.Protocol.BYTES_TILDE;\nimport static redis.clients.jedis.Protocol.toByteArray;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.MINUS;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.PLUS;\nimport static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;\nimport static redis.clients.jedis.util.SafeEncoder.encode;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.params.IParams;\n\n/**\n * Represents optional arguments of TS.RANGE and TS.REVRANGE commands.\n */\npublic class TSRangeParams implements IParams {\n\n  private Long fromTimestamp;\n  private Long toTimestamp;\n\n  private boolean latest;\n\n  private long[] filterByTimestamps;\n  private double[] filterByValues;\n\n  private Integer count;\n\n  private byte[] align;\n\n  private AggregationType aggregationType;\n  private long bucketDuration;\n  private byte[] bucketTimestamp;\n\n  private boolean empty;\n\n  public TSRangeParams(long fromTimestamp, long toTimestamp) {\n    this.fromTimestamp = fromTimestamp;\n    this.toTimestamp = toTimestamp;\n  }\n\n  public static TSRangeParams rangeParams(long fromTimestamp, long toTimestamp) {\n    return new TSRangeParams(fromTimestamp, toTimestamp);\n  }\n\n  public TSRangeParams() {\n  }\n\n  public static TSRangeParams rangeParams() {\n    return new TSRangeParams();\n  }\n\n  public TSRangeParams fromTimestamp(long fromTimestamp) {\n    this.fromTimestamp = fromTimestamp;\n    return this;\n  }\n\n  public TSRangeParams toTimestamp(long toTimestamp) {\n    this.toTimestamp = toTimestamp;\n    return this;\n  }\n\n  public TSRangeParams latest() {\n    this.latest = true;\n    return this;\n  }\n\n  public TSRangeParams filterByTS(long... timestamps) {\n    this.filterByTimestamps = timestamps;\n    return this;\n  }\n\n  public TSRangeParams filterByValues(double min, double max) {\n    this.filterByValues = new double[]{min, max};\n    return this;\n  }\n\n  public TSRangeParams count(int count) {\n    this.count = count;\n    return this;\n  }\n\n  private TSRangeParams align(byte[] raw) {\n    this.align = raw;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams align(long timestamp) {\n    return align(toByteArray(timestamp));\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams alignStart() {\n    return align(MINUS);\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams alignEnd() {\n    return align(PLUS);\n  }\n\n  public TSRangeParams aggregation(AggregationType aggregationType, long bucketDuration) {\n    this.aggregationType = aggregationType;\n    this.bucketDuration = bucketDuration;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams bucketTimestamp(String bucketTimestamp) {\n    this.bucketTimestamp = encode(bucketTimestamp);\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams bucketTimestampLow() {\n    this.bucketTimestamp = MINUS;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams bucketTimestampHigh() {\n    this.bucketTimestamp = PLUS;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams bucketTimestampMid() {\n    this.bucketTimestamp = BYTES_TILDE;\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSRangeParams empty() {\n    this.empty = true;\n    return this;\n  }\n\n  @Override\n  public void addParams(CommandArguments args) {\n\n    if (fromTimestamp == null) {\n      args.add(MINUS);\n    } else {\n      args.add(toByteArray(fromTimestamp));\n    }\n\n    if (toTimestamp == null) {\n      args.add(PLUS);\n    } else {\n      args.add(toByteArray(toTimestamp));\n    }\n\n    if (latest) {\n      args.add(LATEST);\n    }\n\n    if (filterByTimestamps != null) {\n      args.add(FILTER_BY_TS);\n      for (long ts : filterByTimestamps) {\n        args.add(toByteArray(ts));\n      }\n    }\n\n    if (filterByValues != null) {\n      args.add(FILTER_BY_VALUE);\n      for (double value : filterByValues) {\n        args.add(toByteArray(value));\n      }\n    }\n\n    if (count != null) {\n      args.add(COUNT).add(toByteArray(count));\n    }\n\n    if (aggregationType != null) {\n\n      if (align != null) {\n        args.add(ALIGN).add(align);\n      }\n\n      args.add(AGGREGATION).add(aggregationType).add(toByteArray(bucketDuration));\n\n      if (bucketTimestamp != null) {\n        args.add(BUCKETTIMESTAMP).add(bucketTimestamp);\n      }\n\n      if (empty) {\n        args.add(EMPTY);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    TSRangeParams that = (TSRangeParams) o;\n    return latest == that.latest && bucketDuration == that.bucketDuration && empty == that.empty &&\n        Objects.equals(fromTimestamp, that.fromTimestamp) &&\n        Objects.equals(toTimestamp, that.toTimestamp) &&\n        Arrays.equals(filterByTimestamps, that.filterByTimestamps) &&\n        Arrays.equals(filterByValues, that.filterByValues) &&\n        Objects.equals(count, that.count) && Arrays.equals(align, that.align) &&\n        aggregationType == that.aggregationType &&\n        Arrays.equals(bucketTimestamp, that.bucketTimestamp);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hashCode(fromTimestamp);\n    result = 31 * result + Objects.hashCode(toTimestamp);\n    result = 31 * result + Boolean.hashCode(latest);\n    result = 31 * result + Arrays.hashCode(filterByTimestamps);\n    result = 31 * result + Arrays.hashCode(filterByValues);\n    result = 31 * result + Objects.hashCode(count);\n    result = 31 * result + Arrays.hashCode(align);\n    result = 31 * result + Objects.hashCode(aggregationType);\n    result = 31 * result + Long.hashCode(bucketDuration);\n    result = 31 * result + Arrays.hashCode(bucketTimestamp);\n    result = 31 * result + Boolean.hashCode(empty);\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TimeSeriesBuilderFactory.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.util.KeyValue;\n\npublic final class TimeSeriesBuilderFactory {\n\n  public static final Builder<TSElement> TIMESERIES_ELEMENT = new Builder<TSElement>() {\n    @Override\n    public TSElement build(Object data) {\n      List<Object> list = (List<Object>) data;\n      if (list == null || list.isEmpty()) return null;\n      return new TSElement(BuilderFactory.LONG.build(list.get(0)), BuilderFactory.DOUBLE.build(list.get(1)));\n    }\n  };\n\n  public static final Builder<List<TSElement>> TIMESERIES_ELEMENT_LIST = new Builder<List<TSElement>>() {\n    @Override\n    public List<TSElement> build(Object data) {\n      return ((List<Object>) data).stream().map((pairObject) -> (List<Object>) pairObject)\n          .map((pairList) -> new TSElement(BuilderFactory.LONG.build(pairList.get(0)),\n              BuilderFactory.DOUBLE.build(pairList.get(1))))\n          .collect(Collectors.toList());\n    }\n  };\n\n  public static final Builder<Map<String, TSMRangeElements>> TIMESERIES_MRANGE_RESPONSE\n      = new Builder<Map<String, TSMRangeElements>>() {\n    @Override\n    public Map<String, TSMRangeElements> build(Object data) {\n      return ((List<Object>) data).stream().map((tsObject) -> (List<Object>) tsObject)\n          .map((tsList) -> new TSMRangeElements(BuilderFactory.STRING.build(tsList.get(0)),\n              BuilderFactory.STRING_MAP_FROM_PAIRS.build(tsList.get(1)),\n              TIMESERIES_ELEMENT_LIST.build(tsList.get(2))))\n          .collect(Collectors.toMap(TSMRangeElements::getKey, Function.identity(),\n              (x, y) -> x, LinkedHashMap::new));\n    }\n  };\n\n  public static final Builder<Map<String, TSMRangeElements>> TIMESERIES_MRANGE_RESPONSE_RESP3\n      = new Builder<Map<String, TSMRangeElements>>() {\n    @Override\n    public Map<String, TSMRangeElements> build(Object data) {\n      List<KeyValue> dataList = (List<KeyValue>) data;\n      Map<String, TSMRangeElements> map = new LinkedHashMap<>(dataList.size() / 2, 1f);\n      for (KeyValue kv : dataList) {\n        String key = BuilderFactory.STRING.build(kv.getKey());\n        List<Object> valueList = (List<Object>) kv.getValue();\n        TSMRangeElements elements;\n        switch (valueList.size()) {\n          case 3:\n            List<Object> aggrMapObj = (List<Object>) valueList.get(1);\n            KeyValue aggKV = (KeyValue) aggrMapObj.get(0);\n            assert \"aggregators\".equalsIgnoreCase(BuilderFactory.STRING.build(aggKV.getKey()));\n            elements = new TSMRangeElements(key,\n                BuilderFactory.STRING_MAP.build(valueList.get(0)),\n                ((List<Object>) aggKV.getValue()).stream().map(BuilderFactory.STRING::build)\n                    .map(AggregationType::safeValueOf).collect(Collectors.toList()),\n                TIMESERIES_ELEMENT_LIST.build(valueList.get(2)));\n            break;\n          case 4:\n            List<KeyValue> rdcMapObj = (List<KeyValue>) valueList.get(1);\n            assert \"reducers\".equalsIgnoreCase(BuilderFactory.STRING.build(rdcMapObj.get(0).getKey()));\n            List<KeyValue> srcMapObj = (List<KeyValue>) valueList.get(2);\n            assert \"sources\".equalsIgnoreCase(BuilderFactory.STRING.build(srcMapObj.get(0).getKey()));\n            elements = new TSMRangeElements(key,\n                BuilderFactory.STRING_MAP.build(valueList.get(0)),\n                BuilderFactory.STRING_LIST.build(rdcMapObj.get(0).getValue()),\n                BuilderFactory.STRING_LIST.build(srcMapObj.get(0).getValue()),\n                TIMESERIES_ELEMENT_LIST.build(valueList.get(3)));\n            break;\n          default:\n            throw new IllegalStateException();\n        }\n        map.put(key, elements);\n      }\n      return map;\n    }\n  };\n\n  public static final Builder<Map<String, TSMGetElement>> TIMESERIES_MGET_RESPONSE\n      = new Builder<Map<String, TSMGetElement>>() {\n    @Override\n    public Map<String, TSMGetElement> build(Object data) {\n      return ((List<Object>) data).stream().map((tsObject) -> (List<Object>) tsObject)\n          .map((tsList) -> new TSMGetElement(BuilderFactory.STRING.build(tsList.get(0)),\n              BuilderFactory.STRING_MAP_FROM_PAIRS.build(tsList.get(1)),\n              TIMESERIES_ELEMENT.build(tsList.get(2))))\n          .collect(Collectors.toMap(TSMGetElement::getKey, Function.identity()));\n    }\n  };\n\n  public static final Builder<Map<String, TSMGetElement>> TIMESERIES_MGET_RESPONSE_RESP3\n      = new Builder<Map<String, TSMGetElement>>() {\n    @Override\n    public Map<String, TSMGetElement> build(Object data) {\n      List<KeyValue> dataList = (List<KeyValue>) data;\n      Map<String, TSMGetElement> map = new LinkedHashMap<>(dataList.size());\n      for (KeyValue kv : dataList) {\n        String key = BuilderFactory.STRING.build(kv.getKey());\n        List<Object> valueList = (List<Object>) kv.getValue();\n        TSMGetElement value = new TSMGetElement(key,\n            BuilderFactory.STRING_MAP.build(valueList.get(0)),\n            TIMESERIES_ELEMENT.build(valueList.get(1)));\n        map.put(key, value);\n      }\n      return map;\n    }\n  };\n\n  private TimeSeriesBuilderFactory() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/TimeSeriesProtocol.java",
    "content": "package redis.clients.jedis.timeseries;\n\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class TimeSeriesProtocol {\n\n  public static final byte[] PLUS = SafeEncoder.encode(\"+\");\n  public static final byte[] MINUS = SafeEncoder.encode(\"-\");\n\n  public enum TimeSeriesCommand implements ProtocolCommand {\n\n    CREATE(\"TS.CREATE\"),\n    RANGE(\"TS.RANGE\"),\n    REVRANGE(\"TS.REVRANGE\"),\n    MRANGE(\"TS.MRANGE\"),\n    MREVRANGE(\"TS.MREVRANGE\"),\n    CREATERULE(\"TS.CREATERULE\"),\n    DELETERULE(\"TS.DELETERULE\"),\n    ADD(\"TS.ADD\"),\n    MADD(\"TS.MADD\"),\n    DEL(\"TS.DEL\"),\n    INCRBY(\"TS.INCRBY\"),\n    DECRBY(\"TS.DECRBY\"),\n    INFO(\"TS.INFO\"),\n    GET(\"TS.GET\"),\n    MGET(\"TS.MGET\"),\n    ALTER(\"TS.ALTER\"),\n    QUERYINDEX(\"TS.QUERYINDEX\");\n\n    private final byte[] raw;\n\n    private TimeSeriesCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public enum TimeSeriesKeyword implements Rawable {\n\n    RESET,\n    FILTER,\n    AGGREGATION,\n    LABELS,\n    RETENTION,\n    TIMESTAMP,\n    WITHLABELS,\n    SELECTED_LABELS,\n    COUNT,\n    ENCODING,\n    COMPRESSED,\n    UNCOMPRESSED,\n    CHUNK_SIZE,\n    DUPLICATE_POLICY,\n    IGNORE,\n    ON_DUPLICATE,\n    ALIGN,\n    FILTER_BY_TS,\n    FILTER_BY_VALUE,\n    GROUPBY,\n    REDUCE,\n    DEBUG,\n    LATEST,\n    EMPTY,\n    BUCKETTIMESTAMP;\n\n    private final byte[] raw;\n\n    private TimeSeriesKeyword() {\n      raw = SafeEncoder.encode(name());\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/timeseries/package-info.java",
    "content": "/**\n * This package contains the classes and interfaces related to RedisTimeSeries module.\n */\npackage redis.clients.jedis.timeseries;\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/ByteArrayComparator.java",
    "content": "package redis.clients.jedis.util;\n\npublic final class ByteArrayComparator {\n  private ByteArrayComparator() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  public static int compare(final byte[] val1, final byte[] val2) {\n    int len1 = val1.length;\n    int len2 = val2.length;\n    int lmin = Math.min(len1, len2);\n\n    for (int i = 0; i < lmin; i++) {\n      byte b1 = val1[i];\n      byte b2 = val2[i];\n      if (b1 < b2) return -1;\n      if (b1 > b2) return 1;\n    }\n\n    if (len1 < len2) return -1;\n    if (len1 > len2) return 1;\n    return 0;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/CompareCondition.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol.Keyword;\nimport redis.clients.jedis.annots.Experimental;\n\n/**\n * A compare condition to be used with commands that support conditional value checks (e.g. SET with\n * IFEQ/IFNE/IFDEQ/IFDNE and DELEX). This abstraction lets callers express value-based or\n * digest-based comparisons.\n * <p>\n * Digest-based comparisons use a 64-bit XXH3 digest represented as a 16-character lower-case\n * hexadecimal string.\n * </p>\n */\n@Experimental\npublic final class CompareCondition {\n\n  /**\n   * The kind of condition represented by this instance.\n   */\n  public enum Condition {\n    /** current value must equal provided value */\n    VALUE_EQUAL(Keyword.IFEQ),\n    /** current value must not equal provided value */\n    VALUE_NOT_EQUAL(Keyword.IFNE),\n    /** current value's digest must equal provided digest */\n    DIGEST_EQUAL(Keyword.IFDEQ),\n    /** current value's digest must not equal provided digest */\n    DIGEST_NOT_EQUAL(Keyword.IFDNE);\n\n    private final Keyword keyword;\n\n    Condition(Keyword keyword) {\n      this.keyword = keyword;\n    }\n\n    /** The protocol keyword to emit for this condition. */\n    public Keyword getKeyword() {\n      return keyword;\n    }\n  }\n\n  private final Condition condition;\n  private final Object payload; // String or byte[]\n\n  private CompareCondition(Condition condition, Object payload) {\n    if (!(payload instanceof String) && !(payload instanceof byte[])) {\n      throw new IllegalArgumentException(\"payload must be String or byte[]\");\n    }\n    this.condition = condition;\n    this.payload = payload;\n  }\n\n  // Factory methods: value-based\n  public static CompareCondition valueEq(String value) {\n    JedisAsserts.notNull(value, \"value must not be null\");\n    return new CompareCondition(Condition.VALUE_EQUAL, value);\n  }\n\n  public static CompareCondition valueNe(String value) {\n    JedisAsserts.notNull(value, \"value must not be null\");\n    return new CompareCondition(Condition.VALUE_NOT_EQUAL, value);\n  }\n\n  public static CompareCondition valueEq(byte[] value) {\n    JedisAsserts.notNull(value, \"value must not be null\");\n    return new CompareCondition(Condition.VALUE_EQUAL, value);\n  }\n\n  public static CompareCondition valueNe(byte[] value) {\n    JedisAsserts.notNull(value, \"value must not be null\");\n    return new CompareCondition(Condition.VALUE_NOT_EQUAL, value);\n  }\n\n  // Factory methods: digest-based\n  public static CompareCondition digestEq(String hex16) {\n    JedisAsserts.notNull(hex16, \"digest must not be null\");\n    return new CompareCondition(Condition.DIGEST_EQUAL, hex16);\n  }\n\n  public static CompareCondition digestNe(String hex16) {\n    JedisAsserts.notNull(hex16, \"digest must not be null\");\n    return new CompareCondition(Condition.DIGEST_NOT_EQUAL, hex16);\n  }\n\n  public static CompareCondition digestEq(byte[] digest) {\n    JedisAsserts.notNull(digest, \"digest must not be null\");\n    return new CompareCondition(Condition.DIGEST_EQUAL, digest);\n  }\n\n  public static CompareCondition digestNe(byte[] digest) {\n    JedisAsserts.notNull(digest, \"digest must not be null\");\n    return new CompareCondition(Condition.DIGEST_NOT_EQUAL, digest);\n  }\n\n  /**\n   * Append this condition to the command arguments by emitting the appropriate keyword and payload.\n   */\n  public void addTo(CommandArguments args) {\n    args.add(condition.getKeyword()).add(payload);\n  }\n\n  /** The kind of this condition. */\n  public Condition getCondition() {\n    return condition;\n  }\n\n  /** The payload for this condition (String or byte[]). */\n  public Object getPayload() {\n    return payload;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    CompareCondition that = (CompareCondition) o;\n    if (condition != that.condition) return false;\n    // Handle byte[] comparison\n    if (payload instanceof byte[] && that.payload instanceof byte[]) {\n      return Arrays.equals((byte[]) payload, (byte[]) that.payload);\n    }\n    return Objects.equals(payload, that.payload);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = Objects.hash(condition);\n    if (payload instanceof byte[]) {\n      result = 31 * result + Arrays.hashCode((byte[]) payload);\n    } else {\n      result = 31 * result + Objects.hashCode(payload);\n    }\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"CompareCondition{\" + \"condition=\" + condition\n        + (payload != null ? \", payload=\" + payload : \"\") + '}';\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/Delay.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.time.Duration;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic abstract class Delay {\n\n  protected Delay() {\n  }\n\n  /**\n   * Calculate a specific delay based on the attempt.\n   * @param attempt the attempt to calculate the delay from.\n   * @return the calculated delay.\n   */\n  public abstract Duration delay(long attempt);\n\n  /**\n   * Creates a constant delay.\n   * @param delay the constant delay duration\n   * @return a Delay that always returns the same duration\n   */\n  public static Delay constant(Duration delay) {\n    return new ConstantDelay(delay);\n  }\n\n  /**\n   * Creates an exponential delay with equal jitter. Based on AWS exponential backoff strategy:\n   * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ Formula: temp =\n   * min(upper, base * 2^attempt) sleep = temp/2 + random_between(0, temp/2) result = max(lower,\n   * sleep)\n   * @param lower the minimum delay duration (lower bound)\n   * @param upper the maximum delay duration (upper bound)\n   * @param base the base delay duration\n   * @return a Delay with exponential backoff and equal jitter\n   */\n  public static Delay exponentialWithJitter(Duration lower, Duration upper, Duration base) {\n    return new EqualJitterDelay(lower, upper, base);\n  }\n\n  static class ConstantDelay extends Delay {\n\n    private final Duration delay;\n\n    ConstantDelay(Duration delay) {\n      this.delay = delay;\n    }\n\n    @Override\n    public Duration delay(long attempt) {\n      return delay;\n    }\n  }\n\n  static class EqualJitterDelay extends Delay {\n\n    private final long lowerMillis;\n    private final long upperMillis;\n    private final long baseMillis;\n\n    EqualJitterDelay(Duration lower, Duration upper, Duration base) {\n      this.lowerMillis = lower.toMillis();\n      this.upperMillis = upper.toMillis();\n      this.baseMillis = base.toMillis();\n    }\n\n    @Override\n    public Duration delay(long attempt) {\n      // temp = min(upper, base * 2^attempt)\n      long exponential = baseMillis * (1L << Math.min(attempt, 62));\n      long temp = Math.min(upperMillis, exponential);\n\n      // sleep = temp/2 + random_between(0, temp/2)\n      long half = temp / 2;\n      long jitter = ThreadLocalRandom.current().nextLong(half + 1);\n      long delayMillis = half + jitter;\n\n      // Apply lower bound\n      delayMillis = Math.max(lowerMillis, delayMillis);\n\n      return Duration.ofMillis(delayMillis);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/DoublePrecision.java",
    "content": "package redis.clients.jedis.util;\n\npublic final class DoublePrecision {\n\n  private DoublePrecision() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  public static Double parseFloatingPointNumber(String str) throws NumberFormatException {\n\n    if (str == null) return null;\n\n    try {\n\n      return Double.valueOf(str);\n\n    } catch (NumberFormatException e) {\n\n      switch (str) {\n\n        case \"inf\":\n        case \"+inf\":\n          return Double.POSITIVE_INFINITY;\n\n        case \"-inf\":\n          return Double.NEGATIVE_INFINITY;\n\n        case \"nan\":\n        case \"-nan\": // for some module commands // TODO: remove\n          return Double.NaN;\n\n        default:\n          throw e;\n      }\n    }\n  }\n\n  public static Double parseEncodedFloatingPointNumber(Object val) throws NumberFormatException {\n    if (val == null) return null;\n    else if (val instanceof Double) return (Double) val;\n    else return parseFloatingPointNumber((String) val);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/IOUtils.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.io.IOException;\nimport java.net.Socket;\n\npublic class IOUtils {\n\n  public static void closeQuietly(Socket sock) {\n    // It's same thing as Apache Commons - IOUtils.closeQuietly()\n    if (sock != null) {\n      try {\n        sock.close();\n      } catch (IOException e) {\n        // ignored\n      }\n    }\n  }\n\n  public static void closeQuietly(AutoCloseable resource) {\n    // It's same thing as Apache Commons - IOUtils.closeQuietly()\n    if (resource != null) {\n      try {\n        resource.close();\n      } catch (Exception e) {\n        // ignored\n      }\n    }\n  }\n\n  private IOUtils() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisAsserts.java",
    "content": "package redis.clients.jedis.util;\n\n/**\n * Assertion utility class that assists in validating arguments. This class is part of the internal API and may change without\n * further notice.\n *\n * @author ivo.gaydazhiev\n */\npublic class JedisAsserts {\n\n  /**\n   * Assert that an object is not {@code null} .\n   *\n   * @param object the object to check\n   * @param message the exception message to use if the assertion fails\n   * @throws IllegalArgumentException if the object is {@code null}\n   */\n  public static void notNull(Object object, String message) {\n    if (object == null) {\n      throw new IllegalArgumentException(message);\n    }\n  }\n\n  /**\n   * Assert that {@code value} is {@code true}.\n   *\n   * @param value the value to check\n   * @param message the exception message to use if the assertion fails\n   * @throws IllegalArgumentException if the value is {@code false}\n   */\n  public static void isTrue(boolean value, String message) {\n    if (!value) {\n      throw new IllegalArgumentException(message);\n    }\n  }\n\n  /**\n   * Assert that {@code value} is {@code false}.\n   *\n   * @param value the value to check\n   * @param message the exception message to use if the assertion fails\n   * @throws IllegalArgumentException if the value is {@code true}\n   */\n    public static void isFalse(boolean value, String message) {\n      if (value) {\n        throw new IllegalArgumentException(message);\n      }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisByteHashMap.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.io.Serializable;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class JedisByteHashMap implements Map<byte[], byte[]>, Cloneable, Serializable {\n  private static final long serialVersionUID = -6971431362627219416L;\n  private final Map<ByteArrayWrapper, byte[]> internalMap = new LinkedHashMap<>();\n\n  @Override\n  public void clear() {\n    internalMap.clear();\n  }\n\n  @Override\n  public boolean containsKey(Object key) {\n    if (key instanceof byte[]) return internalMap.containsKey(new ByteArrayWrapper((byte[]) key));\n    return internalMap.containsKey(key);\n  }\n\n  @Override\n  public boolean containsValue(Object value) {\n    return internalMap.containsValue(value);\n  }\n\n  @Override\n  public Set<java.util.Map.Entry<byte[], byte[]>> entrySet() {\n    Iterator<java.util.Map.Entry<ByteArrayWrapper, byte[]>> iterator = internalMap.entrySet()\n        .iterator();\n    LinkedHashSet<Entry<byte[], byte[]>> hashSet = new LinkedHashSet<>();\n    while (iterator.hasNext()) {\n      Entry<ByteArrayWrapper, byte[]> entry = iterator.next();\n      hashSet.add(new JedisByteEntry(entry.getKey().data, entry.getValue()));\n    }\n    return hashSet;\n  }\n\n  @Override\n  public byte[] get(Object key) {\n    if (key instanceof byte[]) return internalMap.get(new ByteArrayWrapper((byte[]) key));\n    return internalMap.get(key);\n  }\n\n  @Override\n  public boolean isEmpty() {\n    return internalMap.isEmpty();\n  }\n\n  @Override\n  public Set<byte[]> keySet() {\n    Set<byte[]> keySet = new LinkedHashSet<>();\n    Iterator<ByteArrayWrapper> iterator = internalMap.keySet().iterator();\n    while (iterator.hasNext()) {\n      keySet.add(iterator.next().data);\n    }\n    return keySet;\n  }\n\n  @Override\n  public byte[] put(byte[] key, byte[] value) {\n    return internalMap.put(new ByteArrayWrapper(key), value);\n  }\n\n  @Override\n  @SuppressWarnings(\"unchecked\")\n  public void putAll(Map<? extends byte[], ? extends byte[]> m) {\n    Iterator<?> iterator = m.entrySet().iterator();\n    while (iterator.hasNext()) {\n      Entry<? extends byte[], ? extends byte[]> next = (Entry<? extends byte[], ? extends byte[]>) iterator\n          .next();\n      internalMap.put(new ByteArrayWrapper(next.getKey()), next.getValue());\n    }\n  }\n\n  @Override\n  public byte[] remove(Object key) {\n    if (key instanceof byte[]) return internalMap.remove(new ByteArrayWrapper((byte[]) key));\n    return internalMap.remove(key);\n  }\n\n  @Override\n  public int size() {\n    return internalMap.size();\n  }\n\n  @Override\n  public Collection<byte[]> values() {\n    return internalMap.values();\n  }\n\n  private static final class ByteArrayWrapper implements Serializable {\n    private static final long serialVersionUID = 1L;\n    private final byte[] data;\n    private final int hashCode;\n\n    public ByteArrayWrapper(byte[] data) {\n      if (data == null) {\n        throw new NullPointerException();\n      }\n      this.data = data;\n      this.hashCode = Arrays.hashCode(data);\n    }\n\n    @Override\n    public boolean equals(Object other) {\n      if (other == null) return false;\n      if (other == this) return true;\n      if (!(other instanceof ByteArrayWrapper)) return false;\n\n      return Arrays.equals(data, ((ByteArrayWrapper) other).data);\n    }\n\n    @Override\n    public int hashCode() {\n      return hashCode;\n    }\n  }\n\n  private static final class JedisByteEntry implements Entry<byte[], byte[]> {\n    private byte[] value;\n    private byte[] key;\n\n    public JedisByteEntry(byte[] key, byte[] value) {\n      this.key = key;\n      this.value = value;\n    }\n\n    @Override\n    public byte[] getKey() {\n      return this.key;\n    }\n\n    @Override\n    public byte[] getValue() {\n      return this.value;\n    }\n\n    @Override\n    public byte[] setValue(byte[] value) {\n      this.value = value;\n      return value;\n    }\n\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisByteMap.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.io.Serializable;\nimport java.util.*;\n\npublic class JedisByteMap<T> implements Map<byte[], T>, Cloneable, Serializable {\n    private static final long serialVersionUID = -6971431362627219416L;\n    private final Map<ByteArrayWrapper, T> internalMap = new LinkedHashMap<>();\n\n    @Override\n    public void clear() {\n        internalMap.clear();\n    }\n\n    @Override\n    public boolean containsKey(Object key) {\n        if (key instanceof byte[]) return internalMap.containsKey(new ByteArrayWrapper((byte[]) key));\n        return internalMap.containsKey(key);\n    }\n\n    @Override\n    public boolean containsValue(Object value) {\n        return internalMap.containsValue(value);\n    }\n\n    @Override\n    public Set<Entry<byte[], T>> entrySet() {\n        Iterator<Entry<ByteArrayWrapper, T>> iterator = internalMap.entrySet()\n                .iterator();\n        LinkedHashSet<Entry<byte[], T>> hashSet = new LinkedHashSet<>();\n        while (iterator.hasNext()) {\n            Entry<ByteArrayWrapper, T> entry = iterator.next();\n            hashSet.add(new JedisByteEntry(entry.getKey().data, entry.getValue()));\n        }\n        return hashSet;\n    }\n\n    @Override\n    public T get(Object key) {\n        if (key instanceof byte[]) return internalMap.get(new ByteArrayWrapper((byte[]) key));\n        return internalMap.get(key);\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return internalMap.isEmpty();\n    }\n\n    @Override\n    public Set<byte[]> keySet() {\n        Set<byte[]> keySet = new LinkedHashSet<>();\n        Iterator<ByteArrayWrapper> iterator = internalMap.keySet().iterator();\n        while (iterator.hasNext()) {\n            keySet.add(iterator.next().data);\n        }\n        return keySet;\n    }\n\n    @Override\n    public T put(byte[] key, T value) {\n        return internalMap.put(new ByteArrayWrapper(key), value);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void putAll(Map<? extends byte[], ? extends T> m) {\n        Iterator<?> iterator = m.entrySet().iterator();\n        while (iterator.hasNext()) {\n            Entry<? extends byte[], ? extends T> next = (Entry<? extends byte[], ? extends T>) iterator\n                    .next();\n            internalMap.put(new ByteArrayWrapper(next.getKey()), next.getValue());\n        }\n    }\n\n    @Override\n    public T remove(Object key) {\n        if (key instanceof byte[]) return internalMap.remove(new ByteArrayWrapper((byte[]) key));\n        return internalMap.remove(key);\n    }\n\n    @Override\n    public int size() {\n        return internalMap.size();\n    }\n\n    @Override\n    public Collection<T> values() {\n        return internalMap.values();\n    }\n\n    private static final class ByteArrayWrapper implements Serializable {\n        private static final long serialVersionUID = 1L;\n        private final byte[] data;\n        private final int hashCode;\n\n        public ByteArrayWrapper(byte[] data) {\n            if (data == null) {\n                throw new NullPointerException();\n            }\n            this.data = data;\n            this.hashCode = Arrays.hashCode(data);\n        }\n\n        @Override\n        public boolean equals(Object other) {\n            if (other == null) return false;\n            if (other == this) return true;\n            if (!(other instanceof ByteArrayWrapper)) return false;\n\n            return Arrays.equals(data, ((ByteArrayWrapper) other).data);\n        }\n\n        @Override\n        public int hashCode() {\n            return hashCode;\n        }\n    }\n\n    private static final class JedisByteEntry<T> implements Entry<byte[], T> {\n        private final byte[] key;\n        private T value;\n\n        public JedisByteEntry(byte[] key, T value) {\n            this.key = key;\n            this.value = value;\n        }\n\n        @Override\n        public byte[] getKey() {\n            return this.key;\n        }\n\n        @Override\n        public T getValue() {\n            return this.value;\n        }\n\n        @Override\n        public T setValue(T value) {\n            this.value = value;\n            return value;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisClusterCRC16.java",
    "content": "package redis.clients.jedis.util;\n\n/**\n * CRC16 Implementation according to CCITT standard Polynomial : 1021 (x^16 + x^12 + x^5 + 1) See <a\n * href=\"http://redis.io/topics/cluster-spec\">Appendix A. CRC16 reference implementation in ANSI\n * C</a>\n */\npublic final class JedisClusterCRC16 {\n\n  private static final int[] LOOKUP_TABLE = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5,\n      0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231,\n      0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A,\n      0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4,\n      0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672,\n      0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF,\n      0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,\n      0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7,\n      0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58,\n      0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE,\n      0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4,\n      0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59,\n      0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1,\n      0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D,\n      0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,\n      0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,\n      0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E,\n      0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C,\n      0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827,\n      0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB,\n      0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F,\n      0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2,\n      0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,\n      0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, };\n\n  public static int getSlot(String key) {\n    if (key == null) {\n      throw new NullPointerException(\"Slot calculation of null is impossible\");\n    }\n\n    key = JedisClusterHashTag.getHashTag(key);\n    // optimization with modulo operator with power of 2 equivalent to getCRC16(key) % 16384\n    return getCRC16(key) & (16384 - 1);\n  }\n\n  public static int getSlot(byte[] key) {\n    if (key == null) {\n      throw new NullPointerException(\"Slot calculation of null is impossible\");\n    }\n\n    int s = -1;\n    int e = -1;\n    boolean sFound = false;\n    for (int i = 0; i < key.length; i++) {\n      if (key[i] == '{' && !sFound) {\n        s = i;\n        sFound = true;\n      }\n      if (key[i] == '}' && sFound) {\n        e = i;\n        break;\n      }\n    }\n    if (s > -1 && e > -1 && e != s + 1) {\n      return getCRC16(key, s + 1, e) & (16384 - 1);\n    }\n    return getCRC16(key) & (16384 - 1);\n  }\n\n  /**\n   * Create a CRC16 checksum from the bytes. implementation is from mp911de/lettuce, modified with\n   * some more optimizations\n   * @param bytes\n   * @param s\n   * @param e\n   * @return CRC16 as integer value See <a\n   *         href=\"https://github.com/xetorthio/jedis/pull/733#issuecomment-55840331\">Issue 733</a>\n   */\n  public static int getCRC16(byte[] bytes, int s, int e) {\n    int crc = 0x0000;\n\n    for (int i = s; i < e; i++) {\n      crc = ((crc << 8) ^ LOOKUP_TABLE[((crc >>> 8) ^ (bytes[i] & 0xFF)) & 0xFF]);\n    }\n    return crc & 0xFFFF;\n  }\n\n  public static int getCRC16(byte[] bytes) {\n    return getCRC16(bytes, 0, bytes.length);\n  }\n\n  public static int getCRC16(String key) {\n    byte[] bytesKey = SafeEncoder.encode(key);\n    return getCRC16(bytesKey, 0, bytesKey.length);\n  }\n\n  private JedisClusterCRC16() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisClusterHashTag.java",
    "content": "package redis.clients.jedis.util;\n\n/**\n * Holds various methods/utilities to manipulate and parse redis hash-tags. See <a\n * href=\"http://redis.io/topics/cluster-spec\">Cluster-Spec : Keys hash tags</a>\n */\npublic final class JedisClusterHashTag {\n\n  private JedisClusterHashTag() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  public static String getHashTag(String key) {\n    return extractHashTag(key, true);\n  }\n\n  public static boolean isClusterCompliantMatchPattern(byte[] matchPattern) {\n    return isClusterCompliantMatchPattern(SafeEncoder.encode(matchPattern));\n  }\n\n  public static boolean isClusterCompliantMatchPattern(String matchPattern) {\n    String tag = extractHashTag(matchPattern, false);\n    return tag != null && !tag.isEmpty();\n  }\n\n  private static String extractHashTag(String key, boolean returnKeyOnAbsence) {\n    int s = key.indexOf(\"{\");\n    if (s > -1) {\n      int e = key.indexOf(\"}\", s + 1);\n      if (e > -1 && e != s + 1) {\n        return key.substring(s + 1, e);\n      }\n    }\n    return returnKeyOnAbsence ? key : null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisCommandIterationBase.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Queue;\nimport java.util.function.Supplier;\n\nimport redis.clients.jedis.Builder;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n/**\n * @param <B> Type of each batch reply\n * @param <D> Type of each data\n */\npublic abstract class JedisCommandIterationBase<B, D> {\n\n  private final Builder<B> builder;\n\n  private final Queue<Map.Entry> connections;\n\n  private Map.Entry connection;\n\n  private B lastReply;\n\n  private boolean roundRobinCompleted;\n  private boolean iterationCompleted;\n\n  protected JedisCommandIterationBase(ConnectionProvider connectionProvider, Builder<B> responseBuilder) {\n    Map connectionMap = connectionProvider.getConnectionMap();\n    ArrayList<Map.Entry> connectionList = new ArrayList<>(connectionMap.entrySet());\n    Collections.shuffle(connectionList);\n    this.connections = new LinkedList<>(connectionList);\n    this.builder = responseBuilder;\n    this.iterationCompleted = true;\n    this.roundRobinCompleted = this.connections.isEmpty();\n  }\n\n  public final boolean isIterationCompleted() {\n    return roundRobinCompleted;\n  }\n\n  protected abstract boolean isNodeCompleted(B reply);\n\n  protected abstract CommandArguments initCommandArguments();\n\n  protected abstract CommandArguments nextCommandArguments(B lastReply);\n\n  public final B nextBatch() {\n    if (roundRobinCompleted) {\n      throw new NoSuchElementException();\n    }\n\n    CommandArguments args;\n    if (iterationCompleted) {\n      connection = connections.poll();\n      args = initCommandArguments();\n    } else {\n      args = nextCommandArguments(lastReply);\n    }\n\n    Object rawReply;\n    if (connection.getValue() instanceof Connection) {\n      rawReply = ((Connection) connection.getValue()).executeCommand(args);\n    } else if (connection.getValue() instanceof Pool) {\n      try (Connection c = ((Pool<Connection>) connection.getValue()).getResource()) {\n        rawReply = c.executeCommand(args);\n      }\n    } else {\n      throw new IllegalArgumentException(connection.getValue().getClass() + \"is not supported.\");\n    }\n\n    lastReply = builder.build(rawReply);\n    iterationCompleted = isNodeCompleted(lastReply);\n    if (iterationCompleted) {\n      if (connections.isEmpty()) {\n        roundRobinCompleted = true;\n      }\n    }\n    return lastReply;\n  }\n\n  protected abstract Collection<D> convertBatchToData(B batch);\n\n  public final Collection<D> nextBatchList() {\n    return convertBatchToData(nextBatch());\n  }\n\n  public final Collection<D> collect(Collection<D> c) {\n    while (!isIterationCompleted()) {\n      c.addAll(nextBatchList());\n    }\n    return c;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/JedisURIHelper.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.net.URI;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\n\n/**\n * Utility class for handling Redis URIs.\n * This class provides methods to extract various components from a Redis URI,\n * such as host, port, user, password, database index, and protocol.\n * It also includes methods to validate the URI and check its scheme.\n *\n * <h2>URI syntax</h2>\n *\n * <blockquote>\n * <i>redis[s]</i><b>{@code ://}</b>[[<i>username</i>]<i>[{@code :}password</i>]@]\n * <i>host</i>[<b>{@code :}</b><i>port</i>][<b>{@code /}</b><i>database</i>]\n * </blockquote>\n *\n *\n * <h2>Authentication</h2>\n * <p>Authentication details can be provided in the URI in the form of a username and password.\n * Redis URIs may contain authentication details that effectively lead to usernames with passwords,\n * password-only, or no authentication.</p>\n * <h3>Examples:</h3>\n * <ul>\n *   <li><b>Username and Password:</b> redis://username:password@host:port</li>\n *   <li><b>Password-only:</b> redis://:password@host:port</li>\n *   <li><b>Empty password:</b> redis://username:@host:port</li>\n *   <li><b>No Authentication:</b> redis://host:port</li>\n * </ul>\n */\npublic final class JedisURIHelper {\n\n  private static final String REDIS = \"redis\";\n  private static final String REDISS = \"rediss\";\n\n  private JedisURIHelper() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  public static HostAndPort getHostAndPort(URI uri) {\n    return new HostAndPort(uri.getHost(), uri.getPort());\n  }\n\n  /**\n   * Extracts the user from the given URI.\n   * <p>\n   * For details on the URI format and authentication examples, see {@link JedisURIHelper}.\n   * </p>\n   * @param uri the URI to extract the user from\n   * @return the user as a String, or null if user is empty or {@link URI#getUserInfo()} info is missing\n   */\n  public static String getUser(URI uri) {\n    String userInfo = uri.getUserInfo();\n    if (userInfo != null) {\n      String user = userInfo.split(\":\", 2)[0];\n      if (user.isEmpty()) {\n        user = null; // return null user is not specified\n      }\n      return user;\n    }\n    return null;\n  }\n\n  /**\n   * Extracts the password from the given URI.\n   * <p>\n   * For details on the URI format and authentication examples, see {@link JedisURIHelper}.\n   * </p>\n   * @param uri the URI to extract the password from\n   * @return the password as a String, or null if {@link URI#getUserInfo()} info is missing\n   * @throws IllegalArgumentException if {@link URI#getUserInfo()} is provided but does not contain\n   *           a password\n   */\n  public static String getPassword(URI uri) {\n    String userInfo = uri.getUserInfo();\n    if (userInfo != null) {\n      String[] userAndPassword = userInfo.split(\":\", 2);\n      if (userAndPassword.length < 2) {\n        throw new IllegalArgumentException(\"Password not provided in uri.\");\n      }\n      return userAndPassword[1];\n    }\n    return null;\n  }\n\n  /**\n   * Checks if the given URI has a database index component.\n   *\n   * @param uri the URI to check\n   * @return true if the URI has a database index component, false otherwise\n   */\n  public static boolean hasDbIndex(URI uri) {\n    if (uri.getPath() == null || uri.getPath().isEmpty()) {\n      return false;\n    }\n\n    String[] pathSplit = uri.getPath().split(\"/\", 2);\n\n    return pathSplit.length > 1 && !pathSplit[1].isEmpty();\n  }\n\n  /**\n   * Returns the database index from the given URI.\n   *\n   * @param uri\n   * @return database index, or default database (0) if not specified\n   */\n  public static int getDBIndex(URI uri) {\n    String[] pathSplit = uri.getPath().split(\"/\", 2);\n    if (pathSplit.length > 1) {\n      String dbIndexStr = pathSplit[1];\n      if (dbIndexStr.isEmpty()) {\n        return Protocol.DEFAULT_DATABASE;\n      }\n      return Integer.parseInt(dbIndexStr);\n    } else {\n      return Protocol.DEFAULT_DATABASE;\n    }\n  }\n\n  /**\n   * Returns the Redis protocol from the given URI.\n   *\n   * @param uri\n   * @return Redis protocol, or null if not specified\n   */\n  public static RedisProtocol getRedisProtocol(URI uri) {\n    if (uri.getQuery() == null) return null;\n\n    String[] params = uri.getQuery().split(\"&\");\n    for (String param : params) {\n      int idx = param.indexOf(\"=\");\n      if (idx < 0) continue;\n      if (\"protocol\".equals(param.substring(0, idx))) {\n        String ver = param.substring(idx + 1);\n        for (RedisProtocol proto : RedisProtocol.values()) {\n          if (proto.version().equals(ver)) {\n            return proto;\n          }\n        }\n        throw new IllegalArgumentException(\"Unknown protocol \" + ver);\n      }\n    }\n    return null; // null (default) when not defined\n  }\n\n  public static boolean isValid(URI uri) {\n    if (isEmpty(uri.getScheme()) || isEmpty(uri.getHost()) || uri.getPort() == -1) {\n      return false;\n    }\n\n    return true;\n  }\n\n  private static boolean isEmpty(String value) {\n    return value == null || value.trim().length() == 0;\n  }\n\n  public static boolean isRedisScheme(URI uri) {\n    return REDIS.equals(uri.getScheme());\n  }\n\n  public static boolean isRedisSSLScheme(URI uri) {\n    return REDISS.equals(uri.getScheme());\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/KeyValue.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.AbstractMap.SimpleImmutableEntry;\n\npublic class KeyValue<K, V> extends SimpleImmutableEntry<K, V> {\n\n  public KeyValue(K key, V value) {\n    super(key, value);\n  }\n\n  public static <K, V> KeyValue<K, V> of(K key, V value) {\n    return new KeyValue<>(key, value);\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/LazyRawable.java",
    "content": "package redis.clients.jedis.util;\n\nimport redis.clients.jedis.args.Rawable;\n\npublic class LazyRawable implements Rawable {\n\n  private byte[] raw = null;\n\n  public void setRaw(byte[] raw) {\n    this.raw = raw;\n  }\n\n  @Override\n  public byte[] getRaw() {\n    return raw;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/Pool.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPool;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.exceptions.JedisException;\n\npublic class Pool<T> extends GenericObjectPool<T> {\n\n  // Legacy\n  public Pool(GenericObjectPoolConfig<T> poolConfig, PooledObjectFactory<T> factory) {\n    this(factory, poolConfig);\n  }\n\n  public Pool(final PooledObjectFactory<T> factory, final GenericObjectPoolConfig<T> poolConfig) {\n    super(factory, poolConfig);\n  }\n\n  public Pool(final PooledObjectFactory<T> factory) {\n    super(factory);\n  }\n\n  @Override\n  public void close() {\n    destroy();\n  }\n\n  public void destroy() {\n    try {\n      super.close();\n    } catch (RuntimeException e) {\n      throw new JedisException(\"Could not destroy the pool\", e);\n    }\n  }\n\n  public T getResource() {\n    try {\n      return super.borrowObject();\n    } catch (JedisException je) {\n      throw je;\n    } catch (Exception e) {\n      throw new JedisException(\"Could not get a resource from the pool\", e);\n    }\n  }\n\n  public void returnResource(final T resource) {\n    if (resource == null) {\n      return;\n    }\n    try {\n      super.returnObject(resource);\n    } catch (RuntimeException e) {\n      throw new JedisException(\"Could not return the resource to the pool\", e);\n    }\n  }\n\n  public void returnBrokenResource(final T resource) {\n    if (resource == null) {\n      return;\n    }\n    try {\n      super.invalidateObject(resource);\n    } catch (Exception e) {\n      throw new JedisException(\"Could not return the broken resource to the pool\", e);\n    }\n  }\n\n  @Override\n  public void addObjects(int count) {\n    try {\n      for (int i = 0; i < count; i++) {\n        addObject();\n      }\n    } catch (Exception e) {\n      throw new JedisException(\"Error trying to add idle objects\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java",
    "content": "package redis.clients.jedis.util;\n\nimport redis.clients.jedis.CommandKeyArgumentPreProcessor;\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.args.RawableFactory;\n\n@Experimental\npublic class PrefixedKeyArgumentPreProcessor implements CommandKeyArgumentPreProcessor {\n\n  private final byte[] prefixBytes;\n  private final String prefixString;\n\n  public PrefixedKeyArgumentPreProcessor(String prefix) {\n    this(prefix, SafeEncoder.encode(prefix));\n  }\n\n  public PrefixedKeyArgumentPreProcessor(String prefixString, byte[] prefixBytes) {\n    this.prefixString = prefixString;\n    this.prefixBytes = prefixBytes;\n  }\n\n  @Override\n  public Object actualKey(Object paramKey) {\n    return prefixKey(paramKey, prefixString, prefixBytes);\n  }\n\n  private static Object prefixKey(Object key, String prefixString, byte[] prefixBytes) {\n    if (key instanceof Rawable) {\n      byte[] raw = ((Rawable) key).getRaw();\n      return RawableFactory.from(prefixKeyWithBytes(raw, prefixBytes));\n    } else if (key instanceof byte[]) {\n      return prefixKeyWithBytes((byte[]) key, prefixBytes);\n    } else if (key instanceof String) {\n      String raw = (String) key;\n      return prefixString + raw;\n    }\n    throw new IllegalArgumentException(\"\\\"\" + key.toString() + \"\\\" is not a valid argument.\");\n  }\n\n  private static byte[] prefixKeyWithBytes(byte[] key, byte[] prefixBytes) {\n    byte[] namespaced = new byte[prefixBytes.length + key.length];\n    System.arraycopy(prefixBytes, 0, namespaced, 0, prefixBytes.length);\n    System.arraycopy(key, 0, namespaced, prefixBytes.length, key.length);\n    return namespaced;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/RedisInputStream.java",
    "content": "/*\n * Copyright 2009-2010 MBTE Sweden AB. Licensed under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance with the License. You may obtain a\n * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable\n * law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\"\n * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License\n * for the specific language governing permissions and limitations under the License.\n */\n\npackage redis.clients.jedis.util;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.math.BigInteger;\n\nimport redis.clients.jedis.annots.Experimental;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\n/**\n * This class assumes (to some degree) that we are reading a RESP stream. As such it assumes certain\n * conventions regarding CRLF line termination. It also assumes that if the Protocol layer requires\n * a byte that if that byte is not there it is a stream error.\n */\npublic class RedisInputStream extends FilterInputStream {\n\n  private static final int INPUT_BUFFER_SIZE = Integer.parseInt(\n      System.getProperty(\"jedis.bufferSize.input\",\n          System.getProperty(\"jedis.bufferSize\", \"8192\")));\n\n  protected final byte[] buf;\n\n  protected int count, limit;\n\n  public RedisInputStream(InputStream in, int size) {\n    super(in);\n    if (size <= 0) {\n      throw new IllegalArgumentException(\"Buffer size <= 0\");\n    }\n    buf = new byte[size];\n  }\n\n  public RedisInputStream(InputStream in) {\n    this(in, INPUT_BUFFER_SIZE);\n  }\n\n  @Experimental\n  public boolean peek(byte b) throws JedisConnectionException {\n    ensureFill(); // in current design, at least one reply is expected. so ensureFillSafe() is not necessary.\n    return buf[count] == b;\n  }\n\n  public byte readByte() throws JedisConnectionException {\n    ensureFill();\n    return buf[count++];\n  }\n\n  private void ensureCrLf() {\n    final byte[] buf = this.buf;\n\n    ensureFill();\n    if (buf[count++] == '\\r') {\n\n      ensureFill();\n      if (buf[count++] == '\\n') {\n        return;\n      }\n    }\n\n    throw new JedisConnectionException(\"Unexpected character!\");\n  }\n\n  public String readLine() {\n    final StringBuilder sb = new StringBuilder();\n    while (true) {\n      ensureFill();\n\n      byte b = buf[count++];\n      if (b == '\\r') {\n        ensureFill(); // Must be one more byte\n\n        byte c = buf[count++];\n        if (c == '\\n') {\n          break;\n        }\n        sb.append((char) b);\n        sb.append((char) c);\n      } else {\n        sb.append((char) b);\n      }\n    }\n\n    final String reply = sb.toString();\n    if (reply.isEmpty()) {\n      throw new JedisConnectionException(\"It seems like server has closed the connection.\");\n    }\n\n    return reply;\n  }\n\n  public byte[] readLineBytes() {\n\n    /*\n     * This operation should only require one fill. In that typical case we optimize allocation and\n     * copy of the byte array. In the edge case where more than one fill is required then we take a\n     * slower path and expand a byte array output stream as is necessary.\n     */\n\n    ensureFill();\n\n    int pos = count;\n    final byte[] buf = this.buf;\n    while (true) {\n      if (pos == limit) {\n        return readLineBytesSlowly();\n      }\n\n      if (buf[pos++] == '\\r') {\n        if (pos == limit) {\n          return readLineBytesSlowly();\n        }\n\n        if (buf[pos++] == '\\n') {\n          break;\n        }\n      }\n    }\n\n    final int N = (pos - count) - 2;\n    final byte[] line = new byte[N];\n    System.arraycopy(buf, count, line, 0, N);\n    count = pos;\n    return line;\n  }\n\n  /**\n   * Slow path in case a line of bytes cannot be read in one #fill() operation. This is still faster\n   * than creating the StringBuilder, String, then encoding as byte[] in Protocol, then decoding\n   * back into a String.\n   */\n  private byte[] readLineBytesSlowly() {\n    ByteArrayOutputStream bout = null;\n    while (true) {\n      ensureFill();\n\n      byte b = buf[count++];\n      if (b == '\\r') {\n        ensureFill(); // Must be one more byte\n\n        byte c = buf[count++];\n        if (c == '\\n') {\n          break;\n        }\n\n        if (bout == null) {\n          bout = new ByteArrayOutputStream(16);\n        }\n\n        bout.write(b);\n        bout.write(c);\n      } else {\n        if (bout == null) {\n          bout = new ByteArrayOutputStream(16);\n        }\n\n        bout.write(b);\n      }\n    }\n\n    return bout == null ? new byte[0] : bout.toByteArray();\n  }\n\n  public Object readNullCrLf() {\n    ensureCrLf();\n    return null;\n  }\n\n  public boolean readBooleanCrLf() {\n    final byte[] buf = this.buf;\n\n    ensureFill();\n    final byte b = buf[count++];\n\n    ensureCrLf();\n    switch (b) {\n      case 't':\n        return true;\n      case 'f':\n        return false;\n      default:\n        throw new JedisConnectionException(\"Unexpected character!\");\n    }\n  }\n\n  public int readIntCrLf() {\n    return (int) readLongCrLf();\n  }\n\n  public long readLongCrLf() {\n    final byte[] buf = this.buf;\n\n    ensureFill();\n\n    final boolean isNeg = buf[count] == '-';\n    if (isNeg) {\n      ++count;\n    }\n\n    long value = 0;\n    while (true) {\n      ensureFill();\n\n      final int b = buf[count++];\n      if (b == '\\r') {\n        ensureFill();\n\n        if (buf[count++] != '\\n') {\n          throw new JedisConnectionException(\"Unexpected character!\");\n        }\n\n        break;\n      } else {\n        value = value * 10 + b - '0';\n      }\n    }\n\n    return (isNeg ? -value : value);\n  }\n\n  public double readDoubleCrLf() {\n    return DoublePrecision.parseFloatingPointNumber(readLine());\n  }\n\n  public BigInteger readBigIntegerCrLf() {\n    return new BigInteger(readLine());\n  }\n\n  @Override\n  public int read(byte[] b, int off, int len) throws JedisConnectionException {\n    ensureFill();\n\n    final int length = Math.min(limit - count, len);\n    System.arraycopy(buf, count, b, off, length);\n    count += length;\n    return length;\n  }\n\n  /**\n   * This method assumes there are required bytes to be read. If we cannot read anymore bytes an\n   * exception is thrown to quickly ascertain that the stream was smaller than expected.\n   */\n  private void ensureFill() throws JedisConnectionException {\n    if (count >= limit) {\n      try {\n        limit = in.read(buf);\n        count = 0;\n        if (limit == -1) {\n          throw new JedisConnectionException(\"Unexpected end of stream.\");\n        }\n      } catch (IOException e) {\n        throw new JedisConnectionException(e);\n      }\n    }\n  }\n\n  @Override\n  public int available() throws IOException {\n    int availableInBuf = limit - count;\n    int availableInSocket = this.in.available();\n    return (availableInBuf > availableInSocket) ? availableInBuf : availableInSocket;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/RedisOutputStream.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * The class implements a buffered output stream without synchronization There are also special\n * operations like in-place string encoding. This stream fully ignore mark/reset and should not be\n * used outside Jedis\n */\npublic final class RedisOutputStream extends FilterOutputStream {\n\n  private static final int OUTPUT_BUFFER_SIZE = Integer.parseInt(\n      System.getProperty(\"jedis.bufferSize.output\",\n          System.getProperty(\"jedis.bufferSize\", \"8192\")));\n\n  protected final byte[] buf;\n\n  protected int count;\n\n  private final static int[] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999,\n      999999999, Integer.MAX_VALUE };\n\n  private final static byte[] DigitTens = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1',\n      '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2',\n      '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4',\n      '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6',\n      '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8',\n      '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', };\n\n  private final static byte[] DigitOnes = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',\n      '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8',\n      '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6',\n      '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',\n      '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',\n      '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', };\n\n  private final static byte[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',\n      'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',\n      't', 'u', 'v', 'w', 'x', 'y', 'z' };\n\n  public RedisOutputStream(final OutputStream out) {\n    this(out, OUTPUT_BUFFER_SIZE);\n  }\n\n  public RedisOutputStream(final OutputStream out, final int size) {\n    super(out);\n    if (size <= 0) {\n      throw new IllegalArgumentException(\"Buffer size <= 0\");\n    }\n    buf = new byte[size];\n  }\n\n  private void flushBuffer() throws IOException {\n    if (count > 0) {\n      out.write(buf, 0, count);\n      count = 0;\n    }\n  }\n\n  public void write(final byte b) throws IOException {\n    if (count == buf.length) {\n      flushBuffer();\n    }\n    buf[count++] = b;\n  }\n\n  @Override\n  public void write(final byte[] b) throws IOException {\n    write(b, 0, b.length);\n  }\n\n  @Override\n  public void write(final byte[] b, final int off, final int len) throws IOException {\n    if (len >= buf.length) {\n      flushBuffer();\n      out.write(b, off, len);\n    } else {\n      if (len >= buf.length - count) {\n        flushBuffer();\n      }\n\n      System.arraycopy(b, off, buf, count, len);\n      count += len;\n    }\n  }\n\n  public void writeCrLf() throws IOException {\n    if (2 >= buf.length - count) {\n      flushBuffer();\n    }\n\n    buf[count++] = '\\r';\n    buf[count++] = '\\n';\n  }\n\n  public void writeIntCrLf(int value) throws IOException {\n    if (value < 0) {\n      write((byte) '-');\n      value = -value;\n    }\n\n    int size = 0;\n    while (value > sizeTable[size])\n      size++;\n\n    size++;\n    if (size >= buf.length - count) {\n      flushBuffer();\n    }\n\n    int q, r;\n    int charPos = count + size;\n\n    while (value >= 65536) {\n      q = value / 100;\n      r = value - ((q << 6) + (q << 5) + (q << 2));\n      value = q;\n      buf[--charPos] = DigitOnes[r];\n      buf[--charPos] = DigitTens[r];\n    }\n\n    for (;;) {\n      q = (value * 52429) >>> (16 + 3);\n      r = value - ((q << 3) + (q << 1));\n      buf[--charPos] = digits[r];\n      value = q;\n      if (value == 0) break;\n    }\n    count += size;\n\n    writeCrLf();\n  }\n\n  @Override\n  public void flush() throws IOException {\n    flushBuffer();\n    out.flush();\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/SafeEncoder.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * The only reason to have this is to be able to compatible with java 1.5 :(\n */\npublic final class SafeEncoder {\n\n  public static volatile Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;\n\n  private SafeEncoder() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  public static byte[][] encodeMany(final String... strs) {\n    byte[][] many = new byte[strs.length][];\n    for (int i = 0; i < strs.length; i++) {\n      many[i] = encode(strs[i]);\n    }\n    return many;\n  }\n\n  public static byte[] encode(final String str) {\n    if (str == null) {\n      throw new IllegalArgumentException(\"null value cannot be sent to redis\");\n    }\n    return str.getBytes(DEFAULT_CHARSET);\n  }\n\n  public static String encode(final byte[] data) {\n    return new String(data, DEFAULT_CHARSET);\n  }\n\n  /**\n   * This method takes an object and will convert all bytes[] and list of byte[] and will encode the\n   * object in a recursive way.\n   * @param dataToEncode\n   * @return the object fully encoded\n   */\n  public static Object encodeObject(Object dataToEncode) {\n    if (dataToEncode instanceof byte[]) {\n      return SafeEncoder.encode((byte[]) dataToEncode);\n    }\n\n    if (dataToEncode instanceof KeyValue) {\n      KeyValue keyValue = (KeyValue) dataToEncode;\n      return new KeyValue<>(encodeObject(keyValue.getKey()), encodeObject(keyValue.getValue()));\n    }\n\n    if (dataToEncode instanceof List) {\n      List arrayToDecode = (List) dataToEncode;\n      List returnValueArray = new ArrayList(arrayToDecode.size());\n      for (Object arrayEntry : arrayToDecode) {\n        // recursive call and add to list\n        returnValueArray.add(encodeObject(arrayEntry));\n      }\n      return returnValueArray;\n    }\n\n    return dataToEncode;\n  }\n\n  /**\n   * Converts a byte array to uppercase by converting lowercase ASCII letters (a-z) to uppercase (A-Z).\n   * This method is optimized for ASCII text and performs direct byte manipulation, which is significantly\n   * faster than converting to String and calling String.toUpperCase().\n   * <p>\n   * This method only works correctly for ASCII text. Non-ASCII characters are left unchanged.\n   * For Redis command names (which are always ASCII), this is safe and provides ~47% performance\n   * improvement over the String-based approach.\n   *\n   * @param data the byte array to convert to uppercase\n   * @return a new byte array with lowercase ASCII letters converted to uppercase\n   */\n  public static byte[] toUpperCase(final byte[] data) {\n    if (data == null) {\n      return null;\n    }\n\n    byte[] uppercaseBytes = new byte[data.length];\n    for (int i = 0; i < data.length; i++) {\n      if (data[i] >= 'a' && data[i] <= 'z') {\n        uppercaseBytes[i] = (byte) (data[i] - 32);\n      } else {\n        uppercaseBytes[i] = data[i];\n      }\n    }\n    return uppercaseBytes;\n  }\n}\n"
  },
  {
    "path": "src/main/java/redis/clients/jedis/util/package-info.java",
    "content": "/**\n * This package contains the utility classes.\n */\npackage redis.clients.jedis.util;\n"
  },
  {
    "path": "src/main/resources/redis/clients/jedis/pom.properties",
    "content": "groupId=${project.groupId}\nartifactId=${project.artifactId}\nversion=${project.version}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/BitMapsExample.java",
    "content": "// EXAMPLE: bitmap_tutorial\n// HIDE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class BitMapsExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // HIDE_END\n\n        // REMOVE_START\n        jedis.del(\"pings:2024-01-01-00:00\");\n        // REMOVE_END\n\n        // STEP_START ping\n        boolean res1 = jedis.setbit(\"pings:2024-01-01-00:00\", 123, true);\n        System.out.println(res1); // >>> false\n\n        boolean res2 = jedis.getbit(\"pings:2024-01-01-00:00\", 123);\n        System.out.println(res2); // >>> true\n\n        boolean res3 = jedis.getbit(\"pings:2024-01-01-00:00\", 456);\n        System.out.println(res3); // >>> false\n        // STEP_END\n\n        // REMOVE_START\n        assertFalse(res1);\n        assertTrue(res2);\n        assertFalse(res3);\n        // REMOVE_END\n\n        // STEP_START bitcount\n        long res4 = jedis.bitcount(\"pings:2024-01-01-00:00\");\n        System.out.println(res4); // >>> 1\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1, res4);\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/BitfieldExample.java",
    "content": "// EXAMPLE: bitfield_tutorial\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport java.util.List;\n// REMOVE_END\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class BitfieldExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n// HIDE_END\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        jedis.del(\"bike:1:stats\");\n        //REMOVE_END\n\n        // STEP_START bf\n        List<Long> res1 = jedis.bitfield(\"bike:1:stats\", \"SET\", \"u32\", \"#0\", \"1000\");\n        System.out.println(res1);   // >>> [0]\n\n        List<Long> res2 = jedis.bitfield(\"bike:1:stats\", \"INCRBY\", \"u32\", \"#0\", \"-50\", \"INCRBY\", \"u32\", \"#1\", \"1\");\n        System.out.println(res2);   // >>> [950, 1]\n\n        List<Long> res3 = jedis.bitfield(\"bike:1:stats\", \"INCRBY\", \"u32\", \"#0\", \"500\", \"INCRBY\", \"u32\", \"#1\", \"1\");\n        System.out.println(res3);   // >>> [1450, 2]\n\n        List<Long> res4 = jedis.bitfield(\"bike:1:stats\", \"GET\", \"u32\", \"#0\", \"GET\", \"u32\", \"#1\");\n        System.out.println(res4);   // >>> [1450, 2]\n        // STEP_END\n\n        // Tests for 'bf' step.\n        // REMOVE_START\n        assertEquals(\"[0]\", res1.toString());\n        assertEquals(\"[950, 1]\", res2.toString());\n        assertEquals(\"[1450, 2]\", res3.toString());\n        assertEquals(\"[1450, 2]\", res4.toString());\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/BloomFilterExample.java",
    "content": "// EXAMPLE: bf_tutorial\n\n// HIDE_START\npackage io.redis.examples;\n\nimport redis.clients.jedis.RedisClient;\nimport org.junit.jupiter.api.Test;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class BloomFilterExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // HIDE_END\n\n        // REMOVE_START\n        jedis.del(\"bikes:models\");\n        // REMOVE_END\n\n        // STEP_START bloom\n        String res1 = jedis.bfReserve(\"bikes:models\", 0.01, 1000);\n        System.out.println(res1); // >>> OK\n\n        // REMOVE_START\n        assertEquals(\"OK\", res1);\n        // REMOVE_END\n\n        boolean res2 = jedis.bfAdd(\"bikes:models\", \"Smoky Mountain Striker\");\n        System.out.println(res2); // >>> True\n\n        boolean res3 = jedis.bfExists(\"bikes:models\", \"Smoky Mountain Striker\");\n        System.out.println(res3); // >>> True\n\n        List<Boolean> res4 = jedis.bfMAdd(\"bikes:models\",\n                \"Rocky Mountain Racer\",\n                \"Cloudy City Cruiser\",\n                \"Windy City Wippet\");\n        System.out.println(res4); // >>> [True, True, True]\n\n        List<Boolean> res5 = jedis.bfMExists(\"bikes:models\",\n                \"Rocky Mountain Racer\",\n                \"Cloudy City Cruiser\",\n                \"Windy City Wippet\");\n        System.out.println(res5); // >>> [True, True, True]\n        // STEP_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CMSExample.java",
    "content": "//EXAMPLE: cms_tutorial\n//HIDE_START\npackage io.redis.examples;\n//HIDE_END\n\n//REMOVE_START\nimport redis.clients.jedis.RedisClient;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n//REMOVE_END\n\npublic class CMSExample {\n\n  @Test\n  public void run() {\n    //HIDE_START\n    RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n    //HIDE_END\n\n    //REMOVE_START\n    jedis.del(\"bikes:profit\");\n    //REMOVE_END\n\n    //STEP_START cms\n    String res1 = jedis.cmsInitByProb(\"bikes:profit\", 0.001d, 0.002d);\n    System.out.println(res1); // >>> OK\n\n    long res2 = jedis.cmsIncrBy(\"bikes:profit\", \"Smoky Mountain Striker\", 100L);\n    System.out.println(res2); // >>> 100\n\n    List<Long> res3 = jedis.cmsIncrBy(\"bikes:profit\", new HashMap<String, Long>() {{\n      put(\"Rocky Mountain Racer\", 200L);\n      put(\"Cloudy City Cruiser\", 150L);\n    }});\n    System.out.println(res3); // >>> [200, 150]\n\n    List<Long> res4 = jedis.cmsQuery(\"bikes:profit\", \"Smoky Mountain Striker\");\n    System.out.println(res4); // >>> [100]\n\n    Map<String, Object> res5 = jedis.cmsInfo(\"bikes:profit\");\n    System.out.println(res5.get(\"width\") + \" \" + res5.get(\"depth\") + \" \" + res5.get(\"count\")); // >>> 2000 9 450\n    //STEP_END\n\n    //HIDE_START\n    jedis.close();\n    //HIDE_END\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsCnxmgmtExample.java",
    "content": "// EXAMPLE: cmds_cnxmgmt\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\nimport redis.clients.jedis.Jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n// HIDE_START\npublic class CmdsCnxmgmtExample {\n    @Test\n    public void run() {\n// HIDE_END\n        Jedis jedis = new Jedis(\"redis://localhost:6379\");\n\n        // STEP_START auth1\n        // REMOVE_START\n        jedis.configSet(\"requirepass\", \"temp_pass\");\n        // REMOVE_END\n        // Note: you must use the `Jedis` class rather than `RedisClient`\n        // to access the `auth` commands.\n        String authResult1 = jedis.auth(\"default\",  \"temp_pass\");\n        System.out.println(authResult1); // >>> OK\n        // REMOVE_START\n        assertEquals(\"OK\", authResult1);\n        jedis.configSet(\"requirepass\", \"\");\n        // REMOVE_END\n        // STEP_END\n       \n        // STEP_START auth2\n        // REMOVE_START\n        jedis.aclSetUser(\"test-user\", \"on\", \">strong_password\", \"+acl\");\n        // REMOVE_END\n        // Note: you must use the `Jedis` class rather than `RedisClient`\n        // to access the `auth` commands.\n        String authResult2 = jedis.auth(\"test-user\", \"strong_password\");\n        System.out.println(authResult2); // >>> OK\n        // REMOVE_START\n        assertEquals(\"OK\", authResult2);\n        jedis.aclDelUser(\"test-user\");\n        // REMOVE_END\n        // STEP_END\n        \n        // HIDE_START\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsGenericExample.java",
    "content": "// EXAMPLE: cmds_generic\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.args.ExpiryOption;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class CmdsGenericExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        //REMOVE_END\n// HIDE_END\n\n        // STEP_START del\n        String delResult1 = jedis.set(\"key1\", \"Hello\");\n        System.out.println(delResult1); // >>> OK\n\n        String delResult2 = jedis.set(\"key2\", \"World\");\n        System.out.println(delResult2); // >>> OK\n\n        long delResult3 = jedis.del(\"key1\", \"key2\", \"key3\");\n        System.out.println(delResult3); // >>> 2\n        // STEP_END\n\n        // Tests for 'del' step.\n        // REMOVE_START\n        assertEquals(\"OK\", delResult1);\n        assertEquals(\"OK\", delResult2);\n        assertEquals(2, delResult3);\n        // REMOVE_END\n\n\n        // STEP_START expire\n        String expireResult1 = jedis.set(\"mykey\", \"Hello\");\n        System.out.println(expireResult1);  // >>> OK\n\n        long expireResult2 = jedis.expire(\"mykey\", 10);\n        System.out.println(expireResult2);  // >>> 1\n\n        long expireResult3 = jedis.ttl(\"mykey\");\n        System.out.println(expireResult3);  // >>> 10\n\n        String expireResult4 = jedis.set(\"mykey\", \"Hello World\");\n        System.out.println(expireResult4);  // >>> OK\n\n        long expireResult5 = jedis.ttl(\"mykey\");\n        System.out.println(expireResult5);  // >>> -1\n\n        long expireResult6 = jedis.expire(\"mykey\", 10, ExpiryOption.XX);\n        System.out.println(expireResult6);  // >>> 0\n\n        long expireResult7 = jedis.ttl(\"mykey\");\n        System.out.println(expireResult7);  // >>> -1\n\n        long expireResult8 = jedis.expire(\"mykey\", 10, ExpiryOption.NX);\n        System.out.println(expireResult8);  // >>> 1\n\n        long expireResult9 = jedis.ttl(\"mykey\");\n        System.out.println(expireResult9);  // >>> 10\n        // STEP_END\n\n        // Tests for 'expire' step.\n        // REMOVE_START\n        assertEquals(\"OK\", expireResult1);\n        assertEquals(1, expireResult2);\n        assertEquals(10, expireResult3);\n        assertEquals(\"OK\", expireResult4);\n        assertEquals(-1, expireResult5);\n        assertEquals(0, expireResult6);\n        assertEquals(-1, expireResult7);\n        assertEquals(1, expireResult8);\n        assertEquals(10, expireResult9);\n        jedis.del(\"mykey\");\n        // REMOVE_END\n\n\n        // STEP_START ttl\n        String ttlResult1 = jedis.set(\"mykey\", \"Hello\");\n        System.out.println(ttlResult1); // >>> OK\n\n        long ttlResult2 = jedis.expire(\"mykey\", 10);\n        System.out.println(ttlResult2); // >>> 1\n\n        long ttlResult3 = jedis.ttl(\"mykey\");\n        System.out.println(ttlResult3); // >>> 10\n        // STEP_END\n\n        // Tests for 'ttl' step.\n        // REMOVE_START\n        assertEquals(\"OK\", ttlResult1);\n        assertEquals(1, ttlResult2);\n        assertEquals(10, ttlResult3);\n        jedis.del(\"mykey\");\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsHashExample.java",
    "content": "// EXAMPLE: cmds_hash\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.List;\nimport java.util.Collections;\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\n// HIDE_END\n\nimport static java.util.stream.Collectors.toList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n// HIDE_START\npublic class CmdsHashExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        jedis.del(\"myhash\");\n        //REMOVE_END\n// HIDE_END\n\n        // STEP_START hget\n        Map<String, String> hGetExampleParams = new HashMap<>();\n        hGetExampleParams.put(\"field1\", \"foo\");\n\n        long hGetResult1 = jedis.hset(\"myhash\", hGetExampleParams);\n        System.out.println(hGetResult1);    // >>> 1\n\n        String hGetResult2 = jedis.hget(\"myhash\", \"field1\");\n        System.out.println(hGetResult2);    // >>> foo\n\n        String hGetResult3 = jedis.hget(\"myhash\", \"field2\");\n        System.out.println(hGetResult3);    // >>> null\n        // STEP_END\n        // REMOVE_START\n        // Tests for 'hget' step.\n        assertEquals(1, hGetResult1);\n        assertEquals(\"foo\", hGetResult2);\n        assertNull(hGetResult3);\n        jedis.del(\"myhash\");\n        // REMOVE_END\n\n        // STEP_START hgetall\n        Map<String, String> hGetAllExampleParams = new HashMap<>();\n        hGetAllExampleParams.put(\"field1\", \"Hello\");\n        hGetAllExampleParams.put(\"field2\", \"World\");\n\n        long hGetAllResult1 = jedis.hset(\"myhash\", hGetAllExampleParams);\n        System.out.println(hGetAllResult1); // >>> 2\n\n        Map<String, String> hGetAllResult2 = jedis.hgetAll(\"myhash\");\n        System.out.println(\n            hGetAllResult2.entrySet().stream()\n                    .sorted((s1, s2)-> s1.getKey().compareTo(s2.getKey()))\n                    .collect(toList())\n                    .toString()\n        );\n        // >>> [field1=Hello, field2=World]\n        // STEP_END\n        // REMOVE_START\n        // Tests for 'hgetall' step.\n        assertEquals(2, hGetAllResult1);\n        assertEquals(\"[field1=Hello, field2=World]\",\n            hGetAllResult2.entrySet().stream()\n                    .sorted((s1, s2)-> s1.getKey().compareTo(s2.getKey()))\n                    .collect(toList())\n                    .toString()\n        );\n        jedis.del(\"myhash\");\n        // REMOVE_END\n\n        // STEP_START hset\n        Map<String, String> hSetExampleParams = new HashMap<>();\n        hSetExampleParams.put(\"field1\", \"Hello\");\n        long hSetResult1 = jedis.hset(\"myhash\", hSetExampleParams);\n        System.out.println(hSetResult1);    // >>> 1\n\n        String hSetResult2 = jedis.hget(\"myhash\", \"field1\");\n        System.out.println(hSetResult2);    // >>> Hello\n\n        hSetExampleParams.clear();\n        hSetExampleParams.put(\"field2\", \"Hi\");\n        hSetExampleParams.put(\"field3\", \"World\");\n        long hSetResult3 = jedis.hset(\"myhash\",hSetExampleParams);\n        System.out.println(hSetResult3);    // >>> 2\n\n        String hSetResult4 = jedis.hget(\"myhash\", \"field2\");\n        System.out.println(hSetResult4);    // >>> Hi\n\n        String hSetResult5 = jedis.hget(\"myhash\", \"field3\");\n        System.out.println(hSetResult5);    // >>> World\n\n        Map<String, String> hSetResult6 = jedis.hgetAll(\"myhash\");\n        \n        for (String key: hSetResult6.keySet()) {\n            System.out.println(\"Key: \" + key + \", Value: \" + hSetResult6.get(key));\n        }\n        // >>> Key: field3, Value: World\n        // >>> Key: field2, Value: Hi\n        // >>> Key: field1, Value: Hello\n        // STEP_END\n        // REMOVE_START\n        // Tests for 'hset' step.\n        assertEquals(1, hSetResult1);\n        assertEquals(\"Hello\", hSetResult2);\n        assertEquals(2, hSetResult3);\n        assertEquals(\"Hi\", hSetResult4);\n        assertEquals(\"World\", hSetResult5);\n        assertEquals(3, hSetResult6.size());\n        assertEquals(\"Hello\", hSetResult6.get(\"field1\"));\n        assertEquals(\"Hi\", hSetResult6.get(\"field2\"));\n        assertEquals(\"World\", hSetResult6.get(\"field3\"));\n        jedis.del(\"myhash\");\n        // REMOVE_END\n\n        // STEP_START hvals\n        Map<String, String> hValsExampleParams = new HashMap<>();\n        hValsExampleParams.put(\"field1\", \"Hello\");\n        hValsExampleParams.put(\"field2\", \"World\");\n\n        long hValsResult1 = jedis.hset(\"myhash\", hValsExampleParams);\n        System.out.println(hValsResult1); // >>> 2\n\n        List<String> hValsResult2 = jedis.hvals(\"myhash\");\n        Collections.sort(hValsResult2);\n        System.out.println(hValsResult2);\n        // >>> [Hello, World]\n        // STEP_END\n        // REMOVE_START       \n        // Tests for 'hvals' step.\n        assertEquals(2, hValsResult1);\n        assertEquals(\"[Hello, World]\", hValsResult2.toString());\n        jedis.del(\"myhash\");\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsListExample.java",
    "content": "// EXAMPLE: cmds_list\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\nimport java.util.List;\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class CmdsListExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        //REMOVE_START\n        jedis.del(\"mylist\");\n        //REMOVE_END\n// HIDE_END\n\n        // STEP_START llen\n        long lLenResult1 = jedis.lpush(\"mylist\", \"World\");\n        System.out.println(lLenResult1); // >>> 1\n\n        long lLenResult2 = jedis.lpush(\"mylist\", \"Hello\");\n        System.out.println(lLenResult2); // >>> 2\n\n        long lLenResult3 = jedis.llen(\"mylist\");\n        System.out.println(lLenResult3); // >>> 2\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1, lLenResult1);\n        assertEquals(2, lLenResult2);\n        assertEquals(2, lLenResult3);\n        jedis.del(\"mylist\");\n        // REMOVE_END\n\n        // STEP_START lpop\n        long lPopResult1 = jedis.rpush(\n            \"mylist\", \"one\", \"two\", \"three\", \"four\", \"five\"\n        );\n        System.out.println(lPopResult1); // >>> 5\n\n        String lPopResult2 = jedis.lpop(\"mylist\");\n        System.out.println(lPopResult2); // >>> one\n\n        List<String> lPopResult3 = jedis.lpop(\"mylist\", 2);\n        System.out.println(lPopResult3); // >>> [two, three]\n\n        List<String> lPopResult4 = jedis.lrange(\"mylist\", 0, -1);\n        System.out.println(lPopResult4); // >>> [four, five]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(5, lPopResult1);\n        assertEquals(\"one\", lPopResult2);\n        assertEquals(\"[two, three]\", lPopResult3.toString());\n        assertEquals(\"[four, five]\", lPopResult4.toString());\n        jedis.del(\"mylist\");\n        // REMOVE_END\n\n        // STEP_START lpush\n        long lPushResult1 = jedis.lpush(\"mylist\", \"World\");\n        System.out.println(lPushResult1); // >>> 1\n\n        long lPushResult2 = jedis.lpush(\"mylist\", \"Hello\");\n        System.out.println(lPushResult2); // >>> 2\n\n        List<String> lPushResult3 = jedis.lrange(\"mylist\", 0, -1);\n        System.out.println(lPushResult3);\n        // >>> [Hello, World]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1, lPushResult1);\n        assertEquals(2, lPushResult2);\n        assertEquals(\"[Hello, World]\", lPushResult3.toString());\n        jedis.del(\"mylist\");\n        // REMOVE_END\n\n        // STEP_START lrange\n        long lRangeResult1 = jedis.rpush(\"mylist\", \"one\", \"two\", \"three\");\n        System.out.println(lRangeResult1); // >>> 3\n\n        List<String> lRangeResult2 = jedis.lrange(\"mylist\", 0, 0);\n        System.out.println(lRangeResult2); // >>> [one]\n\n        List<String> lRangeResult3 = jedis.lrange(\"mylist\", -3, 2);\n        System.out.println(lRangeResult3); // >>> [one, two, three]\n\n        List<String> lRangeResult4 = jedis.lrange(\"mylist\", -100, 100);\n        System.out.println(lRangeResult4); // >>> [one, two, three]\n\n        List<String> lRangeResult5 = jedis.lrange(\"mylist\", 5, 10);\n        System.out.println(lRangeResult5); // >>> []\n        // STEP_END\n        // REMOVE_START\n        assertEquals(3, lRangeResult1);\n        assertEquals(\"[one]\", lRangeResult2.toString());\n        assertEquals(\"[one, two, three]\", lRangeResult3.toString());\n        assertEquals(\"[one, two, three]\", lRangeResult4.toString());\n        assertEquals(\"[]\", lRangeResult5.toString());\n        jedis.del(\"mylist\");\n        // REMOVE_END\n\n        // STEP_START rpop\n        long rPopResult1 = jedis.rpush(\n            \"mylist\", \"one\", \"two\", \"three\", \"four\", \"five\"\n        );\n        System.out.println(rPopResult1); // >>> 5\n\n        String rPopResult2 = jedis.rpop(\"mylist\");\n        System.out.println(rPopResult2); // >>> five\n\n        List<String> rPopResult3 = jedis.rpop(\"mylist\", 2);\n        System.out.println(rPopResult3); // >>> [four, three]\n\n        List<String> rPopResult4 = jedis.lrange(\"mylist\", 0, -1);\n        System.out.println(rPopResult4); // >>> [one, two]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(5, rPopResult1);\n        assertEquals(\"five\", rPopResult2);\n        assertEquals(\"[four, three]\", rPopResult3.toString());\n        assertEquals(\"[one, two]\", rPopResult4.toString());\n        jedis.del(\"mylist\");\n        // REMOVE_END\n\n        // STEP_START rpush\n        long rPushResult1 = jedis.rpush(\"mylist\", \"hello\");\n        System.out.println(rPushResult1); // >>> 1\n\n        long rPushResult2 = jedis.rpush(\"mylist\", \"world\");\n        System.out.println(rPushResult2); // >>> 2\n\n        List<String> rPushResult3 = jedis.lrange(\"mylist\", 0, -1);\n        System.out.println(rPushResult3); // >>> [hello, world]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1, rPushResult1);\n        assertEquals(2, rPushResult2);\n        assertEquals(\"[hello, world]\", rPushResult3.toString());\n        jedis.del(\"mylist\");\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsServerMgmtExample.java",
    "content": "// EXAMPLE: cmds_servermgmt\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\nimport java.util.Set;\n\nimport redis.clients.jedis.Jedis;\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class CmdsServerMgmtExample {\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n// HIDE_END\n\n        // STEP_START flushall\n        // REMOVE_START\n        jedis.set(\"testkey1\", \"1\");\n        jedis.set(\"testkey2\", \"2\");\n        jedis.set(\"testkey3\", \"3\");\n        // REMOVE_END\n        String flushAllResult1 = jedis.flushAll();\n        System.out.println(flushAllResult1); // >>> OK\n\n        Set<String> flushAllResult2 = jedis.keys(\"*\");\n        System.out.println(flushAllResult2); // >>> []\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", flushAllResult1);\n        assertEquals(\"[]\", flushAllResult2.toString());\n        // REMOVE_END\n\n        // STEP_START info\n        // Note: you must use the `Jedis` class to access the `info`\n        // command rather than `RedisClient`.\n        Jedis jedis2 = new Jedis(\"redis://localhost:6379\");\n\n        String infoResult = jedis2.info();\n        \n        // Check the first 8 characters of the result (the full `info` string\n        // is much longer than this).\n        System.out.println(infoResult.substring(0, 8)); // >>> # Server\n\n        jedis2.close();\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"# Server\", infoResult.substring(0, 8));\n        // REMOVE_END\n        \n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsSetExample.java",
    "content": "// EXAMPLE: cmds_set\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\nimport static java.util.stream.Collectors.toList;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Set;\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\n\npublic class CmdsSetExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        //REMOVE_START\n        jedis.del(\"myset\");\n        //REMOVE_END\n// HIDE_END\n\n        // STEP_START sadd\n        long sAddResult1 = jedis.sadd(\"myset\", \"Hello\");\n        System.out.println(sAddResult1); // >>> 1\n\n        long sAddResult2 = jedis.sadd(\"myset\", \"World\");\n        System.out.println(sAddResult2); // >>> 1\n\n        long sAddResult3 = jedis.sadd(\"myset\", \"World\");\n        System.out.println(sAddResult3); // >>> 0\n\n        Set<String> sAddResult4 = jedis.smembers(\"myset\");\n        System.out.println(sAddResult4.stream().sorted().collect(toList()));\n        // >>> [Hello, World]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1, sAddResult1);\n        assertEquals(1, sAddResult2);\n        assertEquals(0, sAddResult3);\n        assertArrayEquals(new String[] {\"Hello\", \"World\"}, sAddResult4.stream().sorted().toArray());\n        jedis.del(\"myset\");\n        // REMOVE_END\n\n        // STEP_START smembers\n        long sMembersResult1 = jedis.sadd(\"myset\", \"Hello\", \"World\");\n        System.out.println(sMembersResult1); // >>> 2\n\n        Set<String> sMembersResult2 = jedis.smembers(\"myset\");\n        System.out.println(sMembersResult2.stream().sorted().collect(toList()));\n        // >>> [Hello, World]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(2, sMembersResult1);\n        assertArrayEquals(new String[] {\"Hello\", \"World\"}, sMembersResult2.stream().sorted().toArray());\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsSortedSetExample.java",
    "content": "// EXAMPLE: cmds_sorted_set\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.List;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.params.ZRangeParams;\nimport redis.clients.jedis.resps.Tuple;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n// HIDE_START\npublic class CmdsSortedSetExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        jedis.del(\"myzset\");\n        //REMOVE_END\n// HIDE_END\n\n\n        // STEP_START bzmpop\n\n        // STEP_END\n\n        // Tests for 'bzmpop' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START bzpopmax\n\n        // STEP_END\n\n        // Tests for 'bzpopmax' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START bzpopmin\n\n        // STEP_END\n\n        // Tests for 'bzpopmin' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zadd\n        Map<String, Double> zAddExampleParams = new HashMap<>();\n        zAddExampleParams.put(\"one\", 1.0);\n        long zAddResult1 = jedis.zadd(\"myzset\", zAddExampleParams);\n        System.out.println(zAddResult1);    // >>> 1\n\n        zAddExampleParams.clear();\n        zAddExampleParams.put(\"uno\", 1.0);\n        long zAddResult2 = jedis.zadd(\"myzset\", zAddExampleParams);\n        System.out.println(zAddResult2);    // >>> 1\n\n        zAddExampleParams.clear();\n        zAddExampleParams.put(\"two\", 2.0);\n        zAddExampleParams.put(\"three\", 3.0);\n        long zAddResult3 = jedis.zadd(\"myzset\", zAddExampleParams);\n        System.out.println(zAddResult3);    // >>> 2\n\n        List<Tuple> zAddResult4 = jedis.zrangeWithScores(\"myzset\", new ZRangeParams(0, -1));\n\n        for (Tuple item: zAddResult4) {\n            System.out.println(\"Element: \" + item.getElement() + \", Score: \" + item.getScore());\n        }\n        // >>> Element: one, Score: 1.0\n        // >>> Element: uno, Score: 1.0\n        // >>> Element: two, Score: 2.0\n        // >>> Element: three, Score: 3.0\n        // STEP_END\n\n        // Tests for 'zadd' step.\n        // REMOVE_START\n        assertEquals(1, zAddResult1);\n        assertEquals(1, zAddResult2);\n        assertEquals(2, zAddResult3);\n        assertEquals(new Tuple(\"one\", 1.0), zAddResult4.get(0));\n        assertEquals(new Tuple(\"uno\", 1.0), zAddResult4.get(1));\n        assertEquals(new Tuple(\"two\", 2.0), zAddResult4.get(2));\n        assertEquals(new Tuple(\"three\", 3.0), zAddResult4.get(3));\n        jedis.del(\"myzset\");\n        // REMOVE_END\n\n\n        // STEP_START zcard\n\n        // STEP_END\n\n        // Tests for 'zcard' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zcount\n\n        // STEP_END\n\n        // Tests for 'zcount' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zdiff\n\n        // STEP_END\n\n        // Tests for 'zdiff' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zdiffstore\n\n        // STEP_END\n\n        // Tests for 'zdiffstore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zincrby\n\n        // STEP_END\n\n        // Tests for 'zincrby' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zinter\n\n        // STEP_END\n\n        // Tests for 'zinter' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zintercard\n\n        // STEP_END\n\n        // Tests for 'zintercard' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zinterstore\n\n        // STEP_END\n\n        // Tests for 'zinterstore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zlexcount\n\n        // STEP_END\n\n        // Tests for 'zlexcount' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zmpop\n\n        // STEP_END\n\n        // Tests for 'zmpop' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zmscore\n\n        // STEP_END\n\n        // Tests for 'zmscore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zpopmax\n\n        // STEP_END\n\n        // Tests for 'zpopmax' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zpopmin\n\n        // STEP_END\n\n        // Tests for 'zpopmin' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrandmember\n\n        // STEP_END\n\n        // Tests for 'zrandmember' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrange1\n        Map<String, Double> zRangeExampleParams1 = new HashMap<>();\n        zRangeExampleParams1.put(\"one\", 1.0);\n        zRangeExampleParams1.put(\"two\", 2.0);\n        zRangeExampleParams1.put(\"three\", 3.0);\n        long zRangeResult1 = jedis.zadd(\"myzset\", zRangeExampleParams1);\n        System.out.println(zRangeResult1);  // >>> 3\n\n        List<String> zRangeResult2 = jedis.zrange(\"myzset\", new ZRangeParams(0, -1));\n        System.out.println(String.join(\", \", zRangeResult2));   // >>> one, two, three\n\n        List<String> zRangeResult3 = jedis.zrange(\"myzset\", new ZRangeParams(2, 3));\n        System.out.println(String.join(\", \", zRangeResult3));   // >> three\n\n        List<String> zRangeResult4 = jedis.zrange(\"myzset\", new ZRangeParams(-2, -1));\n        System.out.println(String.join(\", \", zRangeResult4));   // >> two, three\n        // STEP_END\n\n        // Tests for 'zrange1' step.\n        // REMOVE_START\n        assertEquals(3, zRangeResult1);\n        assertEquals(\"one, two, three\", String.join(\", \", zRangeResult2));\n        assertEquals(\"three\", String.join(\", \", zRangeResult3));\n        assertEquals(\"two, three\", String.join(\", \", zRangeResult4));\n        jedis.del(\"myzset\");\n        // REMOVE_END\n\n\n        // STEP_START zrange2\n        Map<String, Double> zRangeExampleParams2 = new HashMap<>();\n        zRangeExampleParams2.put(\"one\", 1.0);\n        zRangeExampleParams2.put(\"two\", 2.0);\n        zRangeExampleParams2.put(\"three\", 3.0);\n        long zRangeResult5 = jedis.zadd(\"myzset\", zRangeExampleParams2);\n        System.out.println(zRangeResult5);  // >>> 3\n\n        List<Tuple> zRangeResult6 = jedis.zrangeWithScores(\"myzset\", new ZRangeParams(0, 1));\n\n        for (Tuple item: zRangeResult6) {\n            System.out.println(\"Element: \" + item.getElement() + \", Score: \" + item.getScore());\n        }\n        // >>> Element: one, Score: 1.0\n        // >>> Element: two, Score: 2.0\n        // STEP_END\n\n        // Tests for 'zrange2' step.\n        // REMOVE_START\n        assertEquals(3, zRangeResult5);\n        assertEquals(new Tuple(\"one\", 1.0), zRangeResult6.get(0));\n        assertEquals(new Tuple(\"two\", 2.0), zRangeResult6.get(1));\n        jedis.del(\"myzset\");\n        // REMOVE_END\n\n\n        // STEP_START zrange3\n        Map<String, Double> zRangeExampleParams3 = new HashMap<>();\n        zRangeExampleParams3.put(\"one\", 1.0);\n        zRangeExampleParams3.put(\"two\", 2.0);\n        zRangeExampleParams3.put(\"three\", 3.0);\n        long zRangeResult7 = jedis.zadd(\"myzset\", zRangeExampleParams3);\n        System.out.println(zRangeResult7);  // >>> 3\n\n        List<String> zRangeResult8 = jedis.zrangeByScore(\"myzset\", \"(1\", \"+inf\", 1, 1);\n        System.out.println(String.join(\", \", zRangeResult8));   // >>> three\n        // STEP_END\n\n        // Tests for 'zrange3' step.\n        // REMOVE_START\n        assertEquals(3, zRangeResult7);\n        assertEquals(\"three\", String.join(\", \", zRangeResult8));\n        jedis.del(\"myzset\");\n        // REMOVE_END\n\n\n        // STEP_START zrangebylex\n\n        // STEP_END\n\n        // Tests for 'zrangebylex' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrangebyscore\n\n        // STEP_END\n\n        // Tests for 'zrangebyscore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrangestore\n\n        // STEP_END\n\n        // Tests for 'zrangestore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrank\n\n        // STEP_END\n\n        // Tests for 'zrank' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrem\n\n        // STEP_END\n\n        // Tests for 'zrem' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zremrangebylex\n\n        // STEP_END\n\n        // Tests for 'zremrangebylex' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zremrangebyrank\n\n        // STEP_END\n\n        // Tests for 'zremrangebyrank' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zremrangebyscore\n\n        // STEP_END\n\n        // Tests for 'zremrangebyscore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrevrange\n\n        // STEP_END\n\n        // Tests for 'zrevrange' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrevrangebylex\n\n        // STEP_END\n\n        // Tests for 'zrevrangebylex' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrevrangebyscore\n\n        // STEP_END\n\n        // Tests for 'zrevrangebyscore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zrevrank\n\n        // STEP_END\n\n        // Tests for 'zrevrank' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zscan\n\n        // STEP_END\n\n        // Tests for 'zscan' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zscore\n\n        // STEP_END\n\n        // Tests for 'zscore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zunion\n\n        // STEP_END\n\n        // Tests for 'zunion' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n        // STEP_START zunionstore\n\n        // STEP_END\n\n        // Tests for 'zunionstore' step.\n        // REMOVE_START\n\n        // REMOVE_END\n\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CmdsStringExample.java",
    "content": "// EXAMPLE: cmds_string\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class CmdsStringExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        jedis.del(\"mykey\");\n        //REMOVE_END\n// HIDE_END\n\n        // STEP_START incr\n        String incrResult1 = jedis.set(\"mykey\", \"10\");\n        System.out.println(incrResult1);    // >>> OK\n\n        long incrResult2 = jedis.incr(\"mykey\");\n        System.out.println(incrResult2);    // >>> 11\n\n        String incrResult3 = jedis.get(\"mykey\");\n        System.out.println(incrResult3);    // >>> 11\n        // STEP_END\n\n        // Tests for 'incr' step.\n        // REMOVE_START\n        assertEquals(\"OK\", incrResult1);\n        assertEquals(11, incrResult2);\n        assertEquals(\"11\", incrResult3);\n        jedis.del(\"mykey\");\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/CuckooFilterExample.java",
    "content": "// EXAMPLE: cuckoo_tutorial\n\n// HIDE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class CuckooFilterExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // HIDE_END\n\n        // REMOVE_START\n        jedis.del(\"bikes:models\");\n        // REMOVE_END\n\n        // STEP_START cuckoo\n        String res1 = jedis.cfReserve(\"bikes:models\", 1000000);\n        System.out.println(res1); // >>> OK\n\n        // REMOVE_START\n        assertEquals(res1, \"OK\");\n        // REMOVE_END\n\n        boolean res2 = jedis.cfAdd(\"bikes:models\", \"Smoky Mountain Striker\");\n        System.out.println(res2); // >>> True\n\n        boolean res3 = jedis.cfExists(\"bikes:models\", \"Smoky Mountain Striker\");\n        System.out.println(res3); // >>> True\n\n        boolean res4 = jedis.cfExists(\"bikes:models\", \"Terrible Bike Name\");\n        System.out.println(res4); // >>> False\n\n        boolean res5 = jedis.cfDel(\"bikes:models\", \"Smoky Mountain Striker\");\n        System.out.println(res5); // >>> True\n\n        // REMOVE_START\n        assertTrue(res5);\n        // REMOVE_END\n        // STEP_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/GeoExample.java",
    "content": "//EXAMPLE: geo_tutorial\npackage io.redis.examples;\n\n// REMOVE_START\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class GeoExample {\n\n  @Test\n  public void run() {\n    try (RedisClient jedis = RedisClient.create(\"redis://localhost:6379\")) {\n      // REMOVE_START\n      jedis.del(\"bikes:rentable\");\n      // REMOVE_END\n\n      // STEP_START geoadd\n      long res1 = jedis.geoadd(\"bikes:rentable\", -122.27652, 37.805186, \"station:1\");\n      System.out.println(res1); // 1\n\n      long res2 = jedis.geoadd(\"bikes:rentable\", -122.2674626, 37.8062344, \"station:2\");\n      System.out.println(res2); // 1\n\n      long res3 = jedis.geoadd(\"bikes:rentable\", -122.2469854, 37.8104049, \"station:3\");\n      System.out.println(res2); // 1\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(1, res1);\n      assertEquals(1, res1);\n      assertEquals(1, res1);\n      // REMOVE_END\n\n      // STEP_START geosearch\n      List<GeoRadiusResponse> res4 = jedis.geosearch(\n          \"bikes:rentable\",\n          new GeoCoordinate(-122.27652, 37.805186),\n          5,\n          GeoUnit.KM\n      );\n      List<String> members = res4.stream() //\n          .map(GeoRadiusResponse::getMemberByString) //\n          .collect(Collectors.toList());\n      System.out.println(members); // [station:1, station:2, station:3]\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(\"[station:1, station:2, station:3]\", members.toString());\n      // REMOVE_END\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/GeoIndexExample.java",
    "content": "// EXAMPLE: geoindex\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport org.json.JSONObject;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.Document;\nimport redis.clients.jedis.search.FTCreateParams;\nimport redis.clients.jedis.search.FTSearchParams;\nimport redis.clients.jedis.search.IndexDataType;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.search.schemafields.GeoShapeField.CoordinateSystem;\nimport redis.clients.jedis.search.SearchResult;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class GeoIndexExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        try {\n            jedis.ftDropIndex(\"productidx\");\n        } catch (JedisDataException j) {}\n        \n        try {\n            jedis.ftDropIndex(\"geomidx\");\n        } catch (JedisDataException j) {}\n        \n        jedis.del(\"product:46885\", \"product:46886\", \"shape:1\", \"shape:2\", \"shape:3\", \"shape:4\");\n        //REMOVE_END\n// HIDE_END\n\n        // STEP_START create_geo_idx\n        SchemaField[] geoSchema = {\n            GeoField.of(\"$.location\").as(\"location\")\n        };\n\n        String geoIdxCreateResult = jedis.ftCreate(\"productidx\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"product:\"),\n            geoSchema\n        );\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", geoIdxCreateResult);\n        // REMOVE_END\n        \n        // STEP_START add_geo_json\n        JSONObject prd46885 = new JSONObject()\n                .put(\"description\", \"Navy Blue Slippers\")\n                .put(\"price\", 45.99)\n                .put(\"city\", \"Denver\")\n                .put(\"location\", \"-104.991531, 39.742043\");\n        \n        String jsonAddResult1 = jedis.jsonSet(\"product:46885\", new Path2(\"$\"), prd46885);\n        System.out.println(jsonAddResult1); // >>> OK\n\n        JSONObject prd46886 = new JSONObject()\n                .put(\"description\", \"Bright Green Socks\")\n                .put(\"price\", 25.50)\n                .put(\"city\", \"Fort Collins\")\n                .put(\"location\", \"-105.0618814,40.5150098\");\n        \n        String jsonAddResult2 = jedis.jsonSet(\"product:46886\", new Path2(\"$\"), prd46886);\n        System.out.println(jsonAddResult2); // >>> OK\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", jsonAddResult1);\n        assertEquals(\"OK\", jsonAddResult2);\n        // REMOVE_END\n\n        // STEP_START geo_query\n        SearchResult geoResult = jedis.ftSearch(\"productidx\",\n            \"@location:[-104.800644 38.846127 100 mi]\"\n        );\n\n        System.out.println(geoResult.getTotalResults()); // >>> 1\n\n        for (Document doc: geoResult.getDocuments()) {\n            System.out.println(doc.getId());\n        }\n        // >>> product:46885\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", jsonAddResult1);\n        assertEquals(\"OK\", jsonAddResult2);\n        assertEquals(\"product:46885\", geoResult.getDocuments().get(0).getId());\n        // REMOVE_END\n\n        // STEP_START create_gshape_idx\n        SchemaField[] geomSchema = {\n            TextField.of(\"$.name\").as(\"name\"),\n            GeoShapeField.of(\"$.geom\", CoordinateSystem.FLAT).as(\"geom\")\n        };\n\n        String geomIndexCreateResult = jedis.ftCreate(\"geomidx\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"shape\"),\n            geomSchema\n        );\n        System.out.println(geomIndexCreateResult); // >>> OK\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", geomIndexCreateResult);\n        // REMOVE_END\n\n        // STEP_START add_gshape_json\n        JSONObject shape1 = new JSONObject()\n                .put(\"name\", \"Green Square\")\n                .put(\"geom\", \"POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))\");\n        \n        String gmJsonRes1 = jedis.jsonSet(\"shape:1\", new Path2(\"$\"), shape1);\n        System.out.println(gmJsonRes1); // >>> OK\n\n        JSONObject shape2 = new JSONObject()\n                .put(\"name\", \"Red Rectangle\")\n                .put(\"geom\", \"POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))\");\n        \n        String gmJsonRes2 = jedis.jsonSet(\"shape:2\", new Path2(\"$\"), shape2);\n        System.out.println(gmJsonRes2); // >>> OK\n\n        JSONObject shape3 = new JSONObject()\n                .put(\"name\", \"Blue Triangle\")\n                .put(\"geom\", \"POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))\");\n        \n        String gmJsonRes3 = jedis.jsonSet(\"shape:3\", new Path2(\"$\"), shape3);\n        System.out.println(gmJsonRes3); // >>> OK\n\n        JSONObject shape4 = new JSONObject()\n                .put(\"name\", \"Purple Point\")\n                .put(\"geom\", \"POINT (2 2)\");\n        \n        String gmJsonRes4 = jedis.jsonSet(\"shape:4\", new Path2(\"$\"), shape4);\n        System.out.println(gmJsonRes4); // >>> OK\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", gmJsonRes1);\n        assertEquals(\"OK\", gmJsonRes2);\n        assertEquals(\"OK\", gmJsonRes3);\n        assertEquals(\"OK\", gmJsonRes4);\n        // REMOVE_END\n\n        // STEP_START gshape_query\n        SearchResult geomResult = jedis.ftSearch(\"geomidx\",\n            \"(-@name:(Green Square) @geom:[WITHIN $qshape])\",\n            FTSearchParams.searchParams()\n                    .addParam(\"qshape\", \"POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))\")\n                    .dialect(4)\n                    .limit(0, 1)\n        );\n        System.out.println(geomResult.getTotalResults()); // >>> 1\n\n        for (Document doc: geomResult.getDocuments()) {\n            System.out.println(doc.getId());\n        }\n        // shape:4\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1, geomResult.getTotalResults());\n        assertEquals(\"shape:4\", geomResult.getDocuments().get(0).getId());\n        // REMOVE_END\n        // HIDE_START\n\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/HashExample.java",
    "content": "//EXAMPLE: hash_tutorial\npackage io.redis.examples;\n\nimport redis.clients.jedis.RedisClient;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n//REMOVE_START\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n//REMOVE_END\n\npublic class HashExample {\n\n  @Test\n  public void run() {\n    try (RedisClient jedis = RedisClient.create(\"redis://localhost:6379\")) {\n      // REMOVE_START\n      jedis.del(\"bike:1\", \"bike:1:stats\");\n      // REMOVE_END\n\n      // STEP_START set_get_all\n      Map<String, String> bike1 = new HashMap<>();\n      bike1.put(\"model\", \"Deimos\");\n      bike1.put(\"brand\", \"Ergonom\");\n      bike1.put(\"type\", \"Enduro bikes\");\n      bike1.put(\"price\", \"4972\");\n\n      Long res1 = jedis.hset(\"bike:1\", bike1);\n      System.out.println(res1); // 4\n\n      String res2 = jedis.hget(\"bike:1\", \"model\");\n      System.out.println(res2); // Deimos\n\n      String res3 = jedis.hget(\"bike:1\", \"price\");\n      System.out.println(res3); // 4972\n\n      Map<String, String> res4 = jedis.hgetAll(\"bike:1\");\n      System.out.println(res4); // {type=Enduro bikes, brand=Ergonom, price=4972, model=Deimos}\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(4, res1.longValue());\n      assertEquals(\"Deimos\", res2);\n      assertEquals(\"4972\", res3);\n      assertEquals(\"Deimos\", res4.get(\"model\"));\n      assertEquals(\"Ergonom\", res4.get(\"brand\"));\n      assertEquals(\"Enduro bikes\", res4.get(\"type\"));\n      assertEquals(\"4972\", res4.get(\"price\"));\n      // REMOVE_END\n\n      // STEP_START hmget\n      List<String> res5 = jedis.hmget(\"bike:1\", \"model\", \"price\");\n      System.out.println(res5); // [Deimos, 4972]\n      // STEP_END\n\n      // REMOVE_START\n      assert res5.toString().equals(\"[Deimos, 4972]\");\n      // REMOVE_END\n\n      // STEP_START hincrby\n      Long res6 = jedis.hincrBy(\"bike:1\", \"price\", 100);\n      System.out.println(res6); // 5072\n      Long res7 = jedis.hincrBy(\"bike:1\", \"price\", -100);\n      System.out.println(res7); // 4972\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(5072, res6.longValue());\n      assertEquals(4972, res7.longValue());\n      // REMOVE_END\n\n      // STEP_START incrby_get_mget\n      Long res8 = jedis.hincrBy(\"bike:1:stats\", \"rides\", 1);\n      System.out.println(res8); // 1\n      Long res9 = jedis.hincrBy(\"bike:1:stats\", \"rides\", 1);\n      System.out.println(res9); // 2\n      Long res10 = jedis.hincrBy(\"bike:1:stats\", \"rides\", 1);\n      System.out.println(res10); // 3\n      Long res11 = jedis.hincrBy(\"bike:1:stats\", \"crashes\", 1);\n      System.out.println(res11); // 1\n      Long res12 = jedis.hincrBy(\"bike:1:stats\", \"owners\", 1);\n      System.out.println(res12); // 1\n      String res13 = jedis.hget(\"bike:1:stats\", \"rides\");\n      System.out.println(res13); // 3\n      List<String> res14 = jedis.hmget(\"bike:1:stats\", \"crashes\", \"owners\");\n      System.out.println(res14); // [1, 1]\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(1, res8.longValue());\n      assertEquals(2, res9.longValue());\n      assertEquals(3, res10.longValue());\n      assertEquals(1, res11.longValue());\n      assertEquals(1, res12.longValue());\n      assertEquals(\"3\", res13);\n      assertEquals(\"[1, 1]\", res14.toString());\n      // REMOVE_END\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/HomeJsonExample.java",
    "content": "// EXAMPLE: java_home_json\n// BINDER_ID jedis-java_home_json\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// REMOVE_END\n// STEP_START import\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.*;\nimport redis.clients.jedis.search.schemafields.*;\nimport org.json.JSONObject;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n// STEP_END\n\npublic class HomeJsonExample {\n\n    @Test\n    public void run() {\n\n        // STEP_START create_data\n        JSONObject user1 = new JSONObject()\n                .put(\"name\", \"Paul John\")\n                .put(\"email\", \"paul.john@example.com\")\n                .put(\"age\", 42)\n                .put(\"city\", \"London\");\n        \n        JSONObject user2 = new JSONObject()\n                .put(\"name\", \"Eden Zamir\")\n                .put(\"email\", \"eden.zamir@example.com\")\n                .put(\"age\", 29)\n                .put(\"city\", \"Tel Aviv\");\n        \n        JSONObject user3 = new JSONObject()\n                .put(\"name\", \"Paul Zamir\")\n                .put(\"email\", \"paul.zamir@example.com\")\n                .put(\"age\", 35)\n                .put(\"city\", \"Tel Aviv\");\n        // STEP_END\n\n        // STEP_START connect\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // STEP_END\n\n        // STEP_START cleanup_json\n        try {jedis.ftDropIndex(\"idx:users\");} catch (JedisDataException j){}\n        jedis.del(\"user:1\", \"user:2\", \"user:3\");\n        // STEP_END\n\n        // STEP_START make_index\n        SchemaField[] schema = {\n            TextField.of(\"$.name\").as(\"name\"),\n            TextField.of(\"$.city\").as(\"city\"),\n            NumericField.of(\"$.age\").as(\"age\")\n        };\n\n        String createResult = jedis.ftCreate(\"idx:users\",\n            FTCreateParams.createParams()\n                .on(IndexDataType.JSON)\n                .addPrefix(\"user:\"),\n                schema\n        );\n\n        System.out.println(createResult); // >>> OK\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", createResult);\n        // REMOVE_END\n\n        // STEP_START add_data\n        String user1Set = jedis.jsonSet(\"user:1\", new Path2(\"$\"), user1);\n        String user2Set = jedis.jsonSet(\"user:2\", new Path2(\"$\"), user2);\n        String user3Set = jedis.jsonSet(\"user:3\", new Path2(\"$\"), user3);\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", user1Set);\n        assertEquals(\"OK\", user2Set);\n        assertEquals(\"OK\", user3Set);\n        // REMOVE_END\n\n        // STEP_START query1\n        SearchResult findPaulResult = jedis.ftSearch(\"idx:users\",\n             \"Paul @age:[30 40]\"\n        );\n        \n        System.out.println(findPaulResult.getTotalResults()); // >>> 1\n\n        List<Document> paulDocs = findPaulResult.getDocuments();\n\n        for (Document doc: paulDocs) {\n            System.out.println(doc.getId());\n        }\n        // >>> user:3\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"user:3\", paulDocs.get(0).getId());\n        // REMOVE_END\n\n        // STEP_START query2\n        SearchResult citiesResult = jedis.ftSearch(\"idx:users\",\n            \"Paul\",\n            FTSearchParams.searchParams()\n                .returnFields(\"city\")\n        );\n\n        System.out.println(citiesResult.getTotalResults()); // >>> 2\n\n        for (Document doc: citiesResult.getDocuments()) {\n            System.out.println(doc.getId());\n        }\n        // >>> user:1\n        // >>> user:3\n        // STEP_END\n        // REMOVE_START\n        assertArrayEquals(\n            new String[] {\"user:1\", \"user:3\"},\n            citiesResult.getDocuments().stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n        // STEP_START query3\n        AggregationResult aggResult = jedis.ftAggregate(\"idx:users\",\n            new AggregationBuilder(\"*\")\n                .groupBy(\"@city\", Reducers.count().as(\"count\"))\n        );\n\n        System.out.println(aggResult.getTotalResults()); // >>> 2\n\n        for (Row cityRow: aggResult.getRows()) {\n            System.out.printf(\"%s - %d%n\",\n                cityRow.getString(\"city\"), cityRow.getLong(\"count\"));\n        }\n        // >>> London - 1\n        // >>> Tel Aviv - 2\n        // STEP_END\n        // REMOVE_START\n        assertArrayEquals(\n            new String[] {\"London - 1\", \"Tel Aviv - 2\"},\n            aggResult.getRows().stream()\n                    .map(r -> r.getString(\"city\") + \" - \" + r.getString(\"count\"))\n                    .sorted().toArray());\n        // REMOVE_END\n\n        // STEP_START cleanup_hash\n        try {jedis.ftDropIndex(\"hash-idx:users\");} catch (JedisDataException j){}\n        jedis.del(\"huser:1\", \"huser:2\", \"huser:3\");\n        // STEP_END\n\n        // STEP_START make_hash_index\n        SchemaField[] hashSchema = {\n            TextField.of(\"name\"),\n            TextField.of(\"city\"),\n            NumericField.of(\"age\")\n        };\n\n        String hashCreateResult = jedis.ftCreate(\"hash-idx:users\",\n            FTCreateParams.createParams()\n                .on(IndexDataType.HASH)\n                .addPrefix(\"huser:\"),\n                hashSchema\n        );\n\n        System.out.println(hashCreateResult); // >>> OK\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", hashCreateResult);\n        // REMOVE_END\n\n        // STEP_START add_hash_data\n        Map<String, String> user1Info = new HashMap<>();\n        user1Info.put(\"name\", \"Paul John\");\n        user1Info.put(\"email\", \"paul.john@example.com\");\n        user1Info.put(\"age\", \"42\");\n        user1Info.put(\"city\", \"London\");\n        long huser1Set = jedis.hset(\"huser:1\", user1Info);\n        \n        System.out.println(huser1Set); // >>> 4\n\n        Map<String, String> user2Info = new HashMap<>();\n        user2Info.put(\"name\", \"Eden Zamir\");\n        user2Info.put(\"email\", \"eden.zamir@example.com\");\n        user2Info.put(\"age\", \"29\");\n        user2Info.put(\"city\", \"Tel Aviv\");\n        long huser2Set = jedis.hset(\"huser:2\", user2Info);\n        \n        System.out.println(huser2Set); // >>> 4\n\n        Map<String, String> user3Info = new HashMap<>();\n        user3Info.put(\"name\", \"Paul Zamir\");\n        user3Info.put(\"email\", \"paul.zamir@example.com\");\n        user3Info.put(\"age\", \"35\");\n        user3Info.put(\"city\", \"Tel Aviv\");\n        long huser3Set = jedis.hset(\"huser:3\", user3Info);\n        \n        System.out.println(huser3Set); // >>> 4\n        // STEP_END\n        // REMOVE_START\n        assertEquals(4, huser1Set);\n        assertEquals(4, huser2Set);\n        assertEquals(4, huser3Set);\n        // REMOVE_END\n        \n        // STEP_START query1_hash\n        SearchResult findPaulHashResult = jedis.ftSearch(\"hash-idx:users\",\n             \"Paul @age:[30 40]\"\n        );\n        \n        System.out.println(findPaulHashResult.getTotalResults()); // >>> 1\n\n        List<Document> paulHashDocs = findPaulHashResult.getDocuments();\n\n        for (Document doc: paulHashDocs) {\n            System.out.println(doc.getId());\n        }\n        // >>> user:3\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"huser:3\", paulHashDocs.get(0).getId());\n        // REMOVE_END\n\n        // STEP_START close\n        jedis.close();\n        // STEP_END\n    }\n}\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/HomeProbDtsExample.java",
    "content": "// EXAMPLE: home_prob_dts\npackage io.redis.examples;\n// REMOVE_START\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// REMOVE_END\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class HomeProbDtsExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        // REMOVE_START\n        jedis.del(\n            \"recorded_users\", \"other_users\",\n            \"group:1\", \"group:2\", \"both_groups\",\n            \"items_sold\",\n            \"male_heights\", \"female_heights\", \"all_heights\",\n            \"top_3_songs\"\n        );\n        // REMOVE_END\n\n        // STEP_START bloom\n        List<Boolean> res1 = jedis.bfMAdd(\n            \"recorded_users\",\n            \"andy\", \"cameron\", \"david\", \"michelle\"\n        );\n        System.out.println(res1);  // >>> [true, true, true, true]\n\n        boolean res2 = jedis.bfExists(\"recorded_users\", \"cameron\");\n        System.out.println(res2);  // >>> true\n\n        boolean res3 = jedis.bfExists(\"recorded_users\", \"kaitlyn\");\n        System.out.println(res3);  // >>> false\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"[true, true, true, true]\", res1.toString());\n        assertTrue(res2);\n        assertFalse(res3);\n        // REMOVE_END\n\n        // STEP_START cuckoo\n        boolean res4 = jedis.cfAdd(\"other_users\", \"paolo\");\n        System.out.println(res4);  // >>> true\n\n        boolean res5 = jedis.cfAdd(\"other_users\", \"kaitlyn\");\n        System.out.println(res5);  // >>> true\n\n        boolean res6 = jedis.cfAdd(\"other_users\", \"rachel\");\n        System.out.println(res6);  // >>> true\n\n        List<Boolean> res7 = jedis.cfMExists(\n            \"other_users\",\n            \"paolo\", \"rachel\", \"andy\"\n        );\n        System.out.println(res7);  // >>> [true, true, false]\n\n        boolean res8 = jedis.cfDel(\"other_users\", \"paolo\");\n        System.out.println(res8);  // >>> true\n\n        boolean res9 = jedis.cfExists(\"other_users\", \"paolo\");\n        System.out.println(res9);  // >>> false\n        // STEP_END\n        // REMOVE_START\n        assertTrue(res4);\n        assertTrue(res5);\n        assertTrue(res6);\n        assertEquals(\"[true, true, false]\", res7.toString());\n        assertTrue(res8);\n        assertFalse(res9);\n        // REMOVE_END\n\n        // STEP_START hyperloglog\n        long res10 = jedis.pfadd(\"group:1\", \"andy\", \"cameron\", \"david\");\n        System.out.println(res10);  // >>> 1\n\n        long res11 = jedis.pfcount(\"group:1\");\n        System.out.println(res11);  // >>> 3\n\n        long res12 = jedis.pfadd(\n            \"group:2\",\n            \"kaitlyn\", \"michelle\", \"paolo\", \"rachel\"\n        );\n        System.out.println(res12);  // >>> 1\n\n        long res13 = jedis.pfcount(\"group:2\");\n        System.out.println(res13);  // >>> 4\n\n        String res14 = jedis.pfmerge(\"both_groups\", \"group:1\", \"group:2\");\n        System.out.println(res14);  // >>> OK\n\n        long res15 = jedis.pfcount(\"both_groups\");\n        System.out.println(res15);  // >>> 7\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1, res10);\n        assertEquals(3, res11);\n        assertEquals(1, res12);\n        assertEquals(4, res13);\n        assertEquals(\"OK\", res14);\n        assertEquals(7, res15);\n        // REMOVE_END\n\n        // STEP_START cms\n        // Specify that you want to keep the counts within 0.01\n        // (0.1%) of the true value with a 0.005 (0.05%) chance\n        // of going outside this limit.\n        String res16 = jedis.cmsInitByProb(\"items_sold\", 0.01, 0.005);\n        System.out.println(res16);  // >>> OK\n\n        Map<String, Long> firstItemIncrements = new HashMap<>();\n        firstItemIncrements.put(\"bread\", 300L);\n        firstItemIncrements.put(\"tea\", 200L);\n        firstItemIncrements.put(\"coffee\", 200L);\n        firstItemIncrements.put(\"beer\", 100L);\n\n        List<Long> res17 = jedis.cmsIncrBy(\"items_sold\",\n            firstItemIncrements\n        );\n        res17.sort(null);\n        System.out.println();  // >>> [100, 200, 200, 300]\n\n        Map<String, Long> secondItemIncrements = new HashMap<>();\n        secondItemIncrements.put(\"bread\", 100L);\n        secondItemIncrements.put(\"coffee\", 150L);\n\n        List<Long> res18 = jedis.cmsIncrBy(\"items_sold\",\n            secondItemIncrements\n        );\n        res18.sort(null);\n        System.out.println(res18);  // >>> [350, 400]\n\n        List<Long> res19 = jedis.cmsQuery(\n            \"items_sold\",\n            \"bread\", \"tea\", \"coffee\", \"beer\"\n        );\n        res19.sort(null);\n        System.out.println(res19);  // >>> [100, 200, 350, 400]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res16);\n        assertEquals(\"[100, 200, 200, 300]\", res17.toString());\n        assertEquals(\"[350, 400]\", res18.toString());\n        assertEquals(\"[100, 200, 350, 400]\", res19.toString());\n        // REMOVE_END\n\n        // STEP_START tdigest\n        String res20 = jedis.tdigestCreate(\"male_heights\");\n        System.out.println(res20);  // >>> OK\n\n        String res21 = jedis.tdigestAdd(\"male_heights\", \n            175.5, 181, 160.8, 152, 177, 196, 164);\n        System.out.println(res21);  // >>> OK\n\n        double res22 = jedis.tdigestMin(\"male_heights\");\n        System.out.println(res22);  // >>> 152.0\n\n        double res23 = jedis.tdigestMax(\"male_heights\");\n        System.out.println(res23);  // >>> 196.0\n\n        List<Double> res24 = jedis.tdigestQuantile(\"male_heights\", 0.75);\n        System.out.println(res24);  // >>> [181.0]\n\n        // Note that the CDF value for 181 is not exactly 0.75.\n        // Both values are estimates.\n        List<Double> res25 = jedis.tdigestCDF(\"male_heights\", 181);\n        System.out.println(res25);  // >>> [0.7857142857142857]\n\n        String res26 = jedis.tdigestCreate(\"female_heights\");\n        System.out.println(res26);  // >>> OK\n\n        String res27 = jedis.tdigestAdd(\"female_heights\",\n            155.5, 161, 168.5, 170, 157.5, 163, 171);\n        System.out.println(res27);  // >>> OK\n\n        List<Double> res28 = jedis.tdigestQuantile(\"female_heights\", 0.75);\n        System.out.println(res28);  // >>> [170.0]\n\n        String res29 = jedis.tdigestMerge(\n            \"all_heights\",\n            \"male_heights\", \"female_heights\"\n        );\n        System.out.println(res29);  // >>> OK\n        List<Double> res30 = jedis.tdigestQuantile(\"all_heights\", 0.75);\n        System.out.println(res30);  // >>> [175.5]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res20);\n        assertEquals(\"OK\", res21);\n        assertEquals(152.0, res22);\n        assertEquals(196.0, res23);\n        assertEquals(\"[181.0]\", res24.toString());\n        assertEquals(\"[0.7857142857142857]\", res25.toString());\n        assertEquals(\"OK\", res26);\n        assertEquals(\"OK\", res27);\n        assertEquals(\"[170.0]\", res28.toString());\n        assertEquals(\"OK\", res29);\n        assertEquals(\"[175.5]\", res30.toString());\n        // REMOVE_END\n\n        // STEP_START topk\n        String res31 = jedis.topkReserve(\"top_3_songs\", 3L, 2000L, 7L, 0.925D);\n        System.out.println(res31);  // >>> OK\n\n        Map<String, Long> songIncrements = new HashMap<>();\n        songIncrements.put(\"Starfish Trooper\", 3000L);\n        songIncrements.put(\"Only one more time\", 1850L);\n        songIncrements.put(\"Rock me, Handel\", 1325L);\n        songIncrements.put(\"How will anyone know?\", 3890L);\n        songIncrements.put(\"Average lover\", 4098L);\n        songIncrements.put(\"Road to everywhere\", 770L);\n\n        List<String> res32 = jedis.topkIncrBy(\"top_3_songs\",\n            songIncrements\n        );\n        System.out.println(res32);\n        // >>> [null, null, null, null, null, Rock me, Handel]\n\n        List<String> res33 = jedis.topkList(\"top_3_songs\");\n        System.out.println(res33);\n        // >>> [Average lover, How will anyone know?, Starfish Trooper]\n\n        List<Boolean> res34 = jedis.topkQuery(\"top_3_songs\",\n            \"Starfish Trooper\", \"Road to everywhere\"\n        );\n        System.out.println(res34);\n        // >>> [true, false]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res31);\n        // Value of res32 is not deterministic.\n        assertEquals(\"[Average lover, How will anyone know?, Starfish Trooper]\", res33.toString());\n        assertEquals(\"[true, false]\", res34.toString());\n        // REMOVE_END\n    }\n}"
  },
  {
    "path": "src/test/java/io/redis/examples/HyperLogLogExample.java",
    "content": "// EXAMPLE: hll_tutorial\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class HyperLogLogExample {\n\n    @Test\n    public void run() {\n        // HIDE_START\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // HIDE_END\n\n        // REMOVE_START\n        jedis.del(\"bikes\", \"commuter_bikes\", \"all_bikes\");\n        // REMOVE_END\n\n        // STEP_START pfadd\n        long res1 = jedis.pfadd(\"bikes\", \"Hyperion\", \"Deimos\", \"Phoebe\", \"Quaoar\");\n        System.out.println(res1); // >>> 1\n\n        long res2 = jedis.pfcount(\"bikes\");\n        System.out.println(res2); // >>> 4\n\n        long res3 = jedis.pfadd(\"commuter_bikes\", \"Salacia\", \"Mimas\", \"Quaoar\");\n        System.out.println(res3); // >>> 1\n\n        String res4 = jedis.pfmerge(\"all_bikes\", \"bikes\", \"commuter_bikes\");\n        System.out.println(res4); // >>> OK\n\n        // REMOVE_START\n        assertEquals(\"OK\", res4);\n        // REMOVE_END\n\n        long res5 = jedis.pfcount(\"all_bikes\");\n        System.out.println(res5); // >>> 6\n        // STEP_END\n\n        // HIDE_START\n        jedis.close();\n        // HIDE_END\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/JsonExample.java",
    "content": "// EXAMPLE: json_tutorial\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n// REMOVE_END\n\n// HIDE_START\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.json.Path2;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class JsonExample {\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n// HIDE_END\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        jedis.del(\"bike\", \"bike:1\", \"crashes\", \"newbike\", \"riders\", \"bikes:inventory\");\n        //REMOVE_END\n\n        // STEP_START set_get\n        String res1 = jedis.jsonSet(\"bike\", new Path2(\"$\"), \"\\\"Hyperion\\\"\");\n        System.out.println(res1);   // >>> OK\n\n        Object res2 = jedis.jsonGet(\"bike\", new Path2(\"$\"));\n        System.out.println(res2);   // >>> [\"Hyperion\"]\n\n        List<Class<?>> res3 = jedis.jsonType(\"bike\", new Path2(\"$\"));\n        System.out.println(res3);   // >>> [class java.lang.String]\n        // STEP_END\n\n        // Tests for 'set_get' step.\n        // REMOVE_START\n        assertEquals(\"OK\", res1);\n        assertEquals(\"[\\\"Hyperion\\\"]\", res2.toString());\n        assertEquals(\"[class java.lang.String]\", res3.toString());\n        // REMOVE_END\n\n\n        // STEP_START str\n        List<Long> res4 = jedis.jsonStrLen(\"bike\", new Path2(\"$\"));\n        System.out.println(res4);   // >>> [8]\n\n        List<Long> res5 = jedis.jsonStrAppend(\"bike\", new Path2(\"$\"), \" (Enduro bikes)\");\n        System.out.println(res5);   // >>> [23]\n\n        Object res6 = jedis.jsonGet(\"bike\", new Path2(\"$\"));\n        System.out.println(res6);   // >>> [\"Hyperion (Enduro bikes)\"]\n        // STEP_END\n\n        // Tests for 'str' step.\n        // REMOVE_START\n        assertEquals(\"[8]\", res4.toString());\n        assertEquals(\"[23]\", res5.toString());\n        assertEquals(\"[\\\"Hyperion (Enduro bikes)\\\"]\", res6.toString());\n        // REMOVE_END\n\n\n        // STEP_START num\n        String res7 = jedis.jsonSet(\"crashes\", new Path2(\"$\"), 0);\n        System.out.println(res7);   // >>> OK\n\n        Object res8 = jedis.jsonNumIncrBy(\"crashes\", new Path2(\"$\"), 1);\n        System.out.println(res8);   // >>> [1]\n\n        Object res9 = jedis.jsonNumIncrBy(\"crashes\", new Path2(\"$\"), 1.5);\n        System.out.println(res9);   // >>> [2.5]\n\n        Object res10 = jedis.jsonNumIncrBy(\"crashes\", new Path2(\"$\"), -0.75);\n        System.out.println(res10);   // >>> [1.75]\n        // STEP_END\n\n        // Tests for 'num' step.\n        // REMOVE_START\n        assertEquals(\"OK\", res7);\n        assertEquals(\"[1]\", res8.toString());\n        assertEquals(\"[2.5]\", res9.toString());\n        assertEquals(\"[1.75]\", res10.toString());\n        // REMOVE_END\n\n\n        // STEP_START arr\n        String res11 = jedis.jsonSet(\"newbike\", new Path2(\"$\"),\n            new JSONArray()\n                .put(\"Deimos\")\n                .put(new JSONObject().put(\"crashes\", 0))\n                .put((Object) null)\n        );\n        System.out.println(res11);  // >>> OK\n        \n        Object res12 = jedis.jsonGet(\"newbike\", new Path2(\"$\"));\n        System.out.println(res12);  // >>> [[\"Deimos\",{\"crashes\":0},null]]\n\n        Object res13 = jedis.jsonGet(\"newbike\", new Path2(\"$[1].crashes\"));\n        System.out.println(res13);  // >>> [0]\n\n        long res14 = jedis.jsonDel(\"newbike\", new Path2(\"$.[-1]\"));\n        System.out.println(res14);  // >>> 1\n\n        Object res15 = jedis.jsonGet(\"newbike\", new Path2(\"$\"));\n        System.out.println(res15);  // >>> [[\"Deimos\",{\"crashes\":0}]]\n        // STEP_END\n\n        // Tests for 'arr' step.\n        // REMOVE_START\n        assertEquals(\"OK\", res11);\n        assertEquals(\"[[\\\"Deimos\\\",{\\\"crashes\\\":0},null]]\", res12.toString());\n        assertEquals(\"[0]\", res13.toString());\n        assertEquals(1, res14);\n        assertEquals(\"[[\\\"Deimos\\\",{\\\"crashes\\\":0}]]\", res15.toString());\n        // REMOVE_END\n\n\n        // STEP_START arr2\n        String res16 = jedis.jsonSet(\"riders\", new Path2(\"$\"), new JSONArray());\n        System.out.println(res16);  // >>> OK\n\n        List<Long> res17 = jedis.jsonArrAppendWithEscape(\"riders\", new Path2(\"$\"), \"Norem\");\n        System.out.println(res17);  // >>> [1]\n\n        Object res18 = jedis.jsonGet(\"riders\", new Path2(\"$\"));\n        System.out.println(res18);  // >>> [[\"Norem\"]]\n\n        List<Long> res19 = jedis.jsonArrInsertWithEscape(\n            \"riders\", new Path2(\"$\"), 1, \"Prickett\", \"Royce\", \"Castilla\"\n        );\n        System.out.println(res19);  // >>> [4]\n\n        Object res20 = jedis.jsonGet(\"riders\", new Path2(\"$\"));\n        System.out.println(res20);\n        // >>> [[\"Norem\",\"Prickett\",\"Royce\",\"Castilla\"]]\n        \n        List<Long> res21 = jedis.jsonArrTrim(\"riders\", new Path2(\"$\"), 1, 1);\n        System.out.println(res21);  // >>> [1]\n\n        Object res22 = jedis.jsonGet(\"riders\", new Path2(\"$\"));\n        System.out.println(res22);  // >>> [[\"Prickett\"]]\n\n        Object res23 = jedis.jsonArrPop(\"riders\", new Path2(\"$\"));\n        System.out.println(res23);  // >>> [Prickett]\n\n        Object res24 = jedis.jsonArrPop(\"riders\", new Path2(\"$\"));\n        System.out.println(res24);  // >>> [null]\n        // STEP_END\n\n        // Tests for 'arr2' step.\n        // REMOVE_START\n        assertEquals(\"OK\", res16);\n        assertEquals(\"[1]\", res17.toString());\n        assertEquals(\"[[\\\"Norem\\\"]]\", res18.toString());\n        assertEquals(\"[4]\", res19.toString());\n        assertEquals(\"[[\\\"Norem\\\",\\\"Prickett\\\",\\\"Royce\\\",\\\"Castilla\\\"]]\", res20.toString());\n        assertEquals(\"[1]\", res21.toString());\n        assertEquals(\"[[\\\"Prickett\\\"]]\", res22.toString());\n        assertEquals(\"[Prickett]\", res23.toString());\n        assertEquals(\"[null]\", res24.toString());\n        // REMOVE_END\n\n\n        // STEP_START obj\n        String res25 = jedis.jsonSet(\"bike:1\", new Path2(\"$\"),\n            new JSONObject()\n                .put(\"model\", \"Deimos\")\n                .put(\"brand\", \"Ergonom\")\n                .put(\"price\", 4972)\n        );\n        System.out.println(res25);  // >>> OK\n\n        List<Long> res26 = jedis.jsonObjLen(\"bike:1\", new Path2(\"$\"));\n        System.out.println(res26);  // >>> [3]\n\n        List<List<String>> res27 = jedis.jsonObjKeys(\"bike:1\", new Path2(\"$\"));\n        System.out.println(res27);  // >>> [[price, model, brand]]\n        // STEP_END\n\n        // Tests for 'obj' step.\n        // REMOVE_START\n        assertEquals(\"OK\", res25);\n        assertEquals(\"[3]\", res26.toString());\n        assertEquals(\"[[price, model, brand]]\", res27.toString());\n        // REMOVE_END\n\n        // STEP_START set_bikes\n        String inventory_json = \"{\"\n        + \"    \\\"inventory\\\": {\"\n        + \"        \\\"mountain_bikes\\\": [\"\n        + \"            {\"\n        + \"                \\\"id\\\": \\\"bike:1\\\",\"\n        + \"                \\\"model\\\": \\\"Phoebe\\\",\"\n        + \"                \\\"description\\\": \\\"This is a mid-travel trail slayer that is a \"\n        + \"fantastic daily driver or one bike quiver. The Shimano Claris 8-speed groupset \"\n        + \"gives plenty of gear range to tackle hills and there\\u2019s room for mudguards \"\n        + \"and a rack too.  This is the bike for the rider who wants trail manners with \"\n        + \"low fuss ownership.\\\",\"\n        + \"                \\\"price\\\": 1920,\"\n        + \"                \\\"specs\\\": {\\\"material\\\": \\\"carbon\\\", \\\"weight\\\": 13.1},\"\n        + \"                \\\"colors\\\": [\\\"black\\\", \\\"silver\\\"]\"\n        + \"            },\"\n        + \"            {\"\n        + \"                \\\"id\\\": \\\"bike:2\\\",\"\n        + \"                \\\"model\\\": \\\"Quaoar\\\",\"\n        + \"                \\\"description\\\": \\\"Redesigned for the 2020 model year, this \"\n        + \"bike impressed our testers and is the best all-around trail bike we've ever \"\n        + \"tested. The Shimano gear system effectively does away with an external cassette, \"\n        + \"so is super low maintenance in terms of wear and tear. All in all it's an \"\n        + \"impressive package for the price, making it very competitive.\\\",\"\n        + \"                \\\"price\\\": 2072,\"\n        + \"                \\\"specs\\\": {\\\"material\\\": \\\"aluminium\\\", \\\"weight\\\": 7.9},\"\n        + \"                \\\"colors\\\": [\\\"black\\\", \\\"white\\\"]\"\n        + \"            },\"\n        + \"            {\"\n        + \"                \\\"id\\\": \\\"bike:3\\\",\"\n        + \"                \\\"model\\\": \\\"Weywot\\\",\"\n        + \"                \\\"description\\\": \\\"This bike gives kids aged six years and older \"\n        + \"a durable and uberlight mountain bike for their first experience on tracks and easy \"\n        + \"cruising through forests and fields. A set of powerful Shimano hydraulic disc brakes \"\n        + \"provide ample stopping ability. If you're after a budget option, this is one of the \"\n        + \"best bikes you could get.\\\",\"\n        + \"                \\\"price\\\": 3264,\"\n        + \"                \\\"specs\\\": {\\\"material\\\": \\\"alloy\\\", \\\"weight\\\": 13.8}\"\n        + \"            }\"\n        + \"        ],\"\n        + \"        \\\"commuter_bikes\\\": [\"\n        + \"            {\"\n        + \"                \\\"id\\\": \\\"bike:4\\\",\"\n        + \"                \\\"model\\\": \\\"Salacia\\\",\"\n        + \"                \\\"description\\\": \\\"This bike is a great option for anyone who just \"\n        + \"wants a bike to get about on With a slick-shifting Claris gears from Shimano\\u2019s, \"\n        + \"this is a bike which doesn\\u2019t break the bank and delivers craved performance.  \"\n        + \"It\\u2019s for the rider who wants both efficiency and capability.\\\",\"\n        + \"                \\\"price\\\": 1475,\"\n        + \"                \\\"specs\\\": {\\\"material\\\": \\\"aluminium\\\", \\\"weight\\\": 16.6},\"\n        + \"                \\\"colors\\\": [\\\"black\\\", \\\"silver\\\"]\"\n        + \"            },\"\n        + \"            {\"\n        + \"                \\\"id\\\": \\\"bike:5\\\",\"\n        + \"                \\\"model\\\": \\\"Mimas\\\",\"\n        + \"                \\\"description\\\": \\\"A real joy to ride, this bike got very high scores \"\n        + \"in last years Bike of the year report. The carefully crafted 50-34 tooth chainset \"\n        + \"and 11-32 tooth cassette give an easy-on-the-legs bottom gear for climbing, and the \"\n        + \"high-quality Vittoria Zaffiro tires give balance and grip.It includes a low-step \"\n        + \"frame , our memory foam seat, bump-resistant shocks and conveniently placed thumb \"\n        + \"throttle. Put it all together and you get a bike that helps redefine what can be \"\n        + \"done for this price.\\\",\"\n        + \"                \\\"price\\\": 3941,\"\n        + \"                \\\"specs\\\": {\\\"material\\\": \\\"alloy\\\", \\\"weight\\\": 11.6}\"\n        + \"            }\"\n        + \"        ]\"\n        + \"    }\"\n        + \"}\";\n\n        String res28 = jedis.jsonSet(\"bikes:inventory\", new Path2(\"$\"), inventory_json);\n        System.out.println(res28);  // >>> OK\n        // STEP_END\n\n        // Tests for 'set_bikes' step.\n        // REMOVE_START\n        assertEquals(\"OK\", res28);\n        // REMOVE_END\n\n\n        // STEP_START get_bikes\n        Object res29 = jedis.jsonGet(\"bikes:inventory\", new Path2(\"$.inventory.*\"));\n        System.out.println(res29);\n        // >>> [[{\"specs\":{\"material\":\"carbon\",\"weight\":13.1},\"price\":1920, ...\n        // STEP_END\n\n        // Tests for 'get_bikes' step.\n        // REMOVE_START\n        assertEquals(\n            \"[[{\\\"specs\\\":{\\\"material\\\":\\\"carbon\\\",\\\"weight\\\":13.1},\\\"price\\\":1920,\"\n            + \"\\\"description\\\":\\\"This is a mid-travel trail slayer that is a \"\n            + \"fantastic daily driver or one bike quiver. The Shimano Claris 8-speed \"\n            + \"groupset gives plenty of gear range to tackle hills and \"\n            + \"there\\\\u2019s room for mudguards and a rack too.  This is the bike \"\n            + \"for the rider who wants trail manners with low fuss \"\n            + \"ownership.\\\",\\\"model\\\":\\\"Phoebe\\\",\\\"id\\\":\\\"bike:1\\\",\\\"colors\\\":\"\n            + \"[\\\"black\\\",\\\"silver\\\"]},{\\\"specs\\\":{\\\"material\\\":\\\"aluminium\\\",\\\"weight\\\":7.9},\"\n            + \"\\\"price\\\":2072,\\\"description\\\":\\\"Redesigned for the 2020 model year, this \"\n            + \"bike impressed our testers and is the best all-around trail bike we've \"\n            + \"ever tested. The Shimano gear system effectively does away with an \"\n            + \"external cassette, so is super low maintenance in terms of wear and tear. \"\n            + \"All in all it's an impressive package for the price, making it very \"\n            + \"competitive.\\\",\\\"model\\\":\\\"Quaoar\\\",\\\"id\\\":\\\"bike:2\\\",\\\"colors\\\":\"\n            + \"[\\\"black\\\",\\\"white\\\"]},{\\\"specs\\\":{\\\"material\\\":\\\"alloy\\\",\\\"weight\\\":13.8},\"\n            + \"\\\"price\\\":3264,\\\"description\\\":\\\"This bike gives kids aged six years and \"\n            + \"older a durable and uberlight mountain bike for their first experience \"\n            + \"on tracks and easy cruising through forests and fields. A set of \"\n            + \"powerful Shimano hydraulic disc brakes provide ample stopping ability. \"\n            + \"If you're after a budget option, this is one of the best bikes you could \"\n            + \"get.\\\",\\\"model\\\":\\\"Weywot\\\",\\\"id\\\":\\\"bike:3\\\"}],[{\\\"specs\\\":\"\n            + \"{\\\"material\\\":\\\"aluminium\\\",\\\"weight\\\":16.6},\\\"price\\\":1475,\\\"description\\\":\"\n            + \"\\\"This bike is a great option for anyone who just wants a bike to get about \"\n            + \"on With a slick-shifting Claris gears from Shimano\\\\u2019s, this is a bike \"\n            + \"which doesn\\\\u2019t break the bank and delivers craved performance.  \"\n            + \"It\\\\u2019s for the rider who wants both efficiency and \"\n            + \"capability.\\\",\\\"model\\\":\\\"Salacia\\\",\\\"id\\\":\\\"bike:4\\\",\\\"colors\\\":\"\n            + \"[\\\"black\\\",\\\"silver\\\"]},{\\\"specs\\\":{\\\"material\\\":\\\"alloy\\\",\\\"weight\\\":11.6},\"\n            + \"\\\"price\\\":3941,\\\"description\\\":\\\"A real joy to ride, this bike got very \"\n            + \"high scores in last years Bike of the year report. The carefully crafted \"\n            + \"50-34 tooth chainset and 11-32 tooth cassette give an easy-on-the-legs \"\n            + \"bottom gear for climbing, and the high-quality Vittoria Zaffiro tires \"\n            + \"give balance and grip.It includes a low-step frame , our memory foam \"\n            + \"seat, bump-resistant shocks and conveniently placed thumb throttle. Put \"\n            + \"it all together and you get a bike that helps redefine what can be done \"\n            + \"for this price.\\\",\\\"model\\\":\\\"Mimas\\\",\\\"id\\\":\\\"bike:5\\\"}]]\",\n             res29.toString()\n        );\n        // REMOVE_END\n\n\n        // STEP_START get_mtnbikes\n        Object res30 = jedis.jsonGet(\n            \"bikes:inventory\", new Path2(\"$.inventory.mountain_bikes[*].model\")\n        );\n        System.out.println(res30);  // >>> [\"Phoebe\",\"Quaoar\",\"Weywot\"]\n\n        Object res31 = jedis.jsonGet(\n            \"bikes:inventory\", new Path2(\"$.inventory[\\\"mountain_bikes\\\"][*].model\")\n        );\n        System.out.println(res31);  // >>> [\"Phoebe\",\"Quaoar\",\"Weywot\"]\n\n        Object res32 = jedis.jsonGet(\n            \"bikes:inventory\", new Path2(\"$..mountain_bikes[*].model\")\n        );\n        System.out.println(res32);  // >>> [\"Phoebe\",\"Quaoar\",\"Weywot\"]\n        // STEP_END\n\n        // Tests for 'get_mtnbikes' step.\n        // REMOVE_START\n        assertEquals(\"[\\\"Phoebe\\\",\\\"Quaoar\\\",\\\"Weywot\\\"]\", res30.toString());\n        assertEquals(\"[\\\"Phoebe\\\",\\\"Quaoar\\\",\\\"Weywot\\\"]\", res31.toString());\n        assertEquals(\"[\\\"Phoebe\\\",\\\"Quaoar\\\",\\\"Weywot\\\"]\", res32.toString());\n        // REMOVE_END\n\n\n        // STEP_START get_models\n        Object res33 = jedis.jsonGet(\"bikes:inventory\", new Path2(\"$..model\"));\n        System.out.println(res33);\n        // >>> [\"Phoebe\",\"Quaoar\",\"Weywot\",\"Salacia\",\"Mimas\"]\n        // STEP_END\n\n        // Tests for 'get_models' step.\n        // REMOVE_START\n        assertEquals(\"[\\\"Phoebe\\\",\\\"Quaoar\\\",\\\"Weywot\\\",\\\"Salacia\\\",\\\"Mimas\\\"]\", res33.toString());\n        // REMOVE_END\n\n\n        // STEP_START get2mtnbikes\n        Object res34 = jedis.jsonGet(\n            \"bikes:inventory\", new Path2(\"$..mountain_bikes[0:2].model\")\n        );\n        System.out.println(res34);  // >>> [\"Phoebe\",\"Quaoar\"]\n        // STEP_END\n\n        // Tests for 'get2mtnbikes' step.\n        // REMOVE_START\n        assertEquals(\"[\\\"Phoebe\\\",\\\"Quaoar\\\"]\", res34.toString());\n        // REMOVE_END\n\n\n        // STEP_START filter1\n        Object res35 = jedis.jsonGet(\n            \"bikes:inventory\",\n            new Path2(\"$..mountain_bikes[?(@.price < 3000 && @.specs.weight < 10)]\")\n        );\n        System.out.println(res35);\n        // >>> [{\"specs\":{\"material\":\"aluminium\",\"weight\":7.9},\"price\":2072,...\n        // STEP_END\n\n        // Tests for 'filter1' step.\n        // REMOVE_START\n        assertEquals(\n            \"[{\\\"specs\\\":{\\\"material\\\":\\\"aluminium\\\",\\\"weight\\\":7.9},\\\"price\\\":2072,\"\n            + \"\\\"description\\\":\\\"Redesigned for the 2020 model year, this bike impressed \"\n            + \"our testers and is the best all-around trail bike we've ever tested. The \"\n            + \"Shimano gear system effectively does away with an external cassette, \"\n            + \"so is super low maintenance in terms of wear and tear. All in all it's an \"\n            + \"impressive package for the price, making it very competitive.\\\",\\\"model\\\":\"\n            + \"\\\"Quaoar\\\",\\\"id\\\":\\\"bike:2\\\",\\\"colors\\\":[\\\"black\\\",\\\"white\\\"]}]\",\n            res35.toString()\n        );\n        // REMOVE_END\n\n\n        // STEP_START filter2\n        Object res36 = jedis.jsonGet(\n            \"bikes:inventory\", new Path2(\"$..[?(@.specs.material == 'alloy')].model\")\n        );\n        System.out.println(res36);  // >>> [\"Weywot\",\"Mimas\"]\n        // STEP_END\n\n        // Tests for 'filter2' step.\n        // REMOVE_START\n        assertEquals(\"[\\\"Weywot\\\",\\\"Mimas\\\"]\", res36.toString());\n        // REMOVE_END\n\n\n        // STEP_START filter3\n        Object res37 = jedis.jsonGet(\n            \"bikes:inventory\", new Path2(\"$..[?(@.specs.material =~ '(?i)al')].model\")\n        );\n        System.out.println(res37);\n        // >>> [\"Quaoar\",\"Weywot\",\"Salacia\",\"Mimas\"]\n        // STEP_END\n\n        // Tests for 'filter3' step.\n        // REMOVE_START\n        assertEquals(\"[\\\"Quaoar\\\",\\\"Weywot\\\",\\\"Salacia\\\",\\\"Mimas\\\"]\", res37.toString());\n        // REMOVE_END\n\n\n        // STEP_START filter4\n        jedis.jsonSet(\n            \"bikes:inventory\", new Path2(\"$.inventory.mountain_bikes[0].regex_pat\"),\n            \"\\\"(?i)al\\\"\"\n        );\n        jedis.jsonSet(\n            \"bikes:inventory\", new Path2(\"$.inventory.mountain_bikes[1].regex_pat\"),\n            \"\\\"(?i)al\\\"\"\n        );\n        jedis.jsonSet(\n            \"bikes:inventory\", new Path2(\"$.inventory.mountain_bikes[2].regex_pat\"),\n            \"\\\"(?i)al\\\"\"\n        );\n        \n        Object res38 = jedis.jsonGet(\n            \"bikes:inventory\",\n            new Path2(\"$.inventory.mountain_bikes[?(@.specs.material =~ @.regex_pat)].model\")\n        );\n        System.out.println(res38);  // >>> [\"Quaoar\",\"Weywot\"]\n        // STEP_END\n\n        // Tests for 'filter4' step.\n        // REMOVE_START\n        assertEquals(\"[\\\"Quaoar\\\",\\\"Weywot\\\"]\", res38.toString());\n        // REMOVE_END\n\n\n        // STEP_START update_bikes\n        Object res39 = jedis.jsonGet(\"bikes:inventory\", new Path2(\"$..price\"));\n        System.out.println(res39);\n        // >>> [1920,2072,3264,1475,3941]\n\n        Object res40 = jedis.jsonNumIncrBy(\"bikes:inventory\", new Path2(\"$..price\"), -100);\n        System.out.println(res40);  // >>> [1820,1972,3164,1375,3841]\n\n        Object res41 = jedis.jsonNumIncrBy(\"bikes:inventory\", new Path2(\"$..price\"), 100);\n        System.out.println(res41);  // >>> [1920,2072,3264,1475,3941]\n        // STEP_END\n\n        // Tests for 'update_bikes' step.\n        // REMOVE_START\n        assertEquals(\"[1920,2072,3264,1475,3941]\", res39.toString());\n        assertEquals(\"[1820,1972,3164,1375,3841]\", res40.toString());\n        assertEquals(\"[1920,2072,3264,1475,3941]\", res41.toString());\n        // REMOVE_END\n\n\n        // STEP_START update_filters1\n        jedis.jsonSet(\"bikes:inventory\", new Path2(\"$.inventory.*[?(@.price<2000)].price\"), 1500);\n        Object res42 = jedis.jsonGet(\"bikes:inventory\", new Path2(\"$..price\"));\n        System.out.println(res42);  // >>> [1500,2072,3264,1500,3941]\n        // STEP_END\n\n        // Tests for 'update_filters1' step.\n        // REMOVE_START\n        assertEquals(\"[1500,2072,3264,1500,3941]\", res42.toString());\n        // REMOVE_END\n\n\n        // STEP_START update_filters2\n        List<Long> res43 = jedis.jsonArrAppendWithEscape(\n            \"bikes:inventory\", new Path2(\"$.inventory.*[?(@.price<2000)].colors\"),\n            \"\\\"pink\\\"\"\n        );\n        System.out.println(res43);  // >>> [3, 3]\n\n        Object res44 = jedis.jsonGet(\"bikes:inventory\", new Path2(\"$..[*].colors\"));\n        System.out.println(res44);\n        // >>> [[\"black\",\"silver\",\"\\\"pink\\\"\"],[\"black\",\"white\"],[\"black\",\"silver\",\"\\\"pink\\\"\"]]\n        // STEP_END\n\n        // Tests for 'update_filters2' step.\n        // REMOVE_START\n        assertEquals(\"[3, 3]\", res43.toString());\n        assertEquals(\n            \"[[\\\"black\\\",\\\"silver\\\",\\\"\\\\\\\"pink\\\\\\\"\\\"],[\\\"black\\\",\\\"white\\\"],\"\n            + \"[\\\"black\\\",\\\"silver\\\",\\\"\\\\\\\"pink\\\\\\\"\\\"]]\",\n            res44.toString());\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/ListExample.java",
    "content": "// EXAMPLE: list_tutorial\n// HIDE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.args.ListDirection;\nimport java.util.List;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ListExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        // HIDE_END\n        // REMOVE_START\n        jedis.del(\"bikes:repairs\");\n        jedis.del(\"bikes:finished\");\n        // REMOVE_END\n\n        // STEP_START queue\n        long res1 = jedis.lpush(\"bikes:repairs\", \"bike:1\");\n        System.out.println(res1);  // >>> 1\n\n        long res2 = jedis.lpush(\"bikes:repairs\", \"bike:2\");\n        System.out.println(res2);  // >>> 2\n\n        String res3 = jedis.rpop(\"bikes:repairs\");\n        System.out.println(res3);  // >>> bike:1\n\n        String res4 = jedis.rpop(\"bikes:repairs\");\n        System.out.println(res4); // >>> bike:2\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1, res1);\n        assertEquals(2, res2);\n        assertEquals(\"bike:1\", res3);\n        assertEquals(\"bike:2\", res4);\n        // REMOVE_END\n\n        // STEP_START stack\n        long res5 = jedis.lpush(\"bikes:repairs\", \"bike:1\");\n        System.out.println(res5);  // >>> 1\n\n        long res6 = jedis.lpush(\"bikes:repairs\", \"bike:2\");\n        System.out.println(res6);  // >>> 2\n\n        String res7 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res7);  // >>> bike:2\n\n        String res8 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res8);  // >>> bike:1\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1, res5);\n        assertEquals(2, res6);\n        assertEquals(\"bike:2\", res7);\n        assertEquals(\"bike:1\", res8);\n        // REMOVE_END\n\n        // STEP_START llen\n        long res9 = jedis.llen(\"bikes:repairs\");\n        System.out.println(res9);  // >>> 0\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(0, res9);\n        // REMOVE_END\n\n        // STEP_START lmove_lrange\n        long res10 = jedis.lpush(\"bikes:repairs\", \"bike:1\");\n        System.out.println(res10);  // >>> 1\n\n        long res11 = jedis.lpush(\"bikes:repairs\", \"bike:2\");\n        System.out.println(res11);  // >>> 2\n\n        String res12 = jedis.lmove(\"bikes:repairs\", \"bikes:finished\", ListDirection.LEFT, ListDirection.LEFT);\n        System.out.println(res12);  // >>> bike:2\n\n        List<String> res13 = jedis.lrange(\"bikes:repairs\", 0, -1);\n        System.out.println(res13);  // >>> [bike:1]\n\n        List<String> res14 = jedis.lrange(\"bikes:finished\", 0, -1);\n        System.out.println(res14);  // >>> [bike:2]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1, res10);\n        assertEquals(2, res11);\n        assertEquals(\"bike:2\", res12);\n        assertEquals(\"[bike:1]\", res13.toString());\n        assertEquals(\"[bike:2]\", res14.toString());\n        jedis.del(\"bikes:repairs\");\n        // REMOVE_END\n\n        // STEP_START lpush_rpush\n        long res15 = jedis.rpush(\"bikes:repairs\", \"bike:1\");\n        System.out.println(res15);  // >>> 1\n\n        long res16 = jedis.rpush(\"bikes:repairs\", \"bike:2\");\n        System.out.println(res16);  // >>> 2\n\n        long res17 = jedis.lpush(\"bikes:repairs\", \"bike:important_bike\");\n        System.out.println(res17);  // >>> 3\n\n        List<String> res18 = jedis.lrange(\"bikes:repairs\", 0, -1);\n        System.out.println(res18);  // >>> [bike:important_bike, bike:1, bike:2]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1, res15);\n        assertEquals(2, res16);\n        assertEquals(3, res17);\n        assertEquals(\"[bike:important_bike, bike:1, bike:2]\", res18.toString());\n        jedis.del(\"bikes:repairs\");\n        // REMOVE_END\n\n        // STEP_START variadic\n        long res19 = jedis.rpush(\"bikes:repairs\", \"bike:1\", \"bike:2\", \"bike:3\");\n        System.out.println(res19);  // >>> 3\n\n        long res20 = jedis.lpush(\"bikes:repairs\", \"bike:important_bike\", \"bike:very_important_bike\");\n        System.out.println(res20);  // >>> 5\n\n        List<String> res21 = jedis.lrange(\"bikes:repairs\", 0, -1);\n        System.out.println(res21);  // >>> [bike:very_important_bike, bike:important_bike, bike:1, bike:2, bike:3]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(3, res19);\n        assertEquals(5, res20);\n        assertEquals(\"[bike:very_important_bike, bike:important_bike, bike:1, bike:2, bike:3]\",res21.toString());\n        jedis.del(\"bikes:repairs\");\n        // REMOVE_END\n\n        // STEP_START lpop_rpop\n        long res22 = jedis.rpush(\"bikes:repairs\", \"bike:1\", \"bike:2\", \"bike:3\");\n        System.out.println(res22);  // >>> 3\n\n        String res23 = jedis.rpop(\"bikes:repairs\");\n        System.out.println(res23);  // >>> bike:3\n\n        String res24 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res24);  // >>> bike:1\n\n        String res25 = jedis.rpop(\"bikes:repairs\");\n        System.out.println(res25);  // >>> bike:2\n\n        String res26 = jedis.rpop(\"bikes:repairs\");\n        System.out.println(res26);  // >>> null\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(3, res22);\n        assertEquals(\"bike:3\", res23);\n        assertEquals(\"bike:1\", res24);\n        assertEquals(\"bike:2\", res25);\n        assertNull(res26);\n        // REMOVE_END\n\n        // STEP_START ltrim\n        long res27 = jedis.rpush(\"bikes:repairs\", \"bike:1\", \"bike:2\", \"bike:3\", \"bike:4\", \"bike:5\");\n        System.out.println(res27);  // >>> 5\n\n        String res28 = jedis.ltrim(\"bikes:repairs\", 0, 2);\n        System.out.println(res28);  // >>> OK\n\n        List<String> res29 = jedis.lrange(\"bikes:repairs\", 0, -1);\n        System.out.println(res29);  // >>> [bike:1, bike:2, bike:3]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(5, res27);\n        assertEquals(\"OK\", res28);\n        assertEquals(\"[bike:1, bike:2, bike:3]\", res29.toString());\n        jedis.del(\"bikes:repairs\");\n        // REMOVE_END\n\n        // STEP_START ltrim_end_of_list\n        res27 = jedis.rpush(\"bikes:repairs\", \"bike:1\", \"bike:2\", \"bike:3\", \"bike:4\", \"bike:5\");\n        System.out.println(res27);  // >>> 5\n\n        res28 = jedis.ltrim(\"bikes:repairs\", -3, -1);\n        System.out.println(res2);  // >>> OK\n\n        res29 = jedis.lrange(\"bikes:repairs\", 0, -1);\n        System.out.println(res29);  // >>> [bike:3, bike:4, bike:5]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(5, res27);\n        assertEquals(\"OK\", res28);\n        assertEquals(\"[bike:3, bike:4, bike:5]\", res29.toString());\n        jedis.del(\"bikes:repairs\");\n        // REMOVE_END\n\n        // STEP_START brpop\n        long res31 = jedis.rpush(\"bikes:repairs\", \"bike:1\", \"bike:2\");\n        System.out.println(res31);  // >>> 2\n\n        List<String> res32 = jedis.brpop(1, \"bikes:repairs\");\n        System.out.println(res32);  // >>> (bikes:repairs, bike:2)\n\n        List<String>  res33 = jedis.brpop(1,\"bikes:repairs\");\n        System.out.println(res33);  // >>> (bikes:repairs, bike:1)\n\n        List<String>  res34 = jedis.brpop(1,\"bikes:repairs\");\n        System.out.println(res34);  // >>> null\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(2, res31);\n        assertEquals(\"[bikes:repairs, bike:2]\", res32.toString());\n        assertEquals( \"[bikes:repairs, bike:1]\", res33.toString());\n        assertNull(res34);\n        jedis.del(\"bikes:repairs\");\n        jedis.del(\"new_bikes\");\n        // REMOVE_END\n\n        // STEP_START rule_1\n        long res35 = jedis.del(\"new_bikes\");\n        System.out.println(res35);  // >>> 0\n\n        long res36 = jedis.lpush(\"new_bikes\", \"bike:1\", \"bike:2\", \"bike:3\");\n        System.out.println(res36);  // >>> 3\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(0, res35);\n        assertEquals(3, res36);\n        jedis.del(\"new_bikes\");\n        // REMOVE_END\n\n        // STEP_START rule_1.1\n        String res37 = jedis.set(\"new_bikes\", \"bike:1\");\n        System.out.println(res37);  // >>> OK\n\n        String res38 = jedis.type(\"new_bikes\");\n        System.out.println(res38);  // >>> string\n\n        try {\n            long res39  = jedis.lpush(\"new_bikes\", \"bike:2\", \"bike:3\");\n        } catch (Exception e) {\n            e.printStackTrace();\n            // >>> redis.clients.jedis.exceptions.JedisDataException:\n            // >>> WRONGTYPE Operation against a key holding the wrong kind of value\n        }\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(\"OK\",res37);\n        assertEquals(\"string\",res38);\n        jedis.del(\"new_bikes\");\n        // REMOVE_END\n\n        // STEP_START rule_2\n        jedis.lpush(\"bikes:repairs\", \"bike:1\", \"bike:2\", \"bike:3\");\n        System.out.println(res36);  // >>> 3\n\n        boolean res40 = jedis.exists(\"bikes:repairs\");\n        System.out.println(res40);  // >>> true\n\n        String res41 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res41);  // >>> bike:3\n\n        String res42 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res42);  // >>> bike:2\n\n        String res43 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res43);  // >>> bike:1\n\n        boolean res44 = jedis.exists(\"bikes:repairs\");\n        System.out.println(res44);  // >>> false\n        // STEP_END\n\n        // REMOVE_START\n        assertTrue(res40);\n        assertEquals(res41, \"bike:3\");\n        assertEquals(res42, \"bike:2\");\n        assertEquals(res43, \"bike:1\");\n        assertFalse(res44);\n        // REMOVE_END\n\n        // STEP_START rule_3\n        long res45 = jedis.del(\"bikes:repairs\");\n        System.out.println(res45);  // >>> 0\n\n        long res46 = jedis.llen(\"bikes:repairs\");\n        System.out.println(res46);  // >>> 0\n\n        String res47 = jedis.lpop(\"bikes:repairs\");\n        System.out.println(res47);  // >>> null\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(0, res45);\n        assertEquals(0, res46);\n        assertNull(res47);\n        // REMOVE_END\n\n        // STEP_START ltrim.1\n        long res48 = jedis.lpush(\"bikes:repairs\", \"bike:1\", \"bike:2\", \"bike:3\", \"bike:4\", \"bike:5\");\n        System.out.println(res48);  // >>> 5\n\n        String res49 = jedis.ltrim(\"bikes:repairs\", 0, 2);\n        System.out.println(res49);  // >>> OK\n\n        List<String> res50 = jedis.lrange(\"bikes:repairs\", 0, -1);\n        System.out.println(res50);  // >>> [bike:5, bike:4, bike:3]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(5, res48);\n        assertEquals(\"OK\", res49);\n        assertEquals(\"[bike:5, bike:4, bike:3]\", res50.toString());\n        jedis.del(\"bikes:repairs\");\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/PipeTransExample.java",
    "content": "// EXAMPLE: pipe_trans_tutorial\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\nimport java.util.List;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.AbstractPipeline;\nimport redis.clients.jedis.AbstractTransaction;\nimport redis.clients.jedis.Response;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class PipeTransExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        // REMOVE_START\n        for (int i = 0; i < 5; i++) {\n            jedis.del(String.format(\"seat:%d\", i));\n        }\n\n        jedis.del(\"counter:1\", \"counter:2\", \"counter:3\", \"shellpath\");\n        // REMOVE_END\n\n        // STEP_START basic_pipe\n        // Make sure you close the pipeline after use to release resources\n        // and return the connection to the pool.\n        try (AbstractPipeline pipe = jedis.pipelined()) {\n\n            for (int i = 0; i < 5; i++) {\n                pipe.set(String.format(\"seat:%d\", i), String.format(\"#%d\", i));\n            }\n\n            pipe.sync();\n        }\n\n        try (AbstractPipeline pipe = jedis.pipelined()) {\n\n            Response<String> resp0 = pipe.get(\"seat:0\");\n            Response<String> resp3 = pipe.get(\"seat:3\");\n            Response<String> resp4 = pipe.get(\"seat:4\");\n\n            pipe.sync();\n\n            // Responses are available after the pipeline has executed.\n            System.out.println(resp0.get()); // >>> #0\n            System.out.println(resp3.get()); // >>> #3\n            System.out.println(resp4.get()); // >>> #4\n\n\n            // REMOVE_START\n            assertEquals(\"#0\", resp0.get());\n            assertEquals(\"#3\", resp3.get());\n            assertEquals(\"#4\", resp4.get());\n            // REMOVE_END\n        }\n        // STEP_END\n\n        // STEP_START basic_trans\n        try ( AbstractTransaction trans = jedis.multi()) {\n\n           trans.incrBy(\"counter:1\", 1);\n           trans.incrBy(\"counter:2\", 2);\n           trans.incrBy(\"counter:3\", 3);\n\n           trans.exec();\n        }\n        System.out.println(jedis.get(\"counter:1\")); // >>> 1\n        System.out.println(jedis.get(\"counter:2\")); // >>> 2\n        System.out.println(jedis.get(\"counter:3\")); // >>> 3\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"1\", jedis.get(\"counter:1\"));\n        assertEquals(\"2\", jedis.get(\"counter:2\"));\n        assertEquals(\"3\", jedis.get(\"counter:3\"));\n        // REMOVE_END\n\n        // STEP_START trans_watch\n        // Set initial value of `shellpath`.\n        jedis.set(\"shellpath\", \"/usr/syscmds/\");\n\n        // Start the transaction and watch the key we are about to update.\n        try (AbstractTransaction trans = jedis.transaction(false)) { // create a Transaction object without sending MULTI command\n            trans.watch(\"shellpath\"); // send WATCH command(s)\n            trans.multi(); // send MULTI command\n\n            String currentPath = jedis.get(\"shellpath\");\n            String newPath = currentPath + \":/usr/mycmds/\";\n\n            // Commands added to the `trans` object\n            // will be buffered until `trans.exec()` is called.\n            Response<String> setResult = trans.set(\"shellpath\", newPath);\n            List<Object> transResults = trans.exec();\n\n            // The `exec()` call returns null if the transaction failed.\n            if (transResults != null) {\n                // Responses are available if the transaction succeeded.\n                System.out.println(setResult.get()); // >>> OK\n\n                // You can also get the results from the list returned by\n                // `trans.exec()`.\n                for (Object item: transResults) {\n                    System.out.println(item);\n                }\n                // >>> OK\n\n                System.out.println(jedis.get(\"shellpath\"));\n                // >>> /usr/syscmds/:/usr/mycmds/\n            }\n            // REMOVE_START\n            assertEquals(\"/usr/syscmds/:/usr/mycmds/\", jedis.get(\"shellpath\"));\n            assertEquals(\"OK\", setResult.get());\n            assertEquals(1, transResults.size());\n            assertEquals(\"OK\", transResults.get(0).toString());\n            // REMOVE_END\n        }\n        // STEP_END\n\n// HIDE_START\n        jedis.close();\n    }   \n}\n// HIDE_END"
  },
  {
    "path": "src/test/java/io/redis/examples/QueryAggExample.java",
    "content": "// EXAMPLE: query_agg\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport java.util.List;\nimport java.util.ArrayList;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.FTCreateParams;\nimport redis.clients.jedis.search.IndexDataType;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.search.aggr.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n// HIDE_END\n\n// HIDE_START\npublic class QueryAggExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        try { jedis.ftDropIndex(\"idx:bicycle\"); } catch (JedisDataException j) {}\n        //REMOVE_END\n// HIDE_END\n\n        SchemaField[] schema = {\n            TextField.of(\"$.brand\").as(\"brand\"),\n            TextField.of(\"$.model\").as(\"model\"),\n            TextField.of(\"$.description\").as(\"description\"),\n            NumericField.of(\"$.price\").as(\"price\"),\n            TagField.of(\"$.condition\").as(\"condition\")\n        };\n\n        jedis.ftCreate(\"idx:bicycle\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"bicycle:\"),\n            schema\n        );\n\n        String[] bicycleJsons = new String[] {\n            \"  {\"\n            + \"\t  \\\"pickup_zone\\\": \\\"POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, \"\n            + \"-74.0610 40.6678, -74.0610 40.7578))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-74.0060,40.7128\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Velorim\\\",\"\n            + \"\t  \\\"model\\\": \\\"Jigger\\\",\"\n            + \"\t  \\\"price\\\": 270,\"\n            + \"\t  \\\"description\\\": \\\"Small and powerful, the Jigger is the best ride for the smallest of tikes! \"\n            + \"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger \"\n            + \"is the vehicle of choice for the rare tenacious little rider raring to go.\\\",\"\n            + \"\t  \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, \"\n            + \"-118.2887 33.9872, -118.2887 34.0972))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-118.2437,34.0522\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Bicyk\\\",\"\n            + \"\t  \\\"model\\\": \\\"Hillcraft\\\",\"\n            + \"\t  \\\"price\\\": 1200,\"\n            + \"\t  \\\"description\\\": \\\"Kids want to ride with as little weight as possible. Especially \"\n            + \"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming \"\n            + \"off a 24'' bike. The Hillcraft 26 is just the solution they need!\\\",\"\n            + \"\t  \\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, \"\n            + \"-87.6848 41.8231, -87.6848 41.9331))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-87.6298,41.8781\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Nord\\\",\"\n            + \"  \t\\\"model\\\": \\\"Chook air 5\\\",\"\n            + \"  \t\\\"price\\\": 815,\"\n            + \"  \t\\\"description\\\": \\\"The Chook Air 5  gives kids aged six years and older a durable \"\n            + \"and uberlight mountain bike for their first experience on tracks and easy cruising through \"\n            + \"forests and fields. The lower  top tube makes it easy to mount and dismount in any \"\n            + \"situation, giving your kids greater safety on the trails.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, \"\n            + \"-80.2433 25.6967, -80.2433 25.8067))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-80.1918,25.7617\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Eva\\\",\"\n            + \"  \t\\\"model\\\": \\\"Eva 291\\\",\"\n            + \"  \t\\\"price\\\": 3400,\"\n            + \"  \t\\\"description\\\": \\\"The sister company to Nord, Eva launched in 2005 as the first \"\n            + \"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes \"\n            + \"are optimized for the feminine physique using analytics from a body metrics database. \"\n            + \"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This \"\n            + \"full-suspension, cross-country ride has been designed for velocity. The 291 has \"\n            + \"100mm of front and rear travel, a superlight aluminum frame and fast-rolling \"\n            + \"29-inch wheels. Yippee!\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, \"\n            + \"-122.4644 37.7099, -122.4644 37.8199))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-122.4194,37.7749\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Noka Bikes\\\",\"\n            + \"  \t\\\"model\\\": \\\"Kahuna\\\",\"\n            + \"  \t\\\"price\\\": 3200,\"\n            + \"  \t\\\"description\\\": \\\"Whether you want to try your hand at XC racing or are looking \"\n            + \"for a lively trail bike that's just as inspiring on the climbs as it is over rougher \"\n            + \"ground, the Wilder is one heck of a bike built specifically for short women. Both the \"\n            + \"frames and components have been tweaked to include a women’s saddle, different bars \"\n            + \"and unique colourway.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, \"\n            + \"-0.1778 51.4024, -0.1778 51.5524))\\\",\"\n            + \"    \\\"store_location\\\": \\\"-0.1278,51.5074\\\",\"\n            + \"    \\\"brand\\\": \\\"Breakout\\\",\"\n            + \"    \\\"model\\\": \\\"XBN 2.1 Alloy\\\",\"\n            + \"    \\\"price\\\": 810,\"\n            + \"    \\\"description\\\": \\\"The XBN 2.1 Alloy is our entry-level road bike – but that’s \"\n            + \"not to say that it’s a basic machine. With an internal weld aluminium frame, a full \"\n            + \"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which \"\n            + \"doesn’t break the bank and delivers craved performance.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, \"\n            + \"2.1767 48.5516, 2.1767 48.9016))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.3522,48.8566\\\",\"\n            + \"    \\\"brand\\\": \\\"ScramBikes\\\",\"\n            + \"    \\\"model\\\": \\\"WattBike\\\",\"\n            + \"    \\\"price\\\": 2300,\"\n            + \"    \\\"description\\\": \\\"The WattBike is the best e-bike for people who still \"\n            + \"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH \"\n            + \"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one \"\n            + \"charge. It’s great for tackling hilly terrain or if you just fancy a more \"\n            + \"leisurely ride. With three working modes, you can choose between E-bike, \"\n            + \"assisted bicycle, and normal bike modes.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, \"\n            + \"13.3260 52.2700, 13.3260 52.5700))\\\",\"\n            + \"    \\\"store_location\\\": \\\"13.4050,52.5200\\\",\"\n            + \"    \\\"brand\\\": \\\"Peaknetic\\\",\"\n            + \"    \\\"model\\\": \\\"Secto\\\",\"\n            + \"    \\\"price\\\": 430,\"\n            + \"    \\\"description\\\": \\\"If you struggle with stiff fingers or a kinked neck or \"\n            + \"back after a few minutes on the road, this lightweight, aluminum bike alleviates \"\n            + \"those issues and allows you to enjoy the ride. From the ergonomic grips to the \"\n            + \"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. \"\n            + \"The rear-inclined seat tube facilitates stability by allowing you to put a foot \"\n            + \"on the ground to balance at a stop, and the low step-over frame makes it \"\n            + \"accessible for all ability and mobility levels. The saddle is very soft, with \"\n            + \"a wide back to support your hip joints and a cutout in the center to redistribute \"\n            + \"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires \"\n            + \"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts \"\n            + \"facilitate setting up the Roll Low-Entry as your preferred commuter, and the \"\n            + \"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, \"\n            + \"1.9450 41.1987, 1.9450 41.4301))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.1734, 41.3851\\\",\"\n            + \"    \\\"brand\\\": \\\"nHill\\\",\"\n            + \"    \\\"model\\\": \\\"Summit\\\",\"\n            + \"    \\\"price\\\": 1200,\"\n            + \"    \\\"description\\\": \\\"This budget mountain bike from nHill performs well both \"\n            + \"on bike paths and on the trail. The fork with 100mm of travel absorbs rough \"\n            + \"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. \"\n            + \"The Shimano Tourney drivetrain offered enough gears for finding a comfortable \"\n            + \"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. \"\n            + \"Whether you want an affordable bike that you can take to work, but also take \"\n            + \"trail in mountains on the weekends or you’re just after a stable, comfortable \"\n            + \"ride for the bike path, the Summit gives a good value for money.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((12.4464 42.1028, 12.5464 42.1028, \"\n            + \"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\\\",\"\n            + \"    \\\"store_location\\\": \\\"12.4964,41.9028\\\",\"\n            + \"    \\\"model\\\": \\\"ThrillCycle\\\",\"\n            + \"    \\\"brand\\\": \\\"BikeShind\\\",\"\n            + \"    \\\"price\\\": 815,\"\n            + \"    \\\"description\\\": \\\"An artsy,  retro-inspired bicycle that’s as \"\n            + \"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. \"\n            + \"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t \"\n            + \"suggest taking it to the mountains. Fenders protect you from mud, and a rear \"\n            + \"basket lets you transport groceries, flowers and books. The ThrillCycle comes \"\n            + \"with a limited lifetime warranty, so this little guy will last you long \"\n            + \"past graduation.\\\",\"\n            + \"    \\\"condition\\\": \\\"refurbished\\\"\"\n            + \"  }\"\n        };\n\n        for (int i = 0; i < bicycleJsons.length; i++) {\n            jedis.jsonSet(\"bicycle:\" + i, new Path2(\"$\"), bicycleJsons[i]);\n        }\n\n        // STEP_START agg1\n        AggregationResult res1 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"@condition:{new}\")\n                    .load(\"__key\", \"price\")\n                    .apply(\"@price - (@price * 0.1)\", \"discounted\")\n        );\n        \n        List<Row> rows1 = res1.getRows();\n        System.out.println(rows1.size());   // >>> 5\n\n        for (int i = 0; i < rows1.size(); i++) {\n            System.out.println(rows1.get(i));\n        }\n        // >>> {__key=bicycle:0, discounted=243, price=270}\n        // >>> {__key=bicycle:5, discounted=729, price=810}\n        // >>> {__key=bicycle:6, discounted=2070, price=2300}\n        // >>> {__key=bicycle:7, discounted=387, price=430}\n        // >>> {__key=bicycle:8, discounted=1080, price=1200}\n        // STEP_END\n\n        // Tests for 'agg1' step.\n        // REMOVE_START\n        assertEquals(5, rows1.size());\n        assertArrayEquals(\n            new String[]{\"bicycle:0\", \"bicycle:5\", \"bicycle:6\", \"bicycle:7\", \"bicycle:8\"},\n            rows1.stream().map(e->e.get(\"__key\")).sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START agg2\n        AggregationResult res2 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"*\")\n                    .load(\"price\")\n                    .apply(\"@price<1000\", \"price_category\")\n                    .groupBy(\"@condition\",\n                        Reducers.sum(\"@price_category\").as(\"num_affordable\"))\n        );\n\n        List<Row> rows2 = res2.getRows();\n        System.out.println(rows2.size());   // >>> 3\n\n        for (int i = 0; i < rows2.size(); i++) {\n            System.out.println(rows2.get(i));\n        }\n        // >>> {condition=refurbished, num_affordable=1}\n        // >>> {condition=used, num_affordable=1}\n        // >>> {condition=new, num_affordable=3}\n        // STEP_END\n\n        // Tests for 'agg2' step.\n        // REMOVE_START\n        assertEquals(3, rows2.size());\n        assertArrayEquals(\n            new String[] { \"new, 3\", \"refurbished, 1\", \"used, 1\" },\n            rows2.stream().map(e->e.get(\"condition\") + \", \" + e.get(\"num_affordable\"))\n                    .sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START agg3\n        AggregationResult res3 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"*\")\n                    .apply(\"'bicycle'\", \"type\")\n                    .groupBy(\"@type\", Reducers.count().as(\"num_total\"))\n        );\n\n        List<Row> rows3 = res3.getRows();\n        System.out.println(rows3.size());   // >>> 1\n\n        for (int i = 0; i < rows3.size(); i++) {\n            System.out.println(rows3.get(i));\n        }\n        // >>> {type=bicycle, num_total=10}\n        // STEP_END\n\n        // Tests for 'agg3' step.\n        // REMOVE_START\n        assertEquals(1, rows3.size());\n        assertEquals(\"{type=bicycle, num_total=10}\", rows3.get(0).toString());\n        // REMOVE_END\n\n\n        // STEP_START agg4\n        AggregationResult res4 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"*\")\n                    .load(\"__key\")\n                    .groupBy(\"@condition\",\n                        Reducers.to_list(\"__key\").as(\"bicycles\"))\n        );\n\n        List<Row> rows4 = res4.getRows();\n        System.out.println(rows4.size());   // >>> 3\n\n        for (int i = 0; i < rows4.size(); i++) {\n            System.out.println(rows4.get(i));\n        }\n        // >>> {condition=refurbished, bicycles=[bicycle:9]}\n        // >>> {condition=used, bicycles=[bicycle:3, bicycle:4, bicycle:1, bicycle:2]}\n        // >>> {condition=new, bicycles=[bicycle:7, bicycle:0, bicycle:5, bicycle:6, bicycle:8]}\n        // STEP_END\n\n        // Tests for 'agg4' step.\n        // REMOVE_START\n        assertEquals(3, rows4.size());\n\n        Row test4Row = rows4.get(0);\n        assertEquals(\"refurbished\", test4Row.getString(\"condition\"));\n\n        ArrayList<String> test4Bikes = (ArrayList<String>) test4Row.get(\"bicycles\");\n        assertEquals(1, test4Bikes.size());\n        assertTrue(test4Bikes.contains(\"bicycle:9\"));\n\n        test4Row = rows4.get(1);\n        assertEquals(\"used\", test4Row.getString(\"condition\"));\n        \n        test4Bikes = (ArrayList<String>) test4Row.get(\"bicycles\");\n        assertEquals(4, test4Bikes.size());\n        assertTrue(test4Bikes.contains(\"bicycle:1\"));\n        assertTrue(test4Bikes.contains(\"bicycle:2\"));\n        assertTrue(test4Bikes.contains(\"bicycle:3\"));\n        assertTrue(test4Bikes.contains(\"bicycle:4\"));\n        \n        test4Row = rows4.get(2);\n        assertEquals(\"new\", test4Row.getString(\"condition\"));\n\n        test4Bikes = (ArrayList<String>) test4Row.get(\"bicycles\");\n        assertEquals(5, test4Bikes.size());\n        assertTrue(test4Bikes.contains(\"bicycle:0\"));\n        assertTrue(test4Bikes.contains(\"bicycle:5\"));\n        assertTrue(test4Bikes.contains(\"bicycle:6\"));\n        assertTrue(test4Bikes.contains(\"bicycle:7\"));\n        assertTrue(test4Bikes.contains(\"bicycle:8\"));\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/QueryEmExample.java",
    "content": "// EXAMPLE: query_em\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport java.util.List;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class QueryEmExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        try {jedis.ftDropIndex(\"idx:bicycle\");} catch (JedisDataException j){}\n        try {jedis.ftDropIndex(\"idx:email\");} catch (JedisDataException j){}\n        //REMOVE_END\n\n        SchemaField[] schema = {\n            TextField.of(\"$.brand\").as(\"brand\"),\n            TextField.of(\"$.model\").as(\"model\"),\n            TextField.of(\"$.description\").as(\"description\"),\n            NumericField.of(\"$.price\").as(\"price\"),\n            TagField.of(\"$.condition\").as(\"condition\")\n        };\n\n        jedis.ftCreate(\"idx:bicycle\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"bicycle:\"),\n            schema\n        );\n\n        String[] bicycleJsons = new String[] {\n            \"  {\"\n            + \"\t  \\\"pickup_zone\\\": \\\"POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, \"\n            + \"-74.0610 40.6678, -74.0610 40.7578))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-74.0060,40.7128\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Velorim\\\",\"\n            + \"\t  \\\"model\\\": \\\"Jigger\\\",\"\n            + \"\t  \\\"price\\\": 270,\"\n            + \"\t  \\\"description\\\": \\\"Small and powerful, the Jigger is the best ride for the smallest of tikes! \"\n            + \"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger \"\n            + \"is the vehicle of choice for the rare tenacious little rider raring to go.\\\",\"\n            + \"\t  \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, \"\n            + \"-118.2887 33.9872, -118.2887 34.0972))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-118.2437,34.0522\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Bicyk\\\",\"\n            + \"\t  \\\"model\\\": \\\"Hillcraft\\\",\"\n            + \"\t  \\\"price\\\": 1200,\"\n            + \"\t  \\\"description\\\": \\\"Kids want to ride with as little weight as possible. Especially \"\n            + \"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming \"\n            + \"off a 24'' bike. The Hillcraft 26 is just the solution they need!\\\",\"\n            + \"\t  \\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, \"\n            + \"-87.6848 41.8231, -87.6848 41.9331))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-87.6298,41.8781\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Nord\\\",\"\n            + \"  \t\\\"model\\\": \\\"Chook air 5\\\",\"\n            + \"  \t\\\"price\\\": 815,\"\n            + \"  \t\\\"description\\\": \\\"The Chook Air 5  gives kids aged six years and older a durable \"\n            + \"and uberlight mountain bike for their first experience on tracks and easy cruising through \"\n            + \"forests and fields. The lower  top tube makes it easy to mount and dismount in any \"\n            + \"situation, giving your kids greater safety on the trails.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, \"\n            + \"-80.2433 25.6967, -80.2433 25.8067))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-80.1918,25.7617\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Eva\\\",\"\n            + \"  \t\\\"model\\\": \\\"Eva 291\\\",\"\n            + \"  \t\\\"price\\\": 3400,\"\n            + \"  \t\\\"description\\\": \\\"The sister company to Nord, Eva launched in 2005 as the first \"\n            + \"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes \"\n            + \"are optimized for the feminine physique using analytics from a body metrics database. \"\n            + \"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This \"\n            + \"full-suspension, cross-country ride has been designed for velocity. The 291 has \"\n            + \"100mm of front and rear travel, a superlight aluminum frame and fast-rolling \"\n            + \"29-inch wheels. Yippee!\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, \"\n            + \"-122.4644 37.7099, -122.4644 37.8199))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-122.4194,37.7749\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Noka Bikes\\\",\"\n            + \"  \t\\\"model\\\": \\\"Kahuna\\\",\"\n            + \"  \t\\\"price\\\": 3200,\"\n            + \"  \t\\\"description\\\": \\\"Whether you want to try your hand at XC racing or are looking \"\n            + \"for a lively trail bike that's just as inspiring on the climbs as it is over rougher \"\n            + \"ground, the Wilder is one heck of a bike built specifically for short women. Both the \"\n            + \"frames and components have been tweaked to include a women’s saddle, different bars \"\n            + \"and unique colourway.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, \"\n            + \"-0.1778 51.4024, -0.1778 51.5524))\\\",\"\n            + \"    \\\"store_location\\\": \\\"-0.1278,51.5074\\\",\"\n            + \"    \\\"brand\\\": \\\"Breakout\\\",\"\n            + \"    \\\"model\\\": \\\"XBN 2.1 Alloy\\\",\"\n            + \"    \\\"price\\\": 810,\"\n            + \"    \\\"description\\\": \\\"The XBN 2.1 Alloy is our entry-level road bike – but that’s \"\n            + \"not to say that it’s a basic machine. With an internal weld aluminium frame, a full \"\n            + \"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which \"\n            + \"doesn’t break the bank and delivers craved performance.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, \"\n            + \"2.1767 48.5516, 2.1767 48.9016))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.3522,48.8566\\\",\"\n            + \"    \\\"brand\\\": \\\"ScramBikes\\\",\"\n            + \"    \\\"model\\\": \\\"WattBike\\\",\"\n            + \"    \\\"price\\\": 2300,\"\n            + \"    \\\"description\\\": \\\"The WattBike is the best e-bike for people who still \"\n            + \"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH \"\n            + \"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one \"\n            + \"charge. It’s great for tackling hilly terrain or if you just fancy a more \"\n            + \"leisurely ride. With three working modes, you can choose between E-bike, \"\n            + \"assisted bicycle, and normal bike modes.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, \"\n            + \"13.3260 52.2700, 13.3260 52.5700))\\\",\"\n            + \"    \\\"store_location\\\": \\\"13.4050,52.5200\\\",\"\n            + \"    \\\"brand\\\": \\\"Peaknetic\\\",\"\n            + \"    \\\"model\\\": \\\"Secto\\\",\"\n            + \"    \\\"price\\\": 430,\"\n            + \"    \\\"description\\\": \\\"If you struggle with stiff fingers or a kinked neck or \"\n            + \"back after a few minutes on the road, this lightweight, aluminum bike alleviates \"\n            + \"those issues and allows you to enjoy the ride. From the ergonomic grips to the \"\n            + \"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. \"\n            + \"The rear-inclined seat tube facilitates stability by allowing you to put a foot \"\n            + \"on the ground to balance at a stop, and the low step-over frame makes it \"\n            + \"accessible for all ability and mobility levels. The saddle is very soft, with \"\n            + \"a wide back to support your hip joints and a cutout in the center to redistribute \"\n            + \"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires \"\n            + \"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts \"\n            + \"facilitate setting up the Roll Low-Entry as your preferred commuter, and the \"\n            + \"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, \"\n            + \"1.9450 41.1987, 1.9450 41.4301))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.1734, 41.3851\\\",\"\n            + \"    \\\"brand\\\": \\\"nHill\\\",\"\n            + \"    \\\"model\\\": \\\"Summit\\\",\"\n            + \"    \\\"price\\\": 1200,\"\n            + \"    \\\"description\\\": \\\"This budget mountain bike from nHill performs well both \"\n            + \"on bike paths and on the trail. The fork with 100mm of travel absorbs rough \"\n            + \"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. \"\n            + \"The Shimano Tourney drivetrain offered enough gears for finding a comfortable \"\n            + \"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. \"\n            + \"Whether you want an affordable bike that you can take to work, but also take \"\n            + \"trail in mountains on the weekends or you’re just after a stable, comfortable \"\n            + \"ride for the bike path, the Summit gives a good value for money.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((12.4464 42.1028, 12.5464 42.1028, \"\n            + \"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\\\",\"\n            + \"    \\\"store_location\\\": \\\"12.4964,41.9028\\\",\"\n            + \"    \\\"model\\\": \\\"ThrillCycle\\\",\"\n            + \"    \\\"brand\\\": \\\"BikeShind\\\",\"\n            + \"    \\\"price\\\": 815,\"\n            + \"    \\\"description\\\": \\\"An artsy,  retro-inspired bicycle that’s as \"\n            + \"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. \"\n            + \"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t \"\n            + \"suggest taking it to the mountains. Fenders protect you from mud, and a rear \"\n            + \"basket lets you transport groceries, flowers and books. The ThrillCycle comes \"\n            + \"with a limited lifetime warranty, so this little guy will last you long \"\n            + \"past graduation.\\\",\"\n            + \"    \\\"condition\\\": \\\"refurbished\\\"\"\n            + \"  }\"\n        };\n\n        for (int i = 0; i < bicycleJsons.length; i++) {\n            jedis.jsonSet(\"bicycle:\" + i, Path2.ROOT_PATH, bicycleJsons[i]);\n        }\n// HIDE_END\n\n\n        // STEP_START em1\n        SearchResult res1 = jedis.ftSearch(\"idx:bicycle\", \"@price:[270 270]\");\n        System.out.println(res1.getTotalResults()); // >>> 1\n\n        List<Document> docs1 = res1.getDocuments();\n\n        for (int i = 0; i < docs1.size(); i++) {\n            System.out.println(docs1.get(i).getId());\n        }\n        // >>> bicycle:0\n\n        SearchResult res2 = jedis.ftSearch(\"idx:bicycle\",\n            \"*\",\n            FTSearchParams.searchParams()\n                    .filter(\"price\", 270, 270)\n        );\n        System.out.println(res2.getTotalResults()); // >>> 1\n\n        List<Document> docs2 = res2.getDocuments();\n\n        for (int i = 0; i < docs2.size(); i++) {\n            System.out.println(docs2.get(i).getId());\n        }\n        // >>> bicycle:0\n        // STEP_END\n\n        // Tests for 'em1' step.\n        // REMOVE_START\n        assertEquals(1, res1.getTotalResults());\n        assertEquals(\"bicycle:0\", docs1.get(0).getId());\n\n        assertEquals(1, res2.getTotalResults());\n        assertEquals(\"bicycle:0\", docs2.get(0).getId());\n        // REMOVE_END\n\n\n        // STEP_START em2\n        SearchResult res3 = jedis.ftSearch(\"idx:bicycle\", \"@condition:{new}\");\n        System.out.println(res3.getTotalResults()); // >>> 5\n\n        List<Document> docs3 = res3.getDocuments();\n\n        for (int i = 0; i < docs3.size(); i++) {\n            System.out.println(docs3.get(i).getId());\n        }\n        // >>> bicycle:5\n        // >>> bicycle:0\n        // >>> bicycle:6\n        // >>> bicycle:7\n        // >>> bicycle:8\n        // STEP_END\n\n        // Tests for 'em2' step.\n        // REMOVE_START\n        assertEquals(5, res3.getTotalResults());\n        assertArrayEquals(\n            new String[] {\"bicycle:0\", \"bicycle:5\", \"bicycle:6\", \"bicycle:7\", \"bicycle:8\" },\n            docs3.stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START em3\n        SchemaField[] emailSchema = {\n            TextField.of(\"$.email\").as(\"email\")\n        };\n\n        jedis.ftCreate(\"idx:email\", \n            new FTCreateParams()\n                    .addPrefix(\"key:\")\n                    .on(IndexDataType.JSON),\n            emailSchema\n        );\n\n        jedis.jsonSet(\"key:1\", Path2.ROOT_PATH, \"{\\\"email\\\": \\\"test@redis.com\\\"}\");\n        \n        SearchResult res4 = jedis.ftSearch(\"idx:email\",\n            RediSearchUtil.escapeQuery(\"@email{test@redis.com}\"),\n            new FTSearchParams().dialect(2)\n        );\n        System.out.println(res4.getTotalResults());\n        // STEP_END\n\n        // Tests for 'em3' step.\n        // REMOVE_START\n        jedis.ftDropIndex(\"idx:email\");\n        // REMOVE_END\n\n\n        // STEP_START em4\n        SearchResult res5 = jedis.ftSearch(\"idx:bicycle\",\n            \"@description:\\\"rough terrain\\\"\"\n        );\n        System.out.println(res5.getTotalResults()); // >>> 1\n\n        List<Document> docs5 = res5.getDocuments();\n\n        for (int i = 0; i < docs5.size(); i++) {\n            System.out.println(docs5.get(i).getId());\n        }\n        // >>> bicycle:8\n        // STEP_END\n\n        // Tests for 'em4' step.\n        // REMOVE_START\n        assertEquals(1, res5.getTotalResults());\n        assertEquals(\"bicycle:8\", docs5.get(0).getId());\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/QueryFtExample.java",
    "content": "// EXAMPLE: query_ft\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n// HIDE_START\nimport java.util.List;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.Path2;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class QueryFtExample {\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        try {jedis.ftDropIndex(\"idx:bicycle\");} catch (JedisDataException j){}\n        //REMOVE_END\n// HIDE_END\n\n        SchemaField[] schema = {\n            TextField.of(\"$.brand\").as(\"brand\"),\n            TextField.of(\"$.model\").as(\"model\"),\n            TextField.of(\"$.description\").as(\"description\"),\n            NumericField.of(\"$.price\").as(\"price\"),\n            TagField.of(\"$.condition\").as(\"condition\")\n        };\n\n        jedis.ftCreate(\"idx:bicycle\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"bicycle:\"),\n            schema\n        );\n\n        String[] bicycleJsons = new String[] {\n            \"  {\"\n            + \"\t  \\\"pickup_zone\\\": \\\"POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, \"\n            + \"-74.0610 40.6678, -74.0610 40.7578))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-74.0060,40.7128\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Velorim\\\",\"\n            + \"\t  \\\"model\\\": \\\"Jigger\\\",\"\n            + \"\t  \\\"price\\\": 270,\"\n            + \"\t  \\\"description\\\": \\\"Small and powerful, the Jigger is the best ride for the smallest of tikes! \"\n            + \"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger \"\n            + \"is the vehicle of choice for the rare tenacious little rider raring to go.\\\",\"\n            + \"\t  \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, \"\n            + \"-118.2887 33.9872, -118.2887 34.0972))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-118.2437,34.0522\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Bicyk\\\",\"\n            + \"\t  \\\"model\\\": \\\"Hillcraft\\\",\"\n            + \"\t  \\\"price\\\": 1200,\"\n            + \"\t  \\\"description\\\": \\\"Kids want to ride with as little weight as possible. Especially \"\n            + \"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming \"\n            + \"off a 24'' bike. The Hillcraft 26 is just the solution they need!\\\",\"\n            + \"\t  \\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, \"\n            + \"-87.6848 41.8231, -87.6848 41.9331))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-87.6298,41.8781\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Nord\\\",\"\n            + \"  \t\\\"model\\\": \\\"Chook air 5\\\",\"\n            + \"  \t\\\"price\\\": 815,\"\n            + \"  \t\\\"description\\\": \\\"The Chook Air 5  gives kids aged six years and older a durable \"\n            + \"and uberlight mountain bike for their first experience on tracks and easy cruising through \"\n            + \"forests and fields. The lower  top tube makes it easy to mount and dismount in any \"\n            + \"situation, giving your kids greater safety on the trails.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, \"\n            + \"-80.2433 25.6967, -80.2433 25.8067))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-80.1918,25.7617\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Eva\\\",\"\n            + \"  \t\\\"model\\\": \\\"Eva 291\\\",\"\n            + \"  \t\\\"price\\\": 3400,\"\n            + \"  \t\\\"description\\\": \\\"The sister company to Nord, Eva launched in 2005 as the first \"\n            + \"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes \"\n            + \"are optimized for the feminine physique using analytics from a body metrics database. \"\n            + \"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This \"\n            + \"full-suspension, cross-country ride has been designed for velocity. The 291 has \"\n            + \"100mm of front and rear travel, a superlight aluminum frame and fast-rolling \"\n            + \"29-inch wheels. Yippee!\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, \"\n            + \"-122.4644 37.7099, -122.4644 37.8199))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-122.4194,37.7749\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Noka Bikes\\\",\"\n            + \"  \t\\\"model\\\": \\\"Kahuna\\\",\"\n            + \"  \t\\\"price\\\": 3200,\"\n            + \"  \t\\\"description\\\": \\\"Whether you want to try your hand at XC racing or are looking \"\n            + \"for a lively trail bike that's just as inspiring on the climbs as it is over rougher \"\n            + \"ground, the Wilder is one heck of a bike built specifically for short women. Both the \"\n            + \"frames and components have been tweaked to include a women’s saddle, different bars \"\n            + \"and unique colourway.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, \"\n            + \"-0.1778 51.4024, -0.1778 51.5524))\\\",\"\n            + \"    \\\"store_location\\\": \\\"-0.1278,51.5074\\\",\"\n            + \"    \\\"brand\\\": \\\"Breakout\\\",\"\n            + \"    \\\"model\\\": \\\"XBN 2.1 Alloy\\\",\"\n            + \"    \\\"price\\\": 810,\"\n            + \"    \\\"description\\\": \\\"The XBN 2.1 Alloy is our entry-level road bike – but that’s \"\n            + \"not to say that it’s a basic machine. With an internal weld aluminium frame, a full \"\n            + \"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which \"\n            + \"doesn’t break the bank and delivers craved performance.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, \"\n            + \"2.1767 48.5516, 2.1767 48.9016))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.3522,48.8566\\\",\"\n            + \"    \\\"brand\\\": \\\"ScramBikes\\\",\"\n            + \"    \\\"model\\\": \\\"WattBike\\\",\"\n            + \"    \\\"price\\\": 2300,\"\n            + \"    \\\"description\\\": \\\"The WattBike is the best e-bike for people who still \"\n            + \"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH \"\n            + \"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one \"\n            + \"charge. It’s great for tackling hilly terrain or if you just fancy a more \"\n            + \"leisurely ride. With three working modes, you can choose between E-bike, \"\n            + \"assisted bicycle, and normal bike modes.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, \"\n            + \"13.3260 52.2700, 13.3260 52.5700))\\\",\"\n            + \"    \\\"store_location\\\": \\\"13.4050,52.5200\\\",\"\n            + \"    \\\"brand\\\": \\\"Peaknetic\\\",\"\n            + \"    \\\"model\\\": \\\"Secto\\\",\"\n            + \"    \\\"price\\\": 430,\"\n            + \"    \\\"description\\\": \\\"If you struggle with stiff fingers or a kinked neck or \"\n            + \"back after a few minutes on the road, this lightweight, aluminum bike alleviates \"\n            + \"those issues and allows you to enjoy the ride. From the ergonomic grips to the \"\n            + \"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. \"\n            + \"The rear-inclined seat tube facilitates stability by allowing you to put a foot \"\n            + \"on the ground to balance at a stop, and the low step-over frame makes it \"\n            + \"accessible for all ability and mobility levels. The saddle is very soft, with \"\n            + \"a wide back to support your hip joints and a cutout in the center to redistribute \"\n            + \"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires \"\n            + \"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts \"\n            + \"facilitate setting up the Roll Low-Entry as your preferred commuter, and the \"\n            + \"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, \"\n            + \"1.9450 41.1987, 1.9450 41.4301))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.1734, 41.3851\\\",\"\n            + \"    \\\"brand\\\": \\\"nHill\\\",\"\n            + \"    \\\"model\\\": \\\"Summit\\\",\"\n            + \"    \\\"price\\\": 1200,\"\n            + \"    \\\"description\\\": \\\"This budget mountain bike from nHill performs well both \"\n            + \"on bike paths and on the trail. The fork with 100mm of travel absorbs rough \"\n            + \"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. \"\n            + \"The Shimano Tourney drivetrain offered enough gears for finding a comfortable \"\n            + \"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. \"\n            + \"Whether you want an affordable bike that you can take to work, but also take \"\n            + \"trail in mountains on the weekends or you’re just after a stable, comfortable \"\n            + \"ride for the bike path, the Summit gives a good value for money.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((12.4464 42.1028, 12.5464 42.1028, \"\n            + \"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\\\",\"\n            + \"    \\\"store_location\\\": \\\"12.4964,41.9028\\\",\"\n            + \"    \\\"model\\\": \\\"ThrillCycle\\\",\"\n            + \"    \\\"brand\\\": \\\"BikeShind\\\",\"\n            + \"    \\\"price\\\": 815,\"\n            + \"    \\\"description\\\": \\\"An artsy,  retro-inspired bicycle that’s as \"\n            + \"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. \"\n            + \"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t \"\n            + \"suggest taking it to the mountains. Fenders protect you from mud, and a rear \"\n            + \"basket lets you transport groceries, flowers and books. The ThrillCycle comes \"\n            + \"with a limited lifetime warranty, so this little guy will last you long \"\n            + \"past graduation.\\\",\"\n            + \"    \\\"condition\\\": \\\"refurbished\\\"\"\n            + \"  }\"\n        };\n\n        for (int i = 0; i < bicycleJsons.length; i++) {\n            jedis.jsonSet(\"bicycle:\" + i, Path2.ROOT_PATH, bicycleJsons[i]);\n        }\n\n        // STEP_START ft1\n        SearchResult res1 = jedis.ftSearch(\"idx:bicycle\", \"@description: kids\");\n        System.out.println(res1.getTotalResults()); // >>> 2\n\n        List<Document> docs1 = res1.getDocuments();\n\n        for (int i = 0; i < docs1.size(); i++) {\n            System.out.println(docs1.get(i).getId());\n        }\n        // >>> bicycle:2\n        // >>> bicycle:1\n        // STEP_END\n\n        // Tests for 'ft1' step.\n        // REMOVE_START\n        assertEquals(2, res1.getTotalResults());\n        assertArrayEquals(\n            new String[] {\"bicycle:1\", \"bicycle:2\" },\n            docs1.stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START ft2\n        SearchResult res2 = jedis.ftSearch(\"idx:bicycle\", \"@model: ka*\");\n        System.out.println(res2.getTotalResults()); // >>> 1\n\n        List<Document> docs2 = res2.getDocuments();\n\n        for (int i = 0; i < docs2.size(); i++) {\n            System.out.println(docs2.get(i).getId());\n        }\n        // >>> bicycle:4\n        // STEP_END\n\n        // Tests for 'ft2' step.\n        // REMOVE_START\n        assertEquals(1, res2.getTotalResults());\n        assertEquals(\"bicycle:4\", docs2.get(0).getId());\n        // REMOVE_END\n\n\n        // STEP_START ft3\n        SearchResult res3 = jedis.ftSearch(\"idx:bicycle\", \"@brand: *bikes\");\n        System.out.println(res3.getTotalResults()); // >>> 2\n\n        List<Document> docs3 = res3.getDocuments();\n\n        for (int i = 0; i < docs3.size(); i++) {\n            System.out.println(docs3.get(i).getId());\n        }\n        // >>> bicycle:6\n        // >>> bicycle:4\n        // STEP_END\n\n        // Tests for 'ft3' step.\n        // REMOVE_START\n        assertEquals(2, res3.getTotalResults());\n        assertArrayEquals(\n            new String[] {\"bicycle:4\", \"bicycle:6\" },\n            docs3.stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START ft4\n        SearchResult res4 = jedis.ftSearch(\"idx:bicycle\", \"%optamized%\");\n        System.out.println(res4.getTotalResults()); // >>> 1\n\n        List<Document> docs4 = res4.getDocuments();\n\n        for (int i = 0; i < docs4.size(); i++) {\n            System.out.println(docs4.get(i).getId());\n        }\n        // >>> bicycle:3\n        // STEP_END\n\n        // Tests for 'ft4' step.\n        // REMOVE_START\n        assertEquals(1, res4.getTotalResults());\n        assertEquals(\"bicycle:3\", docs4.get(0).getId());\n        // REMOVE_END\n\n\n        // STEP_START ft5\n        SearchResult res5 = jedis.ftSearch(\"idx:bicycle\", \"%%optamised%%\");\n        System.out.println(res5.getTotalResults()); // >>> 1\n\n        List<Document> docs5 = res5.getDocuments();\n\n        for (int i = 0; i < docs5.size(); i++) {\n            System.out.println(docs5.get(i).getId());\n        }\n        // >>> bicycle:3\n        // STEP_END\n\n        // Tests for 'ft5' step.\n        // REMOVE_START\n        assertEquals(1, res5.getTotalResults());\n        assertEquals(\"bicycle:3\", docs5.get(0).getId());\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/QueryGeoExample.java",
    "content": "// EXAMPLE: query_geo\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.search.schemafields.GeoShapeField.CoordinateSystem;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.Path2;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class QueryGeoExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        try { jedis.ftDropIndex(\"idx:bicycle\"); } catch (JedisDataException j) {}\n        //REMOVE_END\n// HIDE_END\n\n        SchemaField[] schema = {\n            TextField.of(\"$.brand\").as(\"brand\"),\n            TextField.of(\"$.model\").as(\"model\"),\n            TextField.of(\"$.description\").as(\"description\"),\n            NumericField.of(\"$.price\").as(\"price\"),\n            TagField.of(\"$.condition\").as(\"condition\"),\n            GeoField.of(\"$.store_location\").as(\"store_location\"),\n            GeoShapeField.of(\"$.pickup_zone\", CoordinateSystem.FLAT).as(\"pickup_zone\")\n        };\n\n        jedis.ftCreate(\"idx:bicycle\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"bicycle:\"),\n            schema\n        );\n\n        String[] bicycleJsons = new String[] {\n            \"  {\"\n            + \"\t  \\\"pickup_zone\\\": \\\"POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, \"\n            + \"-74.0610 40.6678, -74.0610 40.7578))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-74.0060,40.7128\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Velorim\\\",\"\n            + \"\t  \\\"model\\\": \\\"Jigger\\\",\"\n            + \"\t  \\\"price\\\": 270,\"\n            + \"\t  \\\"description\\\": \\\"Small and powerful, the Jigger is the best ride for the smallest of tikes! \"\n            + \"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger \"\n            + \"is the vehicle of choice for the rare tenacious little rider raring to go.\\\",\"\n            + \"\t  \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, \"\n            + \"-118.2887 33.9872, -118.2887 34.0972))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-118.2437,34.0522\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Bicyk\\\",\"\n            + \"\t  \\\"model\\\": \\\"Hillcraft\\\",\"\n            + \"\t  \\\"price\\\": 1200,\"\n            + \"\t  \\\"description\\\": \\\"Kids want to ride with as little weight as possible. Especially \"\n            + \"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming \"\n            + \"off a 24'' bike. The Hillcraft 26 is just the solution they need!\\\",\"\n            + \"\t  \\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, \"\n            + \"-87.6848 41.8231, -87.6848 41.9331))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-87.6298,41.8781\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Nord\\\",\"\n            + \"  \t\\\"model\\\": \\\"Chook air 5\\\",\"\n            + \"  \t\\\"price\\\": 815,\"\n            + \"  \t\\\"description\\\": \\\"The Chook Air 5  gives kids aged six years and older a durable \"\n            + \"and uberlight mountain bike for their first experience on tracks and easy cruising through \"\n            + \"forests and fields. The lower  top tube makes it easy to mount and dismount in any \"\n            + \"situation, giving your kids greater safety on the trails.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, \"\n            + \"-80.2433 25.6967, -80.2433 25.8067))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-80.1918,25.7617\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Eva\\\",\"\n            + \"  \t\\\"model\\\": \\\"Eva 291\\\",\"\n            + \"  \t\\\"price\\\": 3400,\"\n            + \"  \t\\\"description\\\": \\\"The sister company to Nord, Eva launched in 2005 as the first \"\n            + \"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes \"\n            + \"are optimized for the feminine physique using analytics from a body metrics database. \"\n            + \"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This \"\n            + \"full-suspension, cross-country ride has been designed for velocity. The 291 has \"\n            + \"100mm of front and rear travel, a superlight aluminum frame and fast-rolling \"\n            + \"29-inch wheels. Yippee!\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, \"\n            + \"-122.4644 37.7099, -122.4644 37.8199))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-122.4194,37.7749\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Noka Bikes\\\",\"\n            + \"  \t\\\"model\\\": \\\"Kahuna\\\",\"\n            + \"  \t\\\"price\\\": 3200,\"\n            + \"  \t\\\"description\\\": \\\"Whether you want to try your hand at XC racing or are looking \"\n            + \"for a lively trail bike that's just as inspiring on the climbs as it is over rougher \"\n            + \"ground, the Wilder is one heck of a bike built specifically for short women. Both the \"\n            + \"frames and components have been tweaked to include a women’s saddle, different bars \"\n            + \"and unique colourway.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, \"\n            + \"-0.1778 51.4024, -0.1778 51.5524))\\\",\"\n            + \"    \\\"store_location\\\": \\\"-0.1278,51.5074\\\",\"\n            + \"    \\\"brand\\\": \\\"Breakout\\\",\"\n            + \"    \\\"model\\\": \\\"XBN 2.1 Alloy\\\",\"\n            + \"    \\\"price\\\": 810,\"\n            + \"    \\\"description\\\": \\\"The XBN 2.1 Alloy is our entry-level road bike – but that’s \"\n            + \"not to say that it’s a basic machine. With an internal weld aluminium frame, a full \"\n            + \"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which \"\n            + \"doesn’t break the bank and delivers craved performance.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, \"\n            + \"2.1767 48.5516, 2.1767 48.9016))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.3522,48.8566\\\",\"\n            + \"    \\\"brand\\\": \\\"ScramBikes\\\",\"\n            + \"    \\\"model\\\": \\\"WattBike\\\",\"\n            + \"    \\\"price\\\": 2300,\"\n            + \"    \\\"description\\\": \\\"The WattBike is the best e-bike for people who still \"\n            + \"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH \"\n            + \"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one \"\n            + \"charge. It’s great for tackling hilly terrain or if you just fancy a more \"\n            + \"leisurely ride. With three working modes, you can choose between E-bike, \"\n            + \"assisted bicycle, and normal bike modes.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, \"\n            + \"13.3260 52.2700, 13.3260 52.5700))\\\",\"\n            + \"    \\\"store_location\\\": \\\"13.4050,52.5200\\\",\"\n            + \"    \\\"brand\\\": \\\"Peaknetic\\\",\"\n            + \"    \\\"model\\\": \\\"Secto\\\",\"\n            + \"    \\\"price\\\": 430,\"\n            + \"    \\\"description\\\": \\\"If you struggle with stiff fingers or a kinked neck or \"\n            + \"back after a few minutes on the road, this lightweight, aluminum bike alleviates \"\n            + \"those issues and allows you to enjoy the ride. From the ergonomic grips to the \"\n            + \"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. \"\n            + \"The rear-inclined seat tube facilitates stability by allowing you to put a foot \"\n            + \"on the ground to balance at a stop, and the low step-over frame makes it \"\n            + \"accessible for all ability and mobility levels. The saddle is very soft, with \"\n            + \"a wide back to support your hip joints and a cutout in the center to redistribute \"\n            + \"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires \"\n            + \"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts \"\n            + \"facilitate setting up the Roll Low-Entry as your preferred commuter, and the \"\n            + \"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, \"\n            + \"1.9450 41.1987, 1.9450 41.4301))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.1734, 41.3851\\\",\"\n            + \"    \\\"brand\\\": \\\"nHill\\\",\"\n            + \"    \\\"model\\\": \\\"Summit\\\",\"\n            + \"    \\\"price\\\": 1200,\"\n            + \"    \\\"description\\\": \\\"This budget mountain bike from nHill performs well both \"\n            + \"on bike paths and on the trail. The fork with 100mm of travel absorbs rough \"\n            + \"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. \"\n            + \"The Shimano Tourney drivetrain offered enough gears for finding a comfortable \"\n            + \"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. \"\n            + \"Whether you want an affordable bike that you can take to work, but also take \"\n            + \"trail in mountains on the weekends or you’re just after a stable, comfortable \"\n            + \"ride for the bike path, the Summit gives a good value for money.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((12.4464 42.1028, 12.5464 42.1028, \"\n            + \"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\\\",\"\n            + \"    \\\"store_location\\\": \\\"12.4964,41.9028\\\",\"\n            + \"    \\\"model\\\": \\\"ThrillCycle\\\",\"\n            + \"    \\\"brand\\\": \\\"BikeShind\\\",\"\n            + \"    \\\"price\\\": 815,\"\n            + \"    \\\"description\\\": \\\"An artsy,  retro-inspired bicycle that’s as \"\n            + \"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. \"\n            + \"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t \"\n            + \"suggest taking it to the mountains. Fenders protect you from mud, and a rear \"\n            + \"basket lets you transport groceries, flowers and books. The ThrillCycle comes \"\n            + \"with a limited lifetime warranty, so this little guy will last you long \"\n            + \"past graduation.\\\",\"\n            + \"    \\\"condition\\\": \\\"refurbished\\\"\"\n            + \"  }\"\n        };\n\n        for (int i = 0; i < bicycleJsons.length; i++) {\n            jedis.jsonSet(\"bicycle:\" + i, Path2.ROOT_PATH, bicycleJsons[i]);\n        }\n\n        // STEP_START geo1\n        SearchResult res1 = jedis.ftSearch(\"idx:bicycle\",\n            \"@store_location:[$lon $lat $radius $units]\",\n            FTSearchParams.searchParams()\n                    .addParam(\"lon\", -0.1778)\n                    .addParam(\"lat\", 51.5524)\n                    .addParam(\"radius\", 20)\n                    .addParam(\"units\", \"mi\")\n                    .dialect(2)\n        );\n        System.out.println(res1.getTotalResults()); // >>> 1\n\n        List<Document> docs1 = res1.getDocuments();\n\n        for (Document document : docs1) {\n            System.out.println(document.getId());\n        }\n        // >>> bicycle:5\n        // STEP_END\n\n        // Tests for 'geo1' step.\n        // REMOVE_START\n        assertEquals(1, res1.getTotalResults());\n        assertEquals(\"bicycle:5\", docs1.get(0).getId());\n        // REMOVE_END\n\n\n        // STEP_START geo2\n        SearchResult res2 = jedis.ftSearch(\"idx:bicycle\",\n            \"@pickup_zone:[CONTAINS $bike]\",\n            FTSearchParams.searchParams()\n                    .addParam(\"bike\", \"POINT(-0.1278 51.5074)\")\n                    .dialect(3)\n        );\n        System.out.println(res2.getTotalResults());   // >>> 1\n\n        List<Document> docs2 = res2.getDocuments();\n\n        for (Document document : docs2) {\n            System.out.println(document.getId());\n        }\n        // >>> bicycle:5\n        // STEP_END\n\n        // Tests for 'geo2' step.\n        // REMOVE_START\n        assertEquals(1, res2.getTotalResults());\n        assertEquals(\"bicycle:5\", docs2.get(0).getId());\n        // REMOVE_END\n\n\n        // STEP_START geo3\n        SearchResult res3 = jedis.ftSearch(\"idx:bicycle\",\n            \"@pickup_zone:[WITHIN $europe]\",\n            FTSearchParams.searchParams()\n                    .addParam(\"europe\", \"POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))\")\n                    .dialect(3)\n        );\n        System.out.println(res3.getTotalResults()); // >>> 5\n\n        List<Document> docs3 = res3.getDocuments();\n\n        for (Document document : docs3) {\n            System.out.println(document.getId());\n        }\n        // >>> bicycle:5\n        // >>> bicycle:6\n        // >>> bicycle:7\n        // >>> bicycle:8\n        // >>> bicycle:9\n        // STEP_END\n\n        // Tests for 'geo3' step.\n        // REMOVE_START\n        assertEquals(5, res3.getTotalResults());\n        assertArrayEquals(\n            Stream.of(\"bicycle:5\", \"bicycle:6\", \"bicycle:7\", \"bicycle:8\", \"bicycle:9\").sorted()\n                .toArray(), docs3.stream().map(Document::getId).sorted().toArray());\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/QueryRangeExample.java",
    "content": "// EXAMPLE: query_range\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\n\n// HIDE_START\nimport java.util.List;\n// REMOVE_START\nimport java.util.stream.Stream;\n// REMOVE_END\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.args.SortingOrder;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// HIDE_END\n\n// HIDE_START\npublic class QueryRangeExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n\n        //REMOVE_START\n        // Clear any keys here before using them in tests.\n        try { jedis.ftDropIndex(\"idx:bicycle\"); } catch (JedisDataException j) {}\n        //REMOVE_END\n        \n        SchemaField[] schema = {\n            TextField.of(\"$.brand\").as(\"brand\"),\n            TextField.of(\"$.model\").as(\"model\"),\n            TextField.of(\"$.description\").as(\"description\"),\n            NumericField.of(\"$.price\").as(\"price\"),\n            TagField.of(\"$.condition\").as(\"condition\")\n        };\n\n        jedis.ftCreate(\"idx:bicycle\",\n            FTCreateParams.createParams()\n                    .on(IndexDataType.JSON)\n                    .addPrefix(\"bicycle:\"),\n            schema\n        );\n\n        String[] bicycleJsons = new String[] {\n            \"  {\"\n            + \"\t  \\\"pickup_zone\\\": \\\"POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, \"\n            + \"-74.0610 40.6678, -74.0610 40.7578))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-74.0060,40.7128\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Velorim\\\",\"\n            + \"\t  \\\"model\\\": \\\"Jigger\\\",\"\n            + \"\t  \\\"price\\\": 270,\"\n            + \"\t  \\\"description\\\": \\\"Small and powerful, the Jigger is the best ride for the smallest of tikes! \"\n            + \"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger \"\n            + \"is the vehicle of choice for the rare tenacious little rider raring to go.\\\",\"\n            + \"\t  \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, \"\n            + \"-118.2887 33.9872, -118.2887 34.0972))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-118.2437,34.0522\\\",\"\n            + \"\t  \\\"brand\\\": \\\"Bicyk\\\",\"\n            + \"\t  \\\"model\\\": \\\"Hillcraft\\\",\"\n            + \"\t  \\\"price\\\": 1200,\"\n            + \"\t  \\\"description\\\": \\\"Kids want to ride with as little weight as possible. Especially \"\n            + \"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming \"\n            + \"off a 24'' bike. The Hillcraft 26 is just the solution they need!\\\",\"\n            + \"\t  \\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, \"\n            + \"-87.6848 41.8231, -87.6848 41.9331))\\\",\"\n            + \"\t  \\\"store_location\\\": \\\"-87.6298,41.8781\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Nord\\\",\"\n            + \"  \t\\\"model\\\": \\\"Chook air 5\\\",\"\n            + \"  \t\\\"price\\\": 815,\"\n            + \"  \t\\\"description\\\": \\\"The Chook Air 5  gives kids aged six years and older a durable \"\n            + \"and uberlight mountain bike for their first experience on tracks and easy cruising through \"\n            + \"forests and fields. The lower  top tube makes it easy to mount and dismount in any \"\n            + \"situation, giving your kids greater safety on the trails.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, \"\n            + \"-80.2433 25.6967, -80.2433 25.8067))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-80.1918,25.7617\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Eva\\\",\"\n            + \"  \t\\\"model\\\": \\\"Eva 291\\\",\"\n            + \"  \t\\\"price\\\": 3400,\"\n            + \"  \t\\\"description\\\": \\\"The sister company to Nord, Eva launched in 2005 as the first \"\n            + \"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes \"\n            + \"are optimized for the feminine physique using analytics from a body metrics database. \"\n            + \"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This \"\n            + \"full-suspension, cross-country ride has been designed for velocity. The 291 has \"\n            + \"100mm of front and rear travel, a superlight aluminum frame and fast-rolling \"\n            + \"29-inch wheels. Yippee!\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, \"\n            + \"-122.4644 37.7099, -122.4644 37.8199))\\\",\"\n            + \"  \t\\\"store_location\\\": \\\"-122.4194,37.7749\\\",\"\n            + \"  \t\\\"brand\\\": \\\"Noka Bikes\\\",\"\n            + \"  \t\\\"model\\\": \\\"Kahuna\\\",\"\n            + \"  \t\\\"price\\\": 3200,\"\n            + \"  \t\\\"description\\\": \\\"Whether you want to try your hand at XC racing or are looking \"\n            + \"for a lively trail bike that's just as inspiring on the climbs as it is over rougher \"\n            + \"ground, the Wilder is one heck of a bike built specifically for short women. Both the \"\n            + \"frames and components have been tweaked to include a women’s saddle, different bars \"\n            + \"and unique colourway.\\\",\"\n            + \"  \t\\\"condition\\\": \\\"used\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, \"\n            + \"-0.1778 51.4024, -0.1778 51.5524))\\\",\"\n            + \"    \\\"store_location\\\": \\\"-0.1278,51.5074\\\",\"\n            + \"    \\\"brand\\\": \\\"Breakout\\\",\"\n            + \"    \\\"model\\\": \\\"XBN 2.1 Alloy\\\",\"\n            + \"    \\\"price\\\": 810,\"\n            + \"    \\\"description\\\": \\\"The XBN 2.1 Alloy is our entry-level road bike – but that’s \"\n            + \"not to say that it’s a basic machine. With an internal weld aluminium frame, a full \"\n            + \"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which \"\n            + \"doesn’t break the bank and delivers craved performance.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, \"\n            + \"2.1767 48.5516, 2.1767 48.9016))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.3522,48.8566\\\",\"\n            + \"    \\\"brand\\\": \\\"ScramBikes\\\",\"\n            + \"    \\\"model\\\": \\\"WattBike\\\",\"\n            + \"    \\\"price\\\": 2300,\"\n            + \"    \\\"description\\\": \\\"The WattBike is the best e-bike for people who still \"\n            + \"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH \"\n            + \"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one \"\n            + \"charge. It’s great for tackling hilly terrain or if you just fancy a more \"\n            + \"leisurely ride. With three working modes, you can choose between E-bike, \"\n            + \"assisted bicycle, and normal bike modes.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, \"\n            + \"13.3260 52.2700, 13.3260 52.5700))\\\",\"\n            + \"    \\\"store_location\\\": \\\"13.4050,52.5200\\\",\"\n            + \"    \\\"brand\\\": \\\"Peaknetic\\\",\"\n            + \"    \\\"model\\\": \\\"Secto\\\",\"\n            + \"    \\\"price\\\": 430,\"\n            + \"    \\\"description\\\": \\\"If you struggle with stiff fingers or a kinked neck or \"\n            + \"back after a few minutes on the road, this lightweight, aluminum bike alleviates \"\n            + \"those issues and allows you to enjoy the ride. From the ergonomic grips to the \"\n            + \"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. \"\n            + \"The rear-inclined seat tube facilitates stability by allowing you to put a foot \"\n            + \"on the ground to balance at a stop, and the low step-over frame makes it \"\n            + \"accessible for all ability and mobility levels. The saddle is very soft, with \"\n            + \"a wide back to support your hip joints and a cutout in the center to redistribute \"\n            + \"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires \"\n            + \"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts \"\n            + \"facilitate setting up the Roll Low-Entry as your preferred commuter, and the \"\n            + \"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, \"\n            + \"1.9450 41.1987, 1.9450 41.4301))\\\",\"\n            + \"    \\\"store_location\\\": \\\"2.1734, 41.3851\\\",\"\n            + \"    \\\"brand\\\": \\\"nHill\\\",\"\n            + \"    \\\"model\\\": \\\"Summit\\\",\"\n            + \"    \\\"price\\\": 1200,\"\n            + \"    \\\"description\\\": \\\"This budget mountain bike from nHill performs well both \"\n            + \"on bike paths and on the trail. The fork with 100mm of travel absorbs rough \"\n            + \"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. \"\n            + \"The Shimano Tourney drivetrain offered enough gears for finding a comfortable \"\n            + \"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. \"\n            + \"Whether you want an affordable bike that you can take to work, but also take \"\n            + \"trail in mountains on the weekends or you’re just after a stable, comfortable \"\n            + \"ride for the bike path, the Summit gives a good value for money.\\\",\"\n            + \"    \\\"condition\\\": \\\"new\\\"\"\n            + \"  }\",\n\n            \"  {\"\n            + \"    \\\"pickup_zone\\\": \\\"POLYGON((12.4464 42.1028, 12.5464 42.1028, \"\n            + \"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\\\",\"\n            + \"    \\\"store_location\\\": \\\"12.4964,41.9028\\\",\"\n            + \"    \\\"model\\\": \\\"ThrillCycle\\\",\"\n            + \"    \\\"brand\\\": \\\"BikeShind\\\",\"\n            + \"    \\\"price\\\": 815,\"\n            + \"    \\\"description\\\": \\\"An artsy,  retro-inspired bicycle that’s as \"\n            + \"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. \"\n            + \"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t \"\n            + \"suggest taking it to the mountains. Fenders protect you from mud, and a rear \"\n            + \"basket lets you transport groceries, flowers and books. The ThrillCycle comes \"\n            + \"with a limited lifetime warranty, so this little guy will last you long \"\n            + \"past graduation.\\\",\"\n            + \"    \\\"condition\\\": \\\"refurbished\\\"\"\n            + \"  }\"\n        };\n\n        for (int i = 0; i < bicycleJsons.length; i++) {\n            jedis.jsonSet(\"bicycle:\" + i, Path2.ROOT_PATH, bicycleJsons[i]);\n        }\n// HIDE_END\n\n\n        // STEP_START range1\n        SearchResult res1 = jedis.ftSearch(\n            \"idx:bicycle\", \"@price:[500 1000]\",\n            FTSearchParams.searchParams().returnFields(\"price\"));\n        System.out.println(res1.getTotalResults()); // >>> 3\n\n        List<Document> docs1 = res1.getDocuments();\n\n        for (Document document : docs1) {\n            System.out.println(document.getId() + \" : price \" + document.getString(\"price\"));\n        }\n        // >>> bicycle:2 : price 815\n        // >>> bicycle:5 : price 810\n        // >>> bicycle:9 : price 815\n        // STEP_END\n\n        // Tests for 'range1' step.\n        // REMOVE_START\n        assertEquals(3, res1.getTotalResults());\n        assertArrayEquals(\n            Stream.of(\"bicycle:5\", \"bicycle:9\", \"bicycle:2\").sorted().toArray(),\n            docs1.stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START range2\n        SearchResult res2 = jedis.ftSearch(\"idx:bicycle\",\n            \"*\",\n            FTSearchParams.searchParams()\n                .returnFields(\"price\")\n                .filter(\"price\", 500, 1000)\n        );\n        System.out.println(res2.getTotalResults()); // >>> 3\n\n        List<Document> docs2 = res2.getDocuments();\n\n        for (Document document : docs2) {\n            System.out.println(document.getId() + \" : price \" + document.getString(\"price\"));\n        }\n        // >>> bicycle:2 : price 815\n        // >>> bicycle:5 : price 810\n        // >>> bicycle:9 : price 815\n        // STEP_END\n\n        // Tests for 'range2' step.\n        // REMOVE_START\n        assertEquals(3, res2.getTotalResults());\n        assertArrayEquals(\n            Stream.of(\"bicycle:5\", \"bicycle:9\", \"bicycle:2\").sorted().toArray(),\n            docs2.stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n\n        // STEP_START range3\n        SearchResult res3 = jedis.ftSearch(\"idx:bicycle\",\n            \"*\",\n            FTSearchParams.searchParams()\n                .returnFields(\"price\")\n                .filter(\"price\", 1000, true, Double.POSITIVE_INFINITY, false)\n        );\n        System.out.println(res3.getTotalResults()); // >>> 5\n\n        List<Document> docs3 = res3.getDocuments();\n\n        for (Document document : docs3) {\n            System.out.println(document.getId() + \" : price \" + document.getString(\"price\"));\n        }\n        // >>> bicycle:1 : price 1200\n        // >>> bicycle:4 : price 3200\n        // >>> bicycle:6 : price 2300\n        // >>> bicycle:3 : price 3400\n        // >>> bicycle:8 : price 1200\n        // STEP_END\n\n        // Tests for 'range3' step.\n        // REMOVE_START\n        assertEquals(5, res3.getTotalResults());\n        assertArrayEquals(\n            Stream.of(\"bicycle:1\", \"bicycle:4\", \"bicycle:6\", \"bicycle:3\", \"bicycle:8\")\n                .sorted().toArray(),\n            docs3.stream().map(Document::getId).sorted().toArray());\n        // REMOVE_END\n\n\n        // STEP_START range4\n        SearchResult res4 = jedis.ftSearch(\"idx:bicycle\",\n            \"@price:[-inf 2000]\",\n            FTSearchParams.searchParams()\n                    .returnFields(\"price\")\n                    .sortBy(\"price\", SortingOrder.ASC)\n                    .limit(0, 5) \n        );\n        System.out.println(res4.getTotalResults()); // >>> 7\n\n        List<Document> docs4 = res4.getDocuments();\n\n        for (Document document : docs4) {\n            System.out.println(document.getId() + \" : price \" + document.getString(\"price\"));\n        }\n        // >>> bicycle:0 : price 270\n        // >>> bicycle:7 : price 430\n        // >>> bicycle:5 : price 810\n        // >>> bicycle:2 : price 815\n        // >>> bicycle:9 : price 815\n        // STEP_END\n\n        // Tests for 'range4' step.\n        // REMOVE_START\n        assertEquals(7, res4.getTotalResults());\n        assertArrayEquals(\n            new String[] {\"bicycle:0\", \"bicycle:2\", \"bicycle:5\", \"bicycle:7\", \"bicycle:9\"},\n            docs4.stream().map(Document::getId).sorted().toArray()\n        );\n        // REMOVE_END\n\n// HIDE_START\n        jedis.close();\n    }\n}\n// HIDE_END\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/SearchQuickstartExample.java",
    "content": "// EXAMPLE: search_quickstart\npackage io.redis.examples;\n\nimport java.math.BigDecimal;\nimport java.util.*;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.exceptions.*;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.*;\nimport redis.clients.jedis.search.schemafields.*;\n// REMOVE_START\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// REMOVE_END\n\nclass Bicycle {\n  public String brand;\n  public String model;\n  public BigDecimal price;\n  public String description;\n  public String condition;\n\n  public Bicycle(String brand, String model, BigDecimal price, String condition, String description) {\n    this.brand = brand;\n    this.model = model;\n    this.price = price;\n    this.condition = condition;\n    this.description = description;\n  }\n}\n\npublic class SearchQuickstartExample {\n\n  @Test\n  public void run() {\n    // STEP_START connect\n    RedisClient jedis = RedisClient.create(\"localhost\", 6379);\n    // STEP_END\n    // REMOVE_START\n    try {\n      jedis.ftDropIndex(\"idx:bicycle\");\n    } catch (JedisDataException e) {\n      System.out.println(\"Can't connect to Redis: \" + e.getMessage());\n    }\n    // REMOVE_END\n\n    // STEP_START create_index\n    SchemaField[] schema = {\n      TextField.of(\"$.brand\").as(\"brand\"),\n      TextField.of(\"$.model\").as(\"model\"),\n      TextField.of(\"$.description\").as(\"description\"),\n      NumericField.of(\"$.price\").as(\"price\"),\n      TagField.of(\"$.condition\").as(\"condition\")\n    };\n\n    jedis.ftCreate(\"idx:bicycle\",\n      FTCreateParams.createParams()\n      .on(IndexDataType.JSON)\n      .addPrefix(\"bicycle:\"),\n      schema\n    );\n    // STEP_END\n\n    Bicycle[] bicycles = {\n        new Bicycle(\n            \"Velorim\",\n            \"Jigger\",\n            new BigDecimal(270),\n            \"new\",\n            \"Small and powerful, the Jigger is the best ride \" +\n            \"for the smallest of tikes! This is the tiniest \" +\n            \"kids’ pedal bike on the market available without\" +\n            \" a coaster brake, the Jigger is the vehicle of \" +\n            \"choice for the rare tenacious little rider \" +\n            \"raring to go.\"\n        ),\n        new Bicycle(\n            \"Bicyk\",\n            \"Hillcraft\",\n            new BigDecimal(1200),\n            \"used\",\n            \"Kids want to ride with as little weight as possible.\" +\n            \" Especially on an incline! They may be at the age \" +\n            \"when a 27.5 inch wheel bike is just too clumsy coming \" +\n            \"off a 24 inch bike. The Hillcraft 26 is just the solution\" +\n            \" they need!\"\n        ),\n        new Bicycle(\n            \"Nord\",\n            \"Chook air 5\",\n            new BigDecimal(815),\n            \"used\",\n            \"The Chook Air 5  gives kids aged six years and older \" +\n            \"a durable and uberlight mountain bike for their first\" +\n            \" experience on tracks and easy cruising through forests\" +\n            \" and fields. The lower  top tube makes it easy to mount\" +\n            \" and dismount in any situation, giving your kids greater\" +\n            \" safety on the trails.\"\n        ),\n        new Bicycle(\n            \"Eva\",\n            \"Eva 291\",\n            new BigDecimal(3400),\n            \"used\",\n            \"The sister company to Nord, Eva launched in 2005 as the\" +\n            \" first and only women-dedicated bicycle brand. Designed\" +\n            \" by women for women, allEva bikes are optimized for the\" +\n            \" feminine physique using analytics from a body metrics\" +\n            \" database. If you like 29ers, try the Eva 291. It's a \" +\n            \"brand new bike for 2022.. This full-suspension, \" +\n            \"cross-country ride has been designed for velocity. The\" +\n            \" 291 has 100mm of front and rear travel, a superlight \" +\n            \"aluminum frame and fast-rolling 29-inch wheels. Yippee!\"\n        ),\n        new Bicycle(\n            \"Noka Bikes\",\n            \"Kahuna\",\n            new BigDecimal(3200),\n            \"used\",\n            \"Whether you want to try your hand at XC racing or are \" +\n            \"looking for a lively trail bike that's just as inspiring\" +\n            \" on the climbs as it is over rougher ground, the Wilder\" +\n            \" is one heck of a bike built specifically for short women.\" +\n            \" Both the frames and components have been tweaked to \" +\n            \"include a women’s saddle, different bars and unique \" +\n            \"colourway.\"\n        ),\n        new Bicycle(\n            \"Breakout\",\n            \"XBN 2.1 Alloy\",\n            new BigDecimal(810),\n            \"new\",\n            \"The XBN 2.1 Alloy is our entry-level road bike – but that’s\" +\n            \" not to say that it’s a basic machine. With an internal \" +\n            \"weld aluminium frame, a full carbon fork, and the slick-shifting\" +\n            \" Claris gears from Shimano’s, this is a bike which doesn’t\" +\n            \" break the bank and delivers craved performance.\"\n        ),\n        new Bicycle(\n            \"ScramBikes\",\n            \"WattBike\",\n            new BigDecimal(2300),\n            \"new\",\n            \"The WattBike is the best e-bike for people who still feel young\" +\n            \" at heart. It has a Bafang 1000W mid-drive system and a 48V\" +\n            \" 17.5AH Samsung Lithium-Ion battery, allowing you to ride for\" +\n            \" more than 60 miles on one charge. It’s great for tackling hilly\" +\n            \" terrain or if you just fancy a more leisurely ride. With three\" +\n            \" working modes, you can choose between E-bike, assisted bicycle,\" +\n            \" and normal bike modes.\"\n        ),\n        new Bicycle(\n            \"Peaknetic\",\n            \"Secto\",\n            new BigDecimal(430),\n            \"new\",\n            \"If you struggle with stiff fingers or a kinked neck or back after\" +\n            \" a few minutes on the road, this lightweight, aluminum bike\" +\n            \" alleviates those issues and allows you to enjoy the ride. From\" +\n            \" the ergonomic grips to the lumbar-supporting seat position, the\" +\n            \" Roll Low-Entry offers incredible comfort. The rear-inclined seat\" +\n            \" tube facilitates stability by allowing you to put a foot on the\" +\n            \" ground to balance at a stop, and the low step-over frame makes it\" +\n            \" accessible for all ability and mobility levels. The saddle is\" +\n            \" very soft, with a wide back to support your hip joints and a\" +\n            \" cutout in the center to redistribute that pressure. Rim brakes\" +\n            \" deliver satisfactory braking control, and the wide tires provide\" +\n            \" a smooth, stable ride on paved roads and gravel. Rack and fender\" +\n            \" mounts facilitate setting up the Roll Low-Entry as your preferred\" +\n            \" commuter, and the BMX-like handlebar offers space for mounting a\" +\n            \" flashlight, bell, or phone holder.\"\n        ),\n        new Bicycle(\n            \"nHill\",\n            \"Summit\",\n            new BigDecimal(1200),\n            \"new\",\n            \"This budget mountain bike from nHill performs well both on bike\" +\n            \" paths and on the trail. The fork with 100mm of travel absorbs\" +\n            \" rough terrain. Fat Kenda Booster tires give you grip in corners\" +\n            \" and on wet trails. The Shimano Tourney drivetrain offered enough\" +\n            \" gears for finding a comfortable pace to ride uphill, and the\" +\n            \" Tektro hydraulic disc brakes break smoothly. Whether you want an\" +\n            \" affordable bike that you can take to work, but also take trail in\" +\n            \" mountains on the weekends or you’re just after a stable,\" +\n            \" comfortable ride for the bike path, the Summit gives a good value\" +\n            \" for money.\"\n        ),\n        new Bicycle(\n            \"ThrillCycle\",\n            \"BikeShind\",\n            new BigDecimal(815),\n            \"refurbished\",\n            \"An artsy,  retro-inspired bicycle that’s as functional as it is\" +\n            \" pretty: The ThrillCycle steel frame offers a smooth ride. A\" +\n            \" 9-speed drivetrain has enough gears for coasting in the city, but\" +\n            \" we wouldn’t suggest taking it to the mountains. Fenders protect\" +\n            \" you from mud, and a rear basket lets you transport groceries,\" +\n            \" flowers and books. The ThrillCycle comes with a limited lifetime\" +\n            \" warranty, so this little guy will last you long past graduation.\"\n        ),\n    };\n\n    // STEP_START add_documents\n    for (int i = 0; i < bicycles.length; i++) {\n      jedis.jsonSetWithEscape(String.format(\"bicycle:%d\", i), bicycles[i]);\n    }\n    // STEP_END\n\n    // STEP_START wildcard_query\n    Query query1 = new Query(\"*\");\n    List<Document> result1 = jedis.ftSearch(\"idx:bicycle\", query1).getDocuments();\n    System.out.println(\"Documents found:\" + result1.size());\n    // Prints: Documents found: 10\n    // STEP_END\n    // REMOVE_START\n    assertEquals(10, result1.size(), \"Validate total results\");\n    // REMOVE_END\n\n    // STEP_START query_single_term\n    Query query2 = new Query(\"@model:Jigger\");\n    List<Document> result2 = jedis.ftSearch(\"idx:bicycle\", query2).getDocuments();\n    System.out.println(result2);\n    // Prints: [id:bicycle:0, score: 1.0, payload:null,\n    // properties:[$={\"brand\":\"Velorim\",\"model\":\"Jigger\",\"price\":270,\"description\":\"Small and powerful, the Jigger is the best ride for the smallest of tikes! This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger is the vehicle of choice for the rare tenacious little rider raring to go.\",\"condition\":\"new\"}]]\n    // STEP_END\n    // REMOVE_START\n    assertEquals(\"bicycle:0\", result2.get(0).getId(), \"Validate bike id\");\n    // REMOVE_END\n\n    // STEP_START query_single_term_limit_fields\n    Query query3 = new Query(\"@model:Jigger\").returnFields(\"price\");\n    List<Document> result3 = jedis.ftSearch(\"idx:bicycle\", query3).getDocuments();\n    System.out.println(result3);\n    // Prints: [id:bicycle:0, score: 1.0, payload:null, properties:[price=270]]\n    // STEP_END\n    // REMOVE_START\n    assertEquals(\"bicycle:0\", result3.get(0).getId(),\"Validate cargo bike id\");\n    // REMOVE_END\n\n    // STEP_START query_single_term_and_num_range\n    Query query4 = new Query(\"basic @price:[500 1000]\");\n    List<Document> result4 = jedis.ftSearch(\"idx:bicycle\", query4).getDocuments();\n    System.out.println(result4);\n    // Prints: [id:bicycle:5, score: 1.0, payload:null,\n    // properties:[$={\"brand\":\"Breakout\",\"model\":\"XBN 2.1 Alloy\",\"price\":810,\"description\":\"The XBN 2.1 Alloy is our entry-level road bike – but that’s not to say that it’s a basic machine. With an internal weld aluminium frame, a full carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which doesn’t break the bank and delivers craved performance.\",\"condition\":\"new\"}]]\n    // STEP_END\n    // REMOVE_START\n    assertEquals(\"bicycle:5\", result4.get(0).getId(), \"Validate bike id\");\n    // REMOVE_END\n\n    // STEP_START query_exact_matching\n    Query query5 = new Query(\"@brand:\\\"Noka Bikes\\\"\");\n    List<Document> result5 = jedis.ftSearch(\"idx:bicycle\", query5).getDocuments();\n    System.out.println(result5);\n    // Prints: [id:bicycle:4, score: 1.0, payload:null,\n    // properties:[$={\"brand\":\"Noka Bikes\",\"model\":\"Kahuna\",\"price\":3200,\"description\":\"Whether you want to try your hand at XC racing or are looking for a lively trail bike that's just as inspiring on the climbs as it is over rougher ground, the Wilder is one heck of a bike built specifically for short women. Both the frames and components have been tweaked to include a women’s saddle, different bars and unique colourway.\",\"condition\":\"used\"}]]\n    // STEP_END\n    // REMOVE_START\n    assertEquals(\"bicycle:4\", result5.get(0).getId(), \"Validate bike id\");\n    // REMOVE_END\n\n    // STEP_START simple_aggregation\n    AggregationBuilder ab = new AggregationBuilder(\"*\").groupBy(\"@condition\",\n      Reducers.count().as(\"count\"));\n    AggregationResult ar = jedis.ftAggregate(\"idx:bicycle\", ab);\n    for (int i = 0; i < ar.getTotalResults(); i++) {\n      System.out.println(ar.getRow(i).getString(\"condition\") + \" - \"\n          + ar.getRow(i).getString(\"count\"));\n    }\n    // Prints:\n    // refurbished - 1\n    // used - 5\n    // new - 4\n    assertEquals(3, ar.getTotalResults(), \"Validate aggregation results\");\n    // STEP_END\n\n    jedis.close();\n  }\n}"
  },
  {
    "path": "src/test/java/io/redis/examples/SetGetExample.java",
    "content": "// EXAMPLE: set_and_get\n// HIDE_START\npackage io.redis.examples;\n\nimport redis.clients.jedis.RedisClient;\n\n// REMOVE_START\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n// REMOVE_END\n\npublic class SetGetExample {\n\n  @Test\n  public void run() {\n\n    RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n    // HIDE_END\n\n    String status = jedis.set(\"bike:1\", \"Process 134\");\n\n    if (\"OK\".equals(status)) System.out.println(\"Successfully added a bike.\");\n\n    String value = jedis.get(\"bike:1\");\n\n    if (value != null) System.out.println(\"The name of the bike is: \" + value + \".\");\n\n    // REMOVE_START\n    assertEquals(\"OK\", status);\n    assertEquals(\"Process 134\", value);\n    // REMOVE_END\n\n// HIDE_START\n    jedis.close();\n  }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/examples/SetsExample.java",
    "content": "//EXAMPLE: sets_tutorial\n//HIDE_START\npackage io.redis.examples;\n\nimport redis.clients.jedis.RedisClient;\nimport org.junit.jupiter.api.Test;\nimport java.util.List;\nimport java.util.Set;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class SetsExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // HIDE_END\n\n        // REMOVE_START\n        jedis.del(\"bikes:racing:france\");\n        jedis.del(\"bikes:racing:usa\");\n        // REMOVE_END\n        // STEP_START sadd\n        long res1 = jedis.sadd(\"bikes:racing:france\", \"bike:1\");\n        System.out.println(res1);  // >>> 1\n\n        long res2 = jedis.sadd(\"bikes:racing:france\", \"bike:1\");\n        System.out.println(res2);  // >>> 0\n\n        long res3 = jedis.sadd(\"bikes:racing:france\", \"bike:2\", \"bike:3\");\n        System.out.println(res3);  // >>> 2\n\n        long res4 = jedis.sadd(\"bikes:racing:usa\", \"bike:1\", \"bike:4\");\n        System.out.println(res4);  // >>> 2\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1,res1);\n        assertEquals(0,res2);\n        assertEquals(2,res3);\n        assertEquals(2,res4);\n        // REMOVE_END\n\n        // STEP_START sismember\n        // HIDE_START\n        jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\");\n        jedis.sadd(\"bikes:racing:usa\", \"bike:1\", \"bike:4\");\n        // HIDE_END\n\n        boolean res5 = jedis.sismember(\"bikes:racing:usa\", \"bike:1\");\n        System.out.println(res5);  // >>> true\n\n        boolean res6 = jedis.sismember(\"bikes:racing:usa\", \"bike:2\");\n        System.out.println(res6);  // >>> false\n        // STEP_END\n\n        // REMOVE_START\n        assertTrue(res5);\n        assertFalse(res6);\n        // REMOVE_END\n\n        // STEP_START sinter\n        // HIDE_START\n        jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\");\n        jedis.sadd(\"bikes:racing:usa\", \"bike:1\", \"bike:4\");\n        // HIDE_END\n\n        Set<String> res7 = jedis.sinter(\"bikes:racing:france\", \"bikes:racing:usa\");\n        System.out.println(res7);  // >>> [bike:1]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(\"[bike:1]\",res7.toString());\n        // REMOVE_END\n\n        // STEP_START scard\n        jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\");\n\n        long res8 = jedis.scard(\"bikes:racing:france\");\n        System.out.println(res8);  // >>> 3\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(3,res8);\n        jedis.del(\"bikes:racing:france\");\n        // REMOVE_END\n\n        // STEP_START sadd_smembers\n        long res9 = jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\");\n        System.out.println(res9);  // >>> 3\n\n        Set<String> res10 = jedis.smembers(\"bikes:racing:france\");\n        System.out.println(res10);  // >>> [bike:1, bike:2, bike:3]\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(3,res9);\n        assertEquals(\"[bike:1, bike:2, bike:3]\",res10.toString());\n        // REMOVE_END\n\n        // STEP_START smismember\n        boolean res11 = jedis.sismember(\"bikes:racing:france\", \"bike:1\");\n        System.out.println(res11);  // >>> true\n\n        List<Boolean> res12 = jedis.smismember(\"bikes:racing:france\", \"bike:2\", \"bike:3\", \"bike:4\");\n        System.out.println(res12);  // >>> [true,true,false]\n        // STEP_END\n\n        // REMOVE_START\n        assertTrue(res11);\n        assertEquals(\"[true, true, false]\",res12.toString());\n        // REMOVE_END\n\n        // STEP_START sdiff\n        jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\");\n        jedis.sadd(\"bikes:racing:usa\", \"bike:1\", \"bike:4\");\n\n        Set<String> res13 = jedis.sdiff(\"bikes:racing:france\", \"bikes:racing:usa\");\n        System.out.println(res13);  // >>> [bike:2, bike:3]\n\n        // REMOVE_START\n        assertEquals(\"[bike:2, bike:3]\",res13.toString());\n        jedis.del(\"bikes:racing:france\");\n        jedis.del(\"bikes:racing:usa\");\n        // REMOVE_END\n        // STEP_END\n\n        // STEP_START multisets\n        jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\");\n        jedis.sadd(\"bikes:racing:usa\", \"bike:1\", \"bike:4\");\n        jedis.sadd(\"bikes:racing:italy\", \"bike:1\", \"bike:2\", \"bike:3\", \"bike:4\");\n\n        Set<String> res14 = jedis.sinter(\"bikes:racing:france\", \"bikes:racing:usa\", \"bikes:racing:italy\");\n        System.out.println(res14);  // >>> [bike:1]\n\n        Set<String> res15 = jedis.sunion(\"bikes:racing:france\", \"bikes:racing:usa\", \"bikes:racing:italy\");\n        System.out.println(res15);  // >>> [bike:1, bike:2, bike:3, bike:4]\n\n        Set<String> res16 = jedis.sdiff(\"bikes:racing:france\", \"bikes:racing:usa\", \"bikes:racing:italy\");\n        System.out.println(res16);  // >>> []\n\n        Set<String> res17 = jedis.sdiff(\"bikes:racing:usa\", \"bikes:racing:france\");\n        System.out.println(res17);  // >>> [bike:4]\n\n        Set<String> res18 = jedis.sdiff(\"bikes:racing:france\", \"bikes:racing:usa\");\n        System.out.println(res18);  // >>> [bike:2, bike:3]\n\n        // REMOVE_START\n        assertEquals(\"[bike:1]\",res14.toString());\n        assertEquals(\"[bike:1, bike:2, bike:3, bike:4]\",res15.toString());\n        assertEquals(\"[]\",res16.toString());\n        assertEquals(\"[bike:4]\",res17.toString());\n        assertEquals(\"[bike:2, bike:3]\",res18.toString());\n        jedis.del(\"bikes:racing:france\");\n        jedis.del(\"bikes:racing:usa\");\n        jedis.del(\"bikes:racing:italy\");\n        // REMOVE_END\n        // STEP_END\n\n        // STEP_START srem\n        jedis.sadd(\"bikes:racing:france\", \"bike:1\", \"bike:2\", \"bike:3\", \"bike:4\", \"bike:5\");\n\n        long res19 = jedis.srem(\"bikes:racing:france\", \"bike:1\");\n        System.out.println(res18);  // >>> 1\n\n        String res20 = jedis.spop(\"bikes:racing:france\");\n        System.out.println(res20);  // >>> bike:3\n\n        Set<String> res21 = jedis.smembers(\"bikes:racing:france\");\n        System.out.println(res21);  // >>> [bike:2, bike:4, bike:5]\n\n        String res22 = jedis.srandmember(\"bikes:racing:france\");\n        System.out.println(res22);  // >>> bike:4\n        // STEP_END\n\n        // REMOVE_START\n        assertEquals(1,res19);\n        // REMOVE_END\n\n        // HIDE_START\n        jedis.close();\n        // HIDE_END\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/SortedSetsExample.java",
    "content": "//EXAMPLE: ss_tutorial\n//HIDE_START\npackage io.redis.examples;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.resps.Tuple;\n//HIDE_END\n\n//REMOVE_START\nimport org.junit.jupiter.api.Test;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n//REMOVE_END\n\npublic class SortedSetsExample {\n\n  @Test\n  public void run() {\n    //HIDE_START\n    RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n    //HIDE_END\n\n    //REMOVE_START\n    jedis.del(\"racer_scores\");\n    //REMOVE_END\n\n    //STEP_START zadd\n    long res1 = jedis.zadd(\"racer_scores\", 10d, \"Norem\");\n    System.out.println(res1); // >>> 1\n\n    long res2 = jedis.zadd(\"racer_scores\", 12d, \"Castilla\");\n    System.out.println(res2); // >>> 1\n\n    long res3 = jedis.zadd(\"racer_scores\", new HashMap<String,Double>() {{\n      put(\"Sam-Bodden\", 8d);\n      put(\"Royce\", 10d);\n      put(\"Ford\", 6d);\n      put(\"Prickett\", 14d);\n      put(\"Castilla\", 12d);\n    }});\n    System.out.println(res3); // >>> 4\n    //STEP_END\n\n    //STEP_START zrange\n    List<String> res4 = jedis.zrange(\"racer_scores\", 0, -1);\n    System.out.println(res4); // >>> [Ford, Sam-Bodden, Norem, Royce, Castil, Castilla, Prickett]\n\n    List<String> res5 = jedis.zrevrange(\"racer_scores\", 0, -1);\n    System.out.println(res5); // >>> [Prickett, Castilla, Castil, Royce, Norem, Sam-Bodden, Ford]\n    //STEP_END\n\n    //STEP_START zrange_withscores\n    List<Tuple> res6 = jedis.zrangeWithScores(\"racer_scores\", 0, -1);\n    System.out.println(res6); // >>> [[Ford,6.0], [Sam-Bodden,8.0], [Norem,10.0], [Royce,10.0], [Castil,12.0], [Castilla,12.0], [Prickett,14.0]]\n    //STEP_END\n\n    //STEP_START zrangebyscore\n    List<String> res7 = jedis.zrangeByScore(\"racer_scores\", Double.MIN_VALUE, 10d);\n    System.out.println(res7); // >>> [Ford, Sam-Bodden, Norem, Royce]\n    //STEP_END\n\n    //STEP_START zremrangebyscore\n    long res8 = jedis.zrem(\"racer_scores\", \"Castilla\");\n    System.out.println(res8); // >>> 1\n\n    long res9 = jedis.zremrangeByScore(\"racer_scores\", Double.MIN_VALUE, 9d);\n    System.out.println(res9); // >>> 2\n\n    List<String> res10 = jedis.zrange(\"racer_scores\", 0, -1);\n    System.out.println(res10); // >>> [Norem, Royce, Prickett]\n    //STEP_END\n\n    //REMOVE_START\n    assertEquals(3, jedis.zcard(\"racer_scores\"));\n    //REMOVE_END\n\n    //STEP_START zrank\n    long res11 = jedis.zrank(\"racer_scores\", \"Norem\");\n    System.out.println(res11); // >>> 0\n\n    long res12 = jedis.zrevrank(\"racer_scores\", \"Norem\");\n    System.out.println(res12); // >>> 2\n    //STEP_END\n\n    //STEP_START zadd_lex\n    long res13 = jedis.zadd(\"racer_scores\", new HashMap<String,Double>() {{\n      put(\"Norem\", 0d);\n      put(\"Sam-Bodden\", 0d);\n      put(\"Royce\", 0d);\n      put(\"Ford\", 0d);\n      put(\"Prickett\", 0d);\n      put(\"Castilla\", 0d);\n    }});\n    System.out.println(res13); // >>> 3\n\n    List<String> res14 = jedis.zrange(\"racer_scores\", 0, -1);\n    System.out.println(res14); // >>> [Castilla, Ford, Norem, Prickett, Royce, Sam-Bodden]\n\n    List<String> res15 = jedis.zrangeByLex(\"racer_scores\", \"[A\", \"[L\");\n    System.out.println(res15); // >>> [Castilla, Ford]\n    //STEP_END\n\n    //STEP_START leaderboard\n    long res16 = jedis.zadd(\"racer_scores\", 100d, \"Wood\");\n    System.out.println(res16); // >>> 1\n\n    long res17 = jedis.zadd(\"racer_scores\", 100d, \"Henshaw\");\n    System.out.println(res17); // >>> 1\n\n    long res18 = jedis.zadd(\"racer_scores\", 100d, \"Henshaw\");\n    System.out.println(res18); // >>> 0\n\n    double res19 = jedis.zincrby(\"racer_scores\", 50d, \"Wood\");\n    System.out.println(res19); // >>> 150.0\n\n    double res20 = jedis.zincrby(\"racer_scores\", 50d, \"Henshaw\");\n    System.out.println(res20); // >>> 200.0\n    //STEP_END\n\n    //HIDE_START\n    jedis.close();\n    //HIDE_END\n  }\n}\n\n"
  },
  {
    "path": "src/test/java/io/redis/examples/StreamsExample.java",
    "content": "//EXAMPLE: stream_tutorial\n//HIDE_START\npackage io.redis.examples;\n\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.RedisClient;\n//HIDE_END\n\n//REMOVE_START\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\n\nimport java.util.*;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n//REMOVE_END\n\npublic class StreamsExample {\n\n  @Test\n  public void run() {\n    //HIDE_START\n    RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n    //HIDE_END\n\n    //REMOVE_START\n    jedis.del(\"race:france\", \"race:italy\", \"race:usa\");\n    //REMOVE_END\n\n    // STEP_START xadd\n    StreamEntryID res1 = jedis.xadd(\"race:france\",new HashMap<String,String>(){{put(\"rider\",\"Castilla\");put(\"speed\",\"30.2\");put(\"position\",\"1\");put(\"location_id\",\"1\");}} , XAddParams.xAddParams());\n\n    System.out.println(res1); // >>> 1701760582225-0\n\n    StreamEntryID res2 = jedis.xadd(\"race:france\",new HashMap<String,String>(){{put(\"rider\",\"Norem\");put(\"speed\",\"28.8\");put(\"position\",\"3\");put(\"location_id\",\"1\");}} , XAddParams.xAddParams());\n\n    System.out.println(res2); // >>> 1701760582225-1\n\n    StreamEntryID res3 = jedis.xadd(\"race:france\",new HashMap<String,String>(){{put(\"rider\",\"Prickett\");put(\"speed\",\"29.7\");put(\"position\",\"2\");put(\"location_id\",\"1\");}} , XAddParams.xAddParams());\n\n    System.out.println(res3); // >>> 1701760582226-0\n    //STEP_END\n\n    //REMOVE_START\n    assertEquals(jedis.xlen(\"race:france\"),3);\n    //REMOVE_END\n\n    //STEP_START xrange\n    List<StreamEntry> res4 = jedis.xrange(\"race:france\",\"1701760582225-0\",\"+\",2);\n\n    System.out.println(res4); // >>> [1701760841292-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701760841292-1 {rider=Norem, speed=28.8, location_id=1, position=3}]\n    //STEP_END\n\n    //STEP_START xread_block\n    List<Map.Entry<String, List<StreamEntry>>> res5= jedis.xread(XReadParams.xReadParams().block(300).count(100),new HashMap<String,StreamEntryID>(){{put(\"race:france\",new StreamEntryID());}});\n    System.out.println(\n      res5\n    ); // >>> [race:france=[1701761996660-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701761996661-0 {rider=Norem, speed=28.8, location_id=1, position=3}, 1701761996661-1 {rider=Prickett, speed=29.7, location_id=1, position=2}]]\n    //STEP_END\n\n    //STEP_START xadd_2\n    StreamEntryID res6 = jedis.xadd(\"race:france\",new HashMap<String,String>(){{put(\"rider\",\"Castilla\");put(\"speed\",\"29.9\");put(\"position\",\"2\");put(\"location_id\",\"1\");}} , XAddParams.xAddParams());\n    System.out.println(res6); // >>> 1701762285679-0\n    //STEP_END\n\n    //STEP_START xlen\n    long res7 = jedis.xlen(\"race:france\");\n    System.out.println(res7); // >>> 4\n    //STEP_END\n\n    //STEP_START xadd_id\n    StreamEntryID res8 = jedis.xadd(\"race:usa\", new HashMap<String,String>(){{put(\"racer\",\"Castilla\");}},XAddParams.xAddParams().id(\"0-1\"));\n    System.out.println(res8); // >>> 0-1\n\n    StreamEntryID res9 = jedis.xadd(\"race:usa\", new HashMap<String,String>(){{put(\"racer\",\"Norem\");}},XAddParams.xAddParams().id(\"0-2\"));\n    System.out.println(res9); // >>> 0-2\n    //STEP_END\n\n    //STEP_START xadd_bad_id\n    try {\n      StreamEntryID res10 = jedis.xadd(\"race:usa\", new HashMap<String,String>(){{put(\"racer\",\"Prickett\");}},XAddParams.xAddParams().id(\"0-1\"));\n      System.out.println(res10); // >>> 0-1\n    }\n    catch (JedisDataException e){\n      System.out.println(e); // >>> ERR The ID specified in XADD is equal or smaller than the target stream top item\n    }\n    //STEP_END\n\n    //STEP_START xadd_7\n    StreamEntryID res11 = jedis.xadd(\"race:usa\", new HashMap<String,String>(){{put(\"racer\",\"Norem\");}},XAddParams.xAddParams().id(\"0-*\"));\n    System.out.println(res11);\n    //STEP_END\n\n    //STEP_START xrange_all\n    List<StreamEntry> res12 = jedis.xrange(\"race:france\",\"-\",\"+\");\n    System.out.println(\n      res12\n    ); // >>> [1701764734160-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701764734160-1 {rider=Norem, speed=28.8, location_id=1, position=3}, 1701764734161-0 {rider=Prickett, speed=29.7, location_id=1, position=2}, 1701764734162-0 {rider=Castilla, speed=29.9, location_id=1, position=2}]\n    //STEP_END\n\n    //STEP_START xrange_time\n    List<StreamEntry> res13 = jedis.xrange(\"race:france\",String.valueOf(System.currentTimeMillis()-1000),String.valueOf(System.currentTimeMillis()+1000));\n    System.out.println(\n      res13\n    ); // >>> [1701764734160-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701764734160-1 {rider=Norem, speed=28.8, location_id=1, position=3}, 1701764734161-0 {rider=Prickett, speed=29.7, location_id=1, position=2}, 1701764734162-0 {rider=Castilla, speed=29.9, location_id=1, position=2}]\n    //STEP_END\n\n    //STEP_START xrange_step_1\n    List<StreamEntry> res14 = jedis.xrange(\"race:france\",\"-\",\"+\",2);\n    System.out.println(res14); // >>> [1701764887638-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701764887638-1 {rider=Norem, speed=28.8, location_id=1, position=3}]\n    //STEP_END\n\n    //STEP_START xrange_step_2\n    List<StreamEntry> res15 = jedis.xrange(\"race:france\",String.valueOf(System.currentTimeMillis()-1000)+\"-0\",\"+\",2);\n    System.out.println(res15); // >>> [1701764887638-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701764887638-1 {rider=Norem, speed=28.8, location_id=1, position=3}]\n    //STEP_END\n\n    //STEP_START xrange_empty\n    List<StreamEntry> res16 = jedis.xrange(\"race:france\",String.valueOf(System.currentTimeMillis()+1000)+\"-0\",\"+\",2);\n    System.out.println(res16); // >>> []\n    // STEP_END\n\n    //STEP_START xrevrange\n    List<StreamEntry> res17 = jedis.xrevrange(\"race:france\",\"+\",\"-\",1);\n    System.out.println(res17); // >>> [1701765218592-0 {rider=Castilla, speed=29.9, location_id=1, position=2}]\n    //STEP_END\n\n    //STEP_START xread\n    List<Map.Entry<String, List<StreamEntry>>> res18= jedis.xread(XReadParams.xReadParams().count(2),new HashMap<String,StreamEntryID>(){{put(\"race:france\",new StreamEntryID());}});\n    System.out.println(\n      res18\n    ); // >>> [race:france=[1701765384638-0 {rider=Castilla, speed=30.2, location_id=1, position=1}, 1701765384638-1 {rider=Norem, speed=28.8, location_id=1, position=3}]]\n    //STEP_END\n\n    //STEP_START xgroup_create\n    String res19 = jedis.xgroupCreate(\"race:france\",\"france_riders\",StreamEntryID.LAST_ENTRY,false);\n    System.out.println(res19); // >>> OK\n    //STEP_END\n\n    //STEP_START xgroup_create_mkstream\n    String res20 = jedis.xgroupCreate(\"race:italy\",\"italy_riders\",StreamEntryID.LAST_ENTRY,true);\n    System.out.println(res20); // >>> OK\n    //STEP_END\n\n    //STEP_START xgroup_read\n    StreamEntryID id1 = jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Castilaa\");}},XAddParams.xAddParams());\n    StreamEntryID id2 = jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Royce\");}},XAddParams.xAddParams());\n    StreamEntryID id3 = jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Sam-Bodden\");}},XAddParams.xAddParams());\n    StreamEntryID id4 = jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Prickett\");}},XAddParams.xAddParams());\n    StreamEntryID id5 = jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Norem\");}},XAddParams.xAddParams());\n\n    List<Map.Entry<String, List<StreamEntry>>> res21 = jedis.xreadGroup(\"italy_riders\",\"Alice\", XReadGroupParams.xReadGroupParams().count(1),new HashMap<String,StreamEntryID>(){{put(\"race:italy\",StreamEntryID.UNRECEIVED_ENTRY);}});\n    System.out.println(res21); // >>> [race:italy=[1701766299006-0 {rider=Castilaa}]]\n    //STEP_END\n\n    //STEP_START xgroup_read_id\n    List<Map.Entry<String, List<StreamEntry>>> res22 = jedis.xreadGroup(\"italy_riders\",\"Alice\", XReadGroupParams.xReadGroupParams().count(1),new HashMap<String,StreamEntryID>(){{put(\"race:italy\",new StreamEntryID());}});\n    System.out.println(res22); // >>> [race:italy=[1701766299006-0 {rider=Castilaa}]]\n    //STEP_END\n\n    //STEP_START xack\n    long res23 = jedis.xack(\"race:italy\",\"italy_riders\",id1);\n    System.out.println(res23); // >>> 1\n\n    List<Map.Entry<String, List<StreamEntry>>> res24 = jedis.xreadGroup(\"italy_riders\",\"Alice\", XReadGroupParams.xReadGroupParams().count(1),new HashMap<String,StreamEntryID>(){{put(\"race:italy\",new StreamEntryID());}});\n    System.out.println(res24); // >>> [race:italy=[]]\n    //STEP_END\n\n    //STEP_START xgroup_read_bob\n    List<Map.Entry<String, List<StreamEntry>>> res25 = jedis.xreadGroup(\"italy_riders\",\"Bob\", XReadGroupParams.xReadGroupParams().count(2),new HashMap<String,StreamEntryID>(){{put(\"race:italy\",StreamEntryID.UNRECEIVED_ENTRY);}});\n    System.out.println(res25); // >>> [race:italy=[1701767632261-1 {rider=Royce}, 1701767632262-0 {rider=Sam-Bodden}]]\n    //STEP_END\n\n    //STEP_START xpending\n    StreamPendingSummary res26 = jedis.xpending(\"race:italy\",\"italy_riders\");\n    System.out.println(res26.getConsumerMessageCount()); // >>> {Bob=2}\n    //STEP_END\n\n    //STEP_START xpending_plus_minus\n    List<StreamPendingEntry> res27 = jedis.xpending(\"race:italy\",\"italy_riders\",XPendingParams.xPendingParams().start(StreamEntryID.MINIMUM_ID).end(StreamEntryID.MAXIMUM_ID).count(10));\n    System.out.println(res27); // >>> [1701768567412-1 Bob idle:0 times:1, 1701768567412-2 Bob idle:0 times:1]\n    //STEP_END\n\n    //STEP_START xrange_pending\n    List<StreamEntry> res28 = jedis.xrange(\"race:italy\",id2.toString(),id2.toString());\n    System.out.println(res28); // >>> [1701768744819-1 {rider=Royce}]\n    //STEP_END\n\n    //STEP_START xclaim\n    List<StreamEntry> res29 = jedis.xclaim(\"race:italy\",\"italy_riders\",\"Alice\", 0L, XClaimParams.xClaimParams().time(60000),id2);\n    System.out.println(res29); // >>> [1701769004195-1 {rider=Royce}]\n    //STEP_END\n\n    //STEP_START xautoclaim\n    Map.Entry<StreamEntryID, List<StreamEntry>> res30 = jedis.xautoclaim(\"race:italy\",\"italy_riders\",\"Alice\",1L,new StreamEntryID(\"0-0\"),XAutoClaimParams.xAutoClaimParams().count(1));\n    System.out.println(res30); // >>> [1701769266831-2=[1701769266831-1 {rider=Royce}]\n    //STEP_END\n\n    //STEP_START xautoclaim_cursor\n    Map.Entry<StreamEntryID, List<StreamEntry>> res31 = jedis.xautoclaim(\"race:italy\",\"italy_riders\",\"Alice\",1L,new StreamEntryID(id2.toString()),XAutoClaimParams.xAutoClaimParams().count(1));\n    System.out.println(res31); // >>> [0-0=[1701769605847-2 {rider=Sam-Bodden}]\n    //STEP_END\n\n    //STEP_START xinfo\n    StreamInfo res32 = jedis.xinfoStream(\"race:italy\");\n    System.out.println(\n      res32.getStreamInfo()\n    ); // >>> {radix-tree-keys=1, radix-tree-nodes=2, entries-added=5, length=5, groups=1, max-deleted-entry-id=0-0, first-entry=1701769637612-0 {rider=Castilaa}, last-generated-id=1701769637612-4, last-entry=1701769637612-4 {rider=Norem}, recorded-first-entry-id=1701769637612-0}\n    //STEP_END\n\n    //STEP_START xinfo_groups\n    List<StreamGroupInfo> res33 = jedis.xinfoGroups(\"race:italy\");\n    for (StreamGroupInfo a : res33){\n      System.out.println(\n        a.getGroupInfo()\n      ); // >>> {last-delivered-id=1701770253659-0, lag=2, pending=2, name=italy_riders, consumers=2, entries-read=3}\n    }\n    //STEP_END\n\n    //STEP_START xinfo_consumers\n    List<StreamConsumersInfo> res34 = jedis.xinfoConsumers(\"race:italy\",\"italy_riders\");\n    for (StreamConsumerInfo a : res34){\n      System.out.println(\n        a.getConsumerInfo()\n      ); // {inactive=1, idle=1, pending=1, name=Alice} , {inactive=3, idle=3, pending=1, name=Bob}\n    }\n    //STEP_END\n\n    //STEP_START maxlen\n    jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Jones\");}},XAddParams.xAddParams().maxLen(10));\n    jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Wood\");}},XAddParams.xAddParams().maxLen(10));\n    jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Henshaw\");}},XAddParams.xAddParams().maxLen(10));\n    long res35 = jedis.xlen(\"race:italy\");\n    System.out.println(res35); // >>> 8\n\n    List<StreamEntry> res36 = jedis.xrange(\"race:italy\",\"-\",\"+\");\n    System.out.println(res36); // >>> [1701771219852-0 {rider=Castilaa}, 1701771219852-1 {rider=Royce}, 1701771219853-0 {rider=Sam-Bodden}, 1701771219853-1 {rider=Prickett}, 1701771219853-2 {rider=Norem}, 1701771219858-0 {rider=Jones}, 1701771219858-1 {rider=Wood}, 1701771219859-0 {rider=Henshaw}]\n\n    StreamEntryID id6 = jedis.xadd(\"race:italy\", new HashMap<String,String>(){{put(\"rider\",\"Smith\");}},XAddParams.xAddParams().maxLen(2));\n\n    List<StreamEntry> res37 = jedis.xrange(\"race:italy\",\"-\",\"+\");\n    System.out.println(res37); // >>> [1701771067332-1 {rider=Henshaw}, 1701771067332-2 {rider=Smith}]\n    //STEP_END\n\n    //STEP_START xtrim\n    long res38 = jedis.xtrim(\"race:italy\",XTrimParams.xTrimParams().maxLen(10).exactTrimming());\n    System.out.println(res38); /// >>> 0\n    //STEP_END\n\n    //STEP_START xtrim2\n    long res39 = jedis.xtrim(\"race:italy\",XTrimParams.xTrimParams().maxLen(10));\n    System.out.println(res39); /// >>> 0\n    //STEP_END\n\n    //STEP_START xdel\n    List<StreamEntry> res40 = jedis.xrange(\"race:italy\",\"-\",\"+\");\n    System.out.println(res40); // >>> [1701771356428-2 {rider=Henshaw}, 1701771356429-0 {rider=Smith}]\n\n    long res41 = jedis.xdel(\"race:italy\",id6);\n    System.out.println(res41); // >>> 1\n\n    List<StreamEntry> res42 = jedis.xrange(\"race:italy\",\"-\",\"+\");\n    System.out.println(res42); // >>> [1701771517639-1 {rider=Henshaw}]\n    //STEP_END\n\n    //HIDE_START\n    jedis.close();\n    //HIDE_END\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/StringExample.java",
    "content": "// EXAMPLE: set_tutorial\npackage io.redis.examples;\n\n//REMOVE_START\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport org.junit.jupiter.api.Test;\n//REMOVE_END\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.params.SetParams;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class StringExample {\n\n  @Test\n  public void run() {\n    try (RedisClient jedis = RedisClient.create(\"redis://localhost:6379\")) {\n\n      // STEP_START set_get\n      String res1 = jedis.set(\"bike:1\", \"Deimos\");\n      System.out.println(res1); // OK\n      String res2 = jedis.get(\"bike:1\");\n      System.out.println(res2); // Deimos\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(\"OK\", res1);\n      assertEquals(\"Deimos\", res2);\n      // REMOVE_END\n\n      // STEP_START setnx_xx\n      Long res3 = jedis.setnx(\"bike:1\", \"bike\");\n      System.out.println(res3); // 0 (because key already exists)\n      System.out.println(jedis.get(\"bike:1\")); // Deimos (value is unchanged)\n      String res4 = jedis.set(\"bike:1\", \"bike\", SetParams.setParams().xx()); // set the value to \"bike\" if it\n      // already\n      // exists\n      System.out.println(res4); // OK\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(0L, res3.longValue());\n      assertEquals(\"OK\", res4);\n      // REMOVE_END\n\n      // STEP_START mset\n      String res5 = jedis.mset(\"bike:1\", \"Deimos\", \"bike:2\", \"Ares\", \"bike:3\", \"Vanth\");\n      System.out.println(res5); // OK\n      List<String> res6 = jedis.mget(\"bike:1\", \"bike:2\", \"bike:3\");\n      System.out.println(res6); // [Deimos, Ares, Vanth]\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(\"OK\", res5);\n      List<String> expected = new ArrayList<>(Arrays.asList(\"Deimos\", \"Ares\", \"Vanth\"));\n      assertEquals(expected, res6);\n      // REMOVE_END\n\n      // STEP_START incr\n      jedis.set(\"total_crashes\", \"0\");\n      Long res7 = jedis.incr(\"total_crashes\");\n      System.out.println(res7); // 1\n      Long res8 = jedis.incrBy(\"total_crashes\", 10);\n      System.out.println(res8); // 11\n      // STEP_END\n\n      // REMOVE_START\n      assertEquals(1L, res7.longValue());\n      assertEquals(11L, res8.longValue());\n      // REMOVE_END\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/TDigestExample.java",
    "content": "//EXAMPLE: tdigest_tutorial\n//HIDE_START\npackage io.redis.examples;\n//HIDE_END\n\n//REMOVE_START\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n//REMOVE_END\n\npublic class TDigestExample {\n\n    @Test\n    public void run(){\n        //HIDE_START\n        RedisClient jedis = RedisClient.create(\"redis://127.0.0.1:6379\");\n        //HIDE_END\n\n        //REMOVE_START\n        jedis.del(\"racer_ages\");\n        jedis.del(\"bikes:sales\");\n        //REMOVE_END\n\n        //STEP_START tdig_start\n        String res1 = jedis.tdigestCreate(\"bikes:sales\", 100);\n        System.out.println(res1); // >>> True\n\n        String res2 = jedis.tdigestAdd(\"bikes:sales\", 21);\n        System.out.println(res2); // >>> OK\n\n        String res3 = jedis.tdigestAdd(\"bikes:sales\", 150, 95, 75, 34);\n        System.out.println(res3); // >>> OK\n        //STEP_END\n\n        //REMOVE_START\n        assertEquals(\"OK\",\"OK\");\n        //REMOVE_END\n\n        //STEP_START tdig_cdf\n        String res4 = jedis.tdigestCreate(\"racer_ages\");\n        System.out.println(res4); // >>> True\n\n        String res5 = jedis.tdigestAdd(\"racer_ages\", 45.88,\n                44.2,\n                58.03,\n                19.76,\n                39.84,\n                69.28,\n                50.97,\n                25.41,\n                19.27,\n                85.71,\n                42.63);\n        System.out.println(res5); // >>> OK\n\n        List<Long> res6 = jedis.tdigestRank(\"racer_ages\", 50);\n        System.out.println(res6); // >>> [7]\n\n        List<Long> res7 = jedis.tdigestRank(\"racer_ages\", 50, 40);\n        System.out.println(res7); // >>> [7, 4]\n        //STEP_END\n\n        //STEP_START tdig_quant\n        List<Double> res8 = jedis.tdigestQuantile(\"racer_ages\", 0.5);\n        System.out.println(res8); // >>> [44.2]\n\n        List<Double> res9 = jedis.tdigestByRank(\"racer_ages\", 4);\n        System.out.println(res9); // >>> [42.63]\n        //STEP_END\n\n        //STEP_START tdig_min\n        double res10 = jedis.tdigestMin(\"racer_ages\");\n        System.out.println(res10); // >>> 19.27\n\n        double res11 = jedis.tdigestMax(\"racer_ages\");\n        System.out.println(res11); // >>> 85.71\n        //STEP_END\n\n        //STEP_START tdig_reset\n        String res12 = jedis.tdigestReset(\"racer_ages\");\n        System.out.println(res12); // >>> OK\n        //STEP_END\n\n        //HIDE_START\n        jedis.close();\n        //HIDE_END\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/TimeSeriesTutorialExample.java",
    "content": "// EXAMPLE: time_series_tutorial\n// REMOVE_START\npackage io.redis.examples;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport org.junit.jupiter.api.Test;\n// REMOVE_END\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.timeseries.*;\nimport redis.clients.jedis.timeseries.TSElement;\n\nimport java.util.*;\n\npublic class TimeSeriesTutorialExample {\n\n    @Test\n    public void run() {\n        RedisClient jedis = RedisClient.create(\"redis://localhost:6379\");\n        // REMOVE_START\n        // Clear any keys before using them in tests\n        jedis.del(\n            \"thermometer:1\", \"thermometer:2\", \"thermometer:3\",\n            \"rg:1\", \"rg:2\", \"rg:3\", \"rg:4\",\n            \"sensor3\",\n            \"wind:1\", \"wind:2\", \"wind:3\", \"wind:4\",\n            \"hyg:1\", \"hyg:compacted\"\n        );\n        // REMOVE_END\n\n        // STEP_START create\n        String res1 = jedis.tsCreate(\"thermometer:1\");\n        System.out.println(res1); // >>> OK\n\n        String res2 = jedis.type(\"thermometer:1\");\n        System.out.println(res2); // >>> TSDB-TYPE\n\n        TSInfo res3 = jedis.tsInfo(\"thermometer:1\");\n        System.out.println(res3.getProperty(\"totalSamples\")); // >>> 0\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res1);\n        assertEquals(\"TSDB-TYPE\", res2);\n        assertEquals((Long) 0L, res3.getProperty(\"totalSamples\"));\n        // REMOVE_END\n\n        // STEP_START create_retention\n        long res4 = jedis.tsAdd(\"thermometer:2\", 1L, 10.8, \n            TSCreateParams.createParams().retention(100));\n        System.out.println(res4); // >>> 1\n\n        TSInfo res5 = jedis.tsInfo(\"thermometer:2\");\n        System.out.println(res5.getProperty(\"retentionTime\")); // >>> 100\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1L, res4);\n        assertEquals((Long) 100L, res5.getProperty(\"retentionTime\"));\n        // REMOVE_END\n\n        // STEP_START create_labels\n        Map<String, String> labels = new HashMap<>();\n        labels.put(\"location\", \"UK\");\n        labels.put(\"type\", \"Mercury\");\n        \n        long res6 = jedis.tsAdd(\"thermometer:3\", 1L, 10.4,\n            TSCreateParams.createParams().labels(labels));\n        System.out.println(res6); // >>> 1\n\n        TSInfo res7 = jedis.tsInfo(\"thermometer:3\");\n        System.out.println(\"Labels: \" + res7.getLabels());\n        // >>> Labels: {location=UK, type=Mercury}\n        // STEP_END\n        // REMOVE_START\n        assertEquals(1L, res6);\n        assertEquals(labels, res7.getLabels());\n        // REMOVE_END\n\n        // STEP_START madd\n        List<Long> res8 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"thermometer:1\", new TSElement(1L, 9.2)),\n            new AbstractMap.SimpleEntry<>(\"thermometer:1\", new TSElement(2L, 9.9)),\n            new AbstractMap.SimpleEntry<>(\"thermometer:2\", new TSElement(2L, 10.3))\n        );\n        System.out.println(res8); // >>> [1, 2, 2]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(Arrays.asList(1L, 2L, 2L), res8);\n        // REMOVE_END\n\n        // STEP_START get\n        // The last recorded temperature for thermometer:2\n        // was 10.3 at time 2.\n        TSElement res9 = jedis.tsGet(\"thermometer:2\");\n        System.out.println(\"(\" + res9.getTimestamp() + \", \" + res9.getValue() + \")\");\n        // >>> (2, 10.3)\n        // STEP_END\n        // REMOVE_START\n        assertEquals(2L, res9.getTimestamp());\n        assertEquals(10.3, res9.getValue(), 0.001);\n        // REMOVE_END\n\n        // STEP_START range\n        // Add 5 data points to a time series named \"rg:1\"\n        String res10 = jedis.tsCreate(\"rg:1\");\n        System.out.println(res10); // >>> OK\n\n        List<Long> res11 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"rg:1\", new TSElement(0L, 18.0)),\n            new AbstractMap.SimpleEntry<>(\"rg:1\", new TSElement(1L, 14.0)),\n            new AbstractMap.SimpleEntry<>(\"rg:1\", new TSElement(2L, 22.0)),\n            new AbstractMap.SimpleEntry<>(\"rg:1\", new TSElement(3L, 18.0)),\n            new AbstractMap.SimpleEntry<>(\"rg:1\", new TSElement(4L, 24.0))\n        );\n        System.out.println(res11); // >>> [0, 1, 2, 3, 4]\n\n        // Retrieve all the data points in ascending order\n        List<TSElement> res12 = jedis.tsRange(\"rg:1\", 0L, 4L);\n        System.out.println(res12);\n        // >>> [(0:18.0), (1:14.0), (2:22.0), (3:18.0), (4:24.0)]\n\n        // Retrieve data points up to time 1 (inclusive)\n        List<TSElement> res13 = jedis.tsRange(\"rg:1\", 0L, 1L);\n        System.out.println(res13);\n        // >>> [(0:18.0), (1:14.0)]\n\n        // Retrieve data points from time 3 onwards\n        List<TSElement> res14 = jedis.tsRange(\"rg:1\", 3L, 4L);\n        System.out.println(res14);\n        // >>> [(3:18.0), (4:24.0)]\n\n        // Retrieve all the data points in descending order\n        List<TSElement> res15 = jedis.tsRevRange(\"rg:1\", 0L, 4L);\n        System.out.println(res15);\n        // >>> [(4:24.0), (3:18.0), (2:22.0), (1:14.0), (0:18.0)]\n\n        // Retrieve data points up to time 1 (inclusive), in descending order\n        List<TSElement> res16 = jedis.tsRevRange(\"rg:1\", 0L, 1L);\n        System.out.println(res16);\n        // >>> [(1:14.0), (0:18.0)]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res10);\n        assertEquals(Arrays.asList(0L, 1L, 2L, 3L, 4L), res11);\n        assertEquals(Arrays.asList(\n            new TSElement(0L, 18.0), new TSElement(1L, 14.0), new TSElement(2L, 22.0), \n            new TSElement(3L, 18.0), new TSElement(4L, 24.0)), res12);\n        assertEquals(Arrays.asList(new TSElement(0L, 18.0), new TSElement(1L, 14.0)), res13);\n        assertEquals(Arrays.asList(new TSElement(3L, 18.0), new TSElement(4L, 24.0)), res14);\n        assertEquals(Arrays.asList(\n            new TSElement(4L, 24.0), new TSElement(3L, 18.0), new TSElement(2L, 22.0), \n            new TSElement(1L, 14.0), new TSElement(0L, 18.0)), res15);\n        assertEquals(Arrays.asList(new TSElement(1L, 14.0), new TSElement(0L, 18.0)), res16);\n        // REMOVE_END\n\n        // STEP_START range_filter\n        List<TSElement> res17 = jedis.tsRange(\"rg:1\",  \n            TSRangeParams.rangeParams()\n                .fromTimestamp(0L)\n                .toTimestamp(4L)\n                .filterByTS(0L, 2L, 4L)\n        );\n        System.out.println(res17);\n        // >>> [(0:18.0), (2:22.0), (4:24.0)]\n\n        List<TSElement> res18 = jedis.tsRevRange(\"rg:1\",\n            TSRangeParams.rangeParams()\n                .fromTimestamp(0L)\n                .toTimestamp(4L)\n                .filterByTS(0L, 2L, 4L)\n                .filterByValues(20.0, 25.0)\n        );\n        System.out.println(res18);\n        // >>> [(4:24.0), (2:22.0)]\n\n        List<TSElement> res19 = jedis.tsRevRange(\"rg:1\",\n            TSRangeParams.rangeParams()\n                .fromTimestamp(0L)\n                .toTimestamp(4L)\n                .filterByTS(0L, 2L, 4L)\n                .filterByValues(22.0, 22.0)\n                .count(1)\n        );\n        System.out.println(res19);\n        // >>> [(2:22.0)]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(Arrays.asList(\n            new TSElement(0L, 18.0), new TSElement(2L, 22.0), new TSElement(4L, 24.0)), res17);\n        assertEquals(Arrays.asList(new TSElement(4L, 24.0), new TSElement(2L, 22.0)), res18);\n        assertEquals(Arrays.asList(new TSElement(2L, 22.0)), res19);\n        // REMOVE_END\n\n        // STEP_START query_multi\n        // Create three new \"rg:\" time series (two in the US\n        // and one in the UK, with different units) and add some\n        // data points.\n        Map<String, String> usLabels1 = new HashMap<>();\n        usLabels1.put(\"location\", \"us\");\n        usLabels1.put(\"unit\", \"cm\");\n        \n        Map<String, String> usLabels2 = new HashMap<>();\n        usLabels2.put(\"location\", \"us\");\n        usLabels2.put(\"unit\", \"in\");\n        \n        Map<String, String> ukLabels = new HashMap<>();\n        ukLabels.put(\"location\", \"uk\");\n        ukLabels.put(\"unit\", \"mm\");\n\n        String res20 = jedis.tsCreate(\"rg:2\",\n            TSCreateParams.createParams().labels(usLabels1));\n        System.out.println(res20); // >>> OK\n\n        String res21 = jedis.tsCreate(\"rg:3\",\n            TSCreateParams.createParams().labels(usLabels2));\n        System.out.println(res21); // >>> OK\n\n        String res22 = jedis.tsCreate(\"rg:4\",\n            TSCreateParams.createParams().labels(ukLabels));\n        System.out.println(res22); // >>> OK\n\n        List<Long> res23 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"rg:2\", new TSElement(0L, 1.8)),\n            new AbstractMap.SimpleEntry<>(\"rg:3\", new TSElement(0L, 0.9)),\n            new AbstractMap.SimpleEntry<>(\"rg:4\", new TSElement(0L, 25.0))\n        );\n        System.out.println(res23); // >>> [0, 0, 0]\n\n        List<Long> res24 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"rg:2\", new TSElement(1L, 2.1)),\n            new AbstractMap.SimpleEntry<>(\"rg:3\", new TSElement(1L, 0.77)),\n            new AbstractMap.SimpleEntry<>(\"rg:4\", new TSElement(1L, 18.0))\n        );\n        System.out.println(res24); // >>> [1, 1, 1]\n\n        List<Long> res25 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"rg:2\", new TSElement(2L, 2.3)),\n            new AbstractMap.SimpleEntry<>(\"rg:3\", new TSElement(2L, 1.1)),\n            new AbstractMap.SimpleEntry<>(\"rg:4\", new TSElement(2L, 21.0))\n        );\n        System.out.println(res25); // >>> [2, 2, 2]\n        \n        List<Long> res26 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"rg:2\", new TSElement(3L, 1.9)),\n            new AbstractMap.SimpleEntry<>(\"rg:3\", new TSElement(3L, 0.81)),\n            new AbstractMap.SimpleEntry<>(\"rg:4\", new TSElement(3L, 19.0))\n        );\n        System.out.println(res26); // >>> [3, 3, 3]\n\n        List<Long> res27 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"rg:2\", new TSElement(4L, 1.78)),\n            new AbstractMap.SimpleEntry<>(\"rg:3\", new TSElement(4L, 0.74)),\n            new AbstractMap.SimpleEntry<>(\"rg:4\", new TSElement(4L, 23.0))\n        );\n        System.out.println(res27); // >>> [4, 4, 4]\n\n        // Retrieve the last data point from each US time series.\n        Map<String, TSMGetElement> res28 = jedis.tsMGet(\n            TSMGetParams.multiGetParams().latest(),\n             \"location=us\"\n        );\n        System.out.println(res28);\n        // >>> {rg:2=TSMGetElement{key=rg:2, labels={}, element=(4:1.78)}...\n\n        // Retrieve the same data points, but include the `unit`\n        // label in the results.\n        Map<String, TSMGetElement> res29 = jedis.tsMGet(\n            TSMGetParams.multiGetParams().selectedLabels(\"unit\"), \n            \"location=us\"\n        );\n        System.out.println(res29);\n        // >>> {rg:2=TSMGetElement{key=rg:2, labels={unit=cm}, element=(4:1.78)}...\n\n        // Retrieve data points up to time 2 (inclusive) from all\n        // time series that use millimeters as the unit. Include all\n        // labels in the results.\n        Map<String, TSMRangeElements> res30 = jedis.tsMRange(\n            TSMRangeParams.multiRangeParams(0L, 2L)\n                .withLabels()\n                .filter(\"unit=mm\")\n        );\n        System.out.println(res30);\n        // >>> {rg:4=TSMRangeElements{key=rg:4, labels={location=uk, unit=mm}, value=[(0:25.0), (1:18.0), (2:21.0)]}}\n\n        // Retrieve data points from time 1 to time 3 (inclusive) from\n        // all time series that use centimeters or millimeters as the unit,\n        // but only return the `location` label. Return the results\n        // in descending order of timestamp.\n        Map<String, TSMRangeElements> res31 = jedis.tsMRevRange(\n            TSMRangeParams.multiRangeParams(1L, 3L)\n                .selectedLabels(\"location\")\n                .filter(\"unit=(cm,mm)\")\n        );\n        System.out.println(res31);\n        // >>> {rg:2=TSMRangeElements{key=rg:2, labels={location=us, unit=cm}, value=[(1:2.1)...\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res20);\n        assertEquals(\"OK\", res21);\n        assertEquals(\"OK\", res22);\n        assertEquals(Arrays.asList(0L, 0L, 0L), res23);\n        assertEquals(Arrays.asList(1L, 1L, 1L), res24);\n        assertEquals(Arrays.asList(2L, 2L, 2L), res25);\n        assertEquals(Arrays.asList(3L, 3L, 3L), res26);\n        assertEquals(Arrays.asList(4L, 4L, 4L), res27);\n        assertEquals(2, res28.size());\n\n        assertTrue(res28.containsKey(\"rg:2\"));\n        TSMGetElement res28rg2 = res28.get(\"rg:2\");\n        assertEquals(\"rg:2\", res28rg2.getKey());\n        assertEquals(0, res28rg2.getLabels().size());\n        assertEquals(4L, res28rg2.getElement().getTimestamp());\n        assertEquals(1.78, res28rg2.getElement().getValue(), 0.001);\n\n        assertTrue(res28.containsKey(\"rg:3\"));\n        TSMGetElement res28rg3 = res28.get(\"rg:3\");\n        assertEquals(\"rg:3\", res28rg3.getKey());\n        assertEquals(0, res28rg3.getLabels().size());\n        assertEquals(4L, res28rg3.getElement().getTimestamp());\n        assertEquals(0.74, res28rg3.getElement().getValue(), 0.001);\n        \n        assertEquals(2, res29.size());\n        assertTrue(res29.containsKey(\"rg:2\"));\n        TSMGetElement res29rg2 = res29.get(\"rg:2\");\n        assertEquals(\"rg:2\", res29rg2.getKey());\n        assertEquals(1, res29rg2.getLabels().size());\n        assertEquals(\"cm\", res29rg2.getLabels().get(\"unit\"));\n        assertEquals(4L, res29rg2.getElement().getTimestamp());\n        assertEquals(1.78, res29rg2.getElement().getValue(), 0.001);\n\n        assertEquals(1, res30.size());\n        assertTrue(res30.containsKey(\"rg:4\"));\n        TSMRangeElements res30rg4 = res30.get(\"rg:4\");\n        assertEquals(\"rg:4\", res30rg4.getKey());\n        assertEquals(2, res30rg4.getLabels().size());\n        assertEquals(\"uk\", res30rg4.getLabels().get(\"location\"));\n        assertEquals(\"mm\", res30rg4.getLabels().get(\"unit\"));\n        assertEquals(3, res30rg4.getElements().size());\n        assertEquals(0L, res30rg4.getElements().get(0).getTimestamp());\n        assertEquals(25.0, res30rg4.getElements().get(0).getValue(), 0.001);\n        assertEquals(1L, res30rg4.getElements().get(1).getTimestamp());\n        assertEquals(18.0, res30rg4.getElements().get(1).getValue(), 0.001);\n        assertEquals(2L, res30rg4.getElements().get(2).getTimestamp());\n        assertEquals(21.0, res30rg4.getElements().get(2).getValue(), 0.001);\n\n        assertEquals(2, res31.size());\n        assertTrue(res31.containsKey(\"rg:2\"));\n        TSMRangeElements res31rg2 = res31.get(\"rg:2\");\n        assertEquals(\"rg:2\", res31rg2.getKey());\n        assertEquals(1, res31rg2.getLabels().size());\n        assertEquals(\"us\", res31rg2.getLabels().get(\"location\"));\n        assertEquals(3, res31rg2.getElements().size());\n        assertEquals(3L, res31rg2.getElements().get(0).getTimestamp());\n        assertEquals(1.9, res31rg2.getElements().get(0).getValue(), 0.001);\n        assertEquals(2L, res31rg2.getElements().get(1).getTimestamp());\n        assertEquals(2.3, res31rg2.getElements().get(1).getValue(), 0.001);\n        assertEquals(1L, res31rg2.getElements().get(2).getTimestamp());\n        assertEquals(2.1, res31rg2.getElements().get(2).getValue(), 0.001);\n        \n        // REMOVE_END\n\n        // STEP_START agg\n        List<TSElement> res32 = jedis.tsRange(\"rg:2\",\n            TSRangeParams.rangeParams()\n                .fromTimestamp(0L)\n                .toTimestamp(4L)\n                .aggregation(AggregationType.AVG, 2)\n        );\n        System.out.println(res32);\n        // >>> [(0:1.9500000000000002), (2:2.0999999999999996), (4:1.78)]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\n            Arrays.asList(\n                new TSElement(0L, 1.9500000000000002),\n                new TSElement(2L, 2.0999999999999996),\n                new TSElement(4L, 1.78)\n            ),\n            res32\n        );\n        // REMOVE_END\n\n        // STEP_START agg_bucket\n        String res33 = jedis.tsCreate(\"sensor3\");\n        System.out.println(res33); // >>> OK\n\n        List<Long> res34 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(10L, 1000.0)),\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(20L, 2000.0)),\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(30L, 3000.0)),\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(40L, 4000.0)),\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(50L, 5000.0)),\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(60L, 6000.0)),\n            new AbstractMap.SimpleEntry<>(\"sensor3\", new TSElement(70L, 7000.0))\n        );\n        System.out.println(res34); // >>> [10, 20, 30, 40, 50, 60, 70]\n\n        List<TSElement> res35 = jedis.tsRange(\"sensor3\",\n            TSRangeParams.rangeParams()\n                .fromTimestamp(10L)\n                .toTimestamp(70L)\n                .aggregation(AggregationType.MIN, 25)\n        );\n        System.out.println(res35);\n        // >>> [(0:1000.0), (25:3000.0), (50:5000.0)]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res33);\n        assertEquals(Arrays.asList(10L, 20L, 30L, 40L, 50L, 60L, 70L), res34);\n        assertEquals(\n            Arrays.asList(\n                new TSElement(0L, 1000.0),\n                new TSElement(25L, 3000.0),\n                new TSElement(50L, 5000.0)\n            ),\n            res35\n        );\n        // REMOVE_END\n\n        // STEP_START agg_align\n        List<TSElement> res36 = jedis.tsRange(\"sensor3\",\n            TSRangeParams.rangeParams()\n                .fromTimestamp(10L)\n                .toTimestamp(70L)\n                .aggregation(AggregationType.MIN, 25)\n                .alignStart()\n        );\n        System.out.println(res36);\n        // >>> [(10:1000.0), (35:4000.0), (60:6000.0)]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\n            Arrays.asList(\n                new TSElement(10L, 1000.0),\n                new TSElement(35L, 4000.0),\n                new TSElement(60L, 6000.0)\n            ),\n            res36\n        );\n        // REMOVE_END\n\n        // STEP_START agg_multi\n        Map<String, String> ukCountry = new HashMap<>();\n        ukCountry.put(\"country\", \"uk\");\n        \n        Map<String, String> usCountry = new HashMap<>();\n        usCountry.put(\"country\", \"us\");\n\n        jedis.tsCreate(\"wind:1\", TSCreateParams.createParams().labels(ukCountry));\n        jedis.tsCreate(\"wind:2\", TSCreateParams.createParams().labels(ukCountry));\n        jedis.tsCreate(\"wind:3\", TSCreateParams.createParams().labels(usCountry));\n        jedis.tsCreate(\"wind:4\", TSCreateParams.createParams().labels(usCountry));\n\n        jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"wind:1\", new TSElement(0L, 10.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:2\", new TSElement(0L, 12.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:3\", new TSElement(0L, 8.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:4\", new TSElement(0L, 15.0))\n        );\n\n        jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"wind:1\", new TSElement(1L, 11.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:2\", new TSElement(1L, 13.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:3\", new TSElement(1L, 9.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:4\", new TSElement(1L, 16.0))\n        );\n\n        jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"wind:1\", new TSElement(2L, 9.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:2\", new TSElement(2L, 11.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:3\", new TSElement(2L, 7.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:4\", new TSElement(2L, 14.0))\n        );\n\n        jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"wind:1\", new TSElement(3L, 12.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:2\", new TSElement(3L, 14.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:3\", new TSElement(3L, 10.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:4\", new TSElement(3L, 17.0))\n        );\n\n        jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"wind:1\", new TSElement(4L, 8.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:2\", new TSElement(4L, 10.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:3\", new TSElement(4L, 6.0)),\n            new AbstractMap.SimpleEntry<>(\"wind:4\", new TSElement(4L, 13.0))\n        );\n\n        // Group by country with max reduction\n        Map<String, TSMRangeElements> res44 = jedis.tsMRange(\n            TSMRangeParams.multiRangeParams(0L, 4L)\n                .filter(\"country=(us,uk)\")\n                .groupBy(\"country\", \"max\"));\n        System.out.println(res44);\n        // >>> {country=uk=TSMRangeElements{key=country=uk, labels={}, value=[(0:12.0)...\n\n        // Group by country with avg reduction\n        Map<String, TSMRangeElements> res45 = jedis.tsMRange(\n            TSMRangeParams.multiRangeParams(0L, 4L)\n                .filter(\"country=(us,uk)\")\n                .groupBy(\"country\", \"avg\"));\n        System.out.println(res45);\n        // >>> {country=uk=TSMRangeElements{key=country=uk, labels={}, value=[(0:11.0)...\n        // STEP_END\n        // REMOVE_START\n        assertEquals(2, res44.size());\n        assertTrue(res44.containsKey(\"country=uk\"));\n        TSMRangeElements res44uk = res44.get(\"country=uk\");\n        assertEquals(\"country=uk\", res44uk.getKey());\n        assertEquals(0, res44uk.getLabels().size());\n        assertEquals(5, res44uk.getElements().size());\n        assertEquals(0L, res44uk.getElements().get(0).getTimestamp());\n        assertEquals(12.0, res44uk.getElements().get(0).getValue(), 0.001);\n        assertEquals(1L, res44uk.getElements().get(1).getTimestamp());\n        assertEquals(13.0, res44uk.getElements().get(1).getValue(), 0.001);\n        assertEquals(2L, res44uk.getElements().get(2).getTimestamp());\n        assertEquals(11.0, res44uk.getElements().get(2).getValue(), 0.001);\n        assertEquals(3L, res44uk.getElements().get(3).getTimestamp());\n        assertEquals(14.0, res44uk.getElements().get(3).getValue(), 0.001);\n        assertEquals(4L, res44uk.getElements().get(4).getTimestamp());\n        assertEquals(10.0, res44uk.getElements().get(4).getValue(), 0.001);\n\n        assertTrue(res44.containsKey(\"country=us\"));\n        TSMRangeElements res44us = res44.get(\"country=us\");\n        assertEquals(\"country=us\", res44us.getKey());\n        assertEquals(0, res44us.getLabels().size());\n        assertEquals(5, res44us.getElements().size());\n        assertEquals(0L, res44us.getElements().get(0).getTimestamp());\n        assertEquals(15.0, res44us.getElements().get(0).getValue(), 0.001);\n        assertEquals(1L, res44us.getElements().get(1).getTimestamp());\n        assertEquals(16.0, res44us.getElements().get(1).getValue(), 0.001);\n        assertEquals(2L, res44us.getElements().get(2).getTimestamp());\n        assertEquals(14.0, res44us.getElements().get(2).getValue(), 0.001);\n        assertEquals(3L, res44us.getElements().get(3).getTimestamp());\n        assertEquals(17.0, res44us.getElements().get(3).getValue(), 0.001);\n        assertEquals(4L, res44us.getElements().get(4).getTimestamp());\n        assertEquals(13.0, res44us.getElements().get(4).getValue(), 0.001);\n\n        assertEquals(2, res45.size());\n        assertTrue(res45.containsKey(\"country=uk\"));\n        TSMRangeElements res45uk = res45.get(\"country=uk\");\n        assertEquals(\"country=uk\", res45uk.getKey());\n        assertEquals(0, res45uk.getLabels().size());\n        assertEquals(5, res45uk.getElements().size());\n        assertEquals(0L, res45uk.getElements().get(0).getTimestamp());\n        assertEquals(11.0, res45uk.getElements().get(0).getValue(), 0.001);\n        assertEquals(1L, res45uk.getElements().get(1).getTimestamp());\n        assertEquals(12.0, res45uk.getElements().get(1).getValue(), 0.001);\n        assertEquals(2L, res45uk.getElements().get(2).getTimestamp());\n        assertEquals(10.0, res45uk.getElements().get(2).getValue(), 0.001);\n        assertEquals(3L, res45uk.getElements().get(3).getTimestamp());\n        assertEquals(13.0, res45uk.getElements().get(3).getValue(), 0.001);\n        assertEquals(4L, res45uk.getElements().get(4).getTimestamp());\n        assertEquals(9.0, res45uk.getElements().get(4).getValue(), 0.001);\n\n        assertTrue(res45.containsKey(\"country=us\"));\n        TSMRangeElements res45us = res45.get(\"country=us\");\n        assertEquals(\"country=us\", res45us.getKey());\n        assertEquals(0, res45us.getLabels().size());\n        assertEquals(5, res45us.getElements().size());\n        assertEquals(0L, res45us.getElements().get(0).getTimestamp());\n        assertEquals(11.5, res45us.getElements().get(0).getValue(), 0.001);\n        assertEquals(1L, res45us.getElements().get(1).getTimestamp());\n        assertEquals(12.5, res45us.getElements().get(1).getValue(), 0.001);\n        assertEquals(2L, res45us.getElements().get(2).getTimestamp());\n        assertEquals(10.5, res45us.getElements().get(2).getValue(), 0.001);\n        assertEquals(3L, res45us.getElements().get(3).getTimestamp());\n        assertEquals(13.5, res45us.getElements().get(3).getValue(), 0.001);\n        assertEquals(4L, res45us.getElements().get(4).getTimestamp());\n        assertEquals(9.5, res45us.getElements().get(4).getValue(), 0.001);\n        // REMOVE_END\n\n        // STEP_START create_compaction\n        String res46 = jedis.tsCreate(\"hyg:1\");\n        System.out.println(res46); // >>> OK\n\n        String res47 = jedis.tsCreate(\"hyg:compacted\");\n        System.out.println(res47); // >>> OK\n\n        String res48 = jedis.tsCreateRule(\"hyg:1\", \"hyg:compacted\", AggregationType.MIN, 3);\n        System.out.println(res48); // >>> OK\n\n        TSInfo res49 = jedis.tsInfo(\"hyg:1\");\n        System.out.println(\"Rules: \" + res49.getProperty(\"rules\"));\n        // >>> Rules: [{compactionKey=hyg:compacted, bucketDuration=3, aggregationType=MIN, alignmentTimestamp=0}]\n\n        TSInfo res50 = jedis.tsInfo(\"hyg:compacted\");\n        System.out.println(\"Source key: \" + res50.getProperty(\"sourceKey\"));\n        // >>> Source key: hyg:1\n        // STEP_END\n        // REMOVE_START\n        assertEquals(\"OK\", res46);\n        assertEquals(\"OK\", res47);\n        assertEquals(\"OK\", res48);\n        assertEquals(\"hyg:1\", res50.getProperty(\"sourceKey\"));\n        // REMOVE_END\n\n        // STEP_START comp_add\n        List<Long> res51 = jedis.tsMAdd(\n            new AbstractMap.SimpleEntry<>(\"hyg:1\", new TSElement(0L, 75.0)),\n            new AbstractMap.SimpleEntry<>(\"hyg:1\", new TSElement(1L, 77.0)),\n            new AbstractMap.SimpleEntry<>(\"hyg:1\", new TSElement(2L, 78.0))\n        );\n        System.out.println(res51); // >>> [0, 1, 2]\n\n        List<TSElement> res52 = jedis.tsRange(\"hyg:compacted\", 0L, 10L);\n        System.out.println(res52); // >>> []\n\n        long res53 = jedis.tsAdd(\"hyg:1\", 3L, 79.0);\n        System.out.println(res53); // >>> 3\n\n        List<TSElement> res54 = jedis.tsRange(\"hyg:compacted\", 0L, 10L);\n        System.out.println(res54); // >>> [(0:75.0)]\n        // STEP_END\n        // REMOVE_START\n        assertEquals(Arrays.asList(0L, 1L, 2L), res51);\n        assertEquals(Arrays.asList(), res52);\n        assertEquals(3L, res53);\n        assertEquals(Arrays.asList(new TSElement(0L, 75.0)), res54);\n        // REMOVE_END\n\n        // STEP_START del\n        TSInfo res55 = jedis.tsInfo(\"thermometer:1\");\n        System.out.println(res55.getProperty(\"totalSamples\")); // >>> 2\n        System.out.println(res55.getProperty(\"firstTimestamp\")); // >>> 1\n        System.out.println(res55.getProperty(\"lastTimestamp\")); // >>> 2\n\n        long res56 = jedis.tsAdd(\"thermometer:1\", 3L, 9.7);\n        System.out.println(res56); // >>> 3\n\n        TSInfo res57 = jedis.tsInfo(\"thermometer:1\");\n        System.out.println(res57.getProperty(\"totalSamples\")); // >>> 3\n\n        long res58 = jedis.tsDel(\"thermometer:1\", 1L, 2L);\n        System.out.println(res58); // >>> 2\n\n        TSInfo res59 = jedis.tsInfo(\"thermometer:1\");\n        System.out.println(res59.getProperty(\"totalSamples\")); // >>> 1\n\n        long res60 = jedis.tsDel(\"thermometer:1\", 3L, 3L);\n        System.out.println(res60); // >>> 1\n\n        TSInfo res61 = jedis.tsInfo(\"thermometer:1\");\n        System.out.println(res61.getProperty(\"totalSamples\")); // >>> 0\n        // STEP_END\n        // REMOVE_START\n        assertEquals((Long) 2L, res55.getProperty(\"totalSamples\"));\n        assertEquals((Long) 1L, res55.getProperty(\"firstTimestamp\"));\n        assertEquals((Long) 2L, res55.getProperty(\"lastTimestamp\"));\n        assertEquals(3L, res56);\n        assertEquals((Long) 3L, res57.getProperty(\"totalSamples\"));\n        assertEquals(2L, res58);\n        assertEquals((Long) 1L, res59.getProperty(\"totalSamples\"));\n        assertEquals(1L, res60);\n        assertEquals((Long) 0L, res61.getProperty(\"totalSamples\"));\n        // REMOVE_END\n\n        //HIDE_START\n        jedis.close();\n        //HIDE_END\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/TopKExample.java",
    "content": "//EXAMPLE: topk_tutorial\n//HIDE_START\npackage io.redis.examples;\n//HIDE_END\n\n//REMOVE_START\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisClient;\n//REMOVE_END\n\npublic class TopKExample {\n\n    @Test\n    public void run() {\n        //HIDE_START\n        RedisClient jedis = RedisClient.create(\"redis://127.0.0.1:6379\");\n        //HIDE_END\n\n        //REMOVE_START\n        jedis.del(\"bikes:keywords\");\n        //REMOVE_END\n\n        //STEP_START topk\n        String res1 = jedis.topkReserve(\"bikes:keywords\", 5L, 2000L, 7L, 0.925D);\n        System.out.println(res1); // >>> True\n\n        List<String> res2 = jedis.topkAdd(\"bikes:keywords\",\n                \"store\",\n                \"seat\",\n                \"handlebars\",\n                \"handles\",\n                \"pedals\",\n                \"tires\",\n                \"store\",\n                \"seat\");\n\n        System.out.println(res2); // >>> [None, None, None, None, None, 'handlebars', None, None]\n\n        List<String> res3 = jedis.topkList(\"bikes:keywords\");\n        System.out.println(res3); // >>> ['store', 'seat', 'pedals', 'tires', 'handles']\n\n        List<Boolean> res4 = jedis.topkQuery(\"bikes:keywords\", \"store\", \"handlebars\");\n        System.out.println(res4); // >>> [1, 0]\n        //STEP_END\n\n        //HIDE_START\n        jedis.close();\n        //HIDE_END\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/redis/examples/VectorSetExample.java",
    "content": "// EXAMPLE: vecset_tutorial\n// REMOVE_START\npackage io.redis.examples;\n\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n// REMOVE_END\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\n\nimport java.util.*;\n\npublic class VectorSetExample {\n\n  @Test\n  public void run() {\n    try (RedisClient jedis = RedisClient.create(\"redis://localhost:6379\")) {\n      // REMOVE_START\n      jedis.del(\"points\", \"quantSetQ8\", \"quantSetNoQ\", \"quantSetBin\", \"setNotReduced\",\n        \"setReduced\");\n      // REMOVE_END\n\n      // STEP_START vadd\n      boolean res1 = jedis.vadd(\"points\", new float[] { 1.0f, 1.0f }, \"pt:A\");\n      System.out.println(res1); // >>> true\n\n      boolean res2 = jedis.vadd(\"points\", new float[] { -1.0f, -1.0f }, \"pt:B\");\n      System.out.println(res2); // >>> true\n\n      boolean res3 = jedis.vadd(\"points\", new float[] { -1.0f, 1.0f }, \"pt:C\");\n      System.out.println(res3); // >>> true\n\n      boolean res4 = jedis.vadd(\"points\", new float[] { 1.0f, -1.0f }, \"pt:D\");\n      System.out.println(res4); // >>> true\n\n      boolean res5 = jedis.vadd(\"points\", new float[] { 1.0f, 0.0f }, \"pt:E\");\n      System.out.println(res5); // >>> true\n\n      String res6 = jedis.type(\"points\");\n      System.out.println(res6); // >>> vectorset\n      // STEP_END\n      // REMOVE_START\n      assertTrue(res1);\n      assertTrue(res2);\n      assertTrue(res3);\n      assertTrue(res4);\n      assertTrue(res5);\n      assertEquals(\"vectorset\", res6);\n      // REMOVE_END\n\n      // STEP_START vcardvdim\n      long res7 = jedis.vcard(\"points\");\n      System.out.println(res7); // >>> 5\n\n      long res8 = jedis.vdim(\"points\");\n      System.out.println(res8); // >>> 2\n      // STEP_END\n      // REMOVE_START\n      assertEquals(5L, res7);\n      assertEquals(2L, res8);\n      // REMOVE_END\n\n      // STEP_START vemb\n      List<Double> res9 = jedis.vemb(\"points\", \"pt:A\");\n      System.out.println(res9); // >>> [0.9999999..., 0.9999999...]\n\n      List<Double> res10 = jedis.vemb(\"points\", \"pt:B\");\n      System.out.println(res10); // >>> [-0.9999999..., -0.9999999...]\n\n      List<Double> res11 = jedis.vemb(\"points\", \"pt:C\");\n      System.out.println(res11); // >>> [-0.9999999..., 0.9999999...]\n\n      List<Double> res12 = jedis.vemb(\"points\", \"pt:D\");\n      System.out.println(res12); // >>> [0.9999999..., -0.9999999...]\n\n      List<Double> res13 = jedis.vemb(\"points\", \"pt:E\");\n      System.out.println(res13); // >>> [1, 0]\n      // STEP_END\n      // REMOVE_START\n      assertEquals(1, res9.get(0), 0.01);\n      assertEquals(1, res9.get(1), 0.01);\n      assertEquals(-1, res10.get(0), 0.01);\n      assertEquals(-1, res10.get(1), 0.01);\n      assertEquals(-1, res11.get(0), 0.01);\n      assertEquals(1, res11.get(1), 0.01);\n      assertEquals(1, res12.get(0), 0.01);\n      assertEquals(-1, res12.get(1), 0.01);\n      assertEquals(Arrays.asList(1.0, 0.0), res13);\n      // REMOVE_END\n\n      // STEP_START attr\n      boolean res14 = jedis.vsetattr(\"points\", \"pt:A\",\n        \"{\\\"name\\\":\\\"Point A\\\",\\\"description\\\":\\\"First point added\\\"}\");\n      System.out.println(res14); // >>> true\n\n      String res15 = jedis.vgetattr(\"points\", \"pt:A\");\n      System.out.println(res15);\n      // >>> {\"name\":\"Point A\",\"description\":\"First point added\"}\n\n      boolean res16 = jedis.vsetattr(\"points\", \"pt:A\", \"\");\n      System.out.println(res16); // >>> true\n\n      String res17 = jedis.vgetattr(\"points\", \"pt:A\");\n      System.out.println(res17); // >>> null\n      // STEP_END\n      // REMOVE_START\n      assertTrue(res14);\n      assertTrue(res15.contains(\"\\\"name\\\":\\\"Point A\\\"\"));\n      assertTrue(res15.contains(\"\\\"description\\\":\\\"First point added\\\"\"));\n      assertTrue(res16);\n      assertNull(res17);\n      // REMOVE_END\n\n      // STEP_START vrem\n      boolean res18 = jedis.vadd(\"points\", new float[] { 0.0f, 0.0f }, \"pt:F\");\n      System.out.println(res18); // >>> true\n\n      long res19 = jedis.vcard(\"points\");\n      System.out.println(res19); // >>> 6\n\n      boolean res20 = jedis.vrem(\"points\", \"pt:F\");\n      System.out.println(res20); // >>> true\n\n      long res21 = jedis.vcard(\"points\");\n      System.out.println(res21); // >>> 5\n      // STEP_END\n      // REMOVE_START\n      assertTrue(res18);\n      assertEquals(6L, res19);\n      assertTrue(res20);\n      assertEquals(5L, res21);\n      // REMOVE_END\n\n      // STEP_START vsim_basic\n      List<String> res22 = jedis.vsim(\"points\", new float[] { 0.9f, 0.1f });\n      System.out.println(res22);\n      // >>> [\"pt:E\", \"pt:A\", \"pt:D\", \"pt:C\", \"pt:B\"]\n      // STEP_END\n      // REMOVE_START\n      assertEquals(Arrays.asList(\"pt:E\", \"pt:A\", \"pt:D\", \"pt:C\", \"pt:B\"), res22);\n      // REMOVE_END\n\n      // STEP_START vsim_options\n      Map<String, Double> res23 = jedis.vsimByElementWithScores(\"points\", \"pt:A\",\n        new VSimParams().count(4));\n      System.out.println(res23);\n      // >>> {pt:A=1.0, pt:E≈0.85355, pt:D=0.5, pt:C=0.5}\n      // STEP_END\n      // REMOVE_START\n      assertEquals(1.0, res23.get(\"pt:A\"), 0.0001);\n      assertEquals(0.5, res23.get(\"pt:C\"), 0.0001);\n      assertEquals(0.5, res23.get(\"pt:D\"), 0.0001);\n      assertTrue(Math.abs(res23.get(\"pt:E\") - 0.8535) < 0.01);\n      // REMOVE_END\n\n      // STEP_START vsim_filter\n      boolean res24 = jedis.vsetattr(\"points\", \"pt:A\", \"{\\\"size\\\":\\\"large\\\",\\\"price\\\":18.99}\");\n      System.out.println(res24); // >>> true\n\n      boolean res25 = jedis.vsetattr(\"points\", \"pt:B\", \"{\\\"size\\\":\\\"large\\\",\\\"price\\\":35.99}\");\n      System.out.println(res25); // >>> true\n\n      boolean res26 = jedis.vsetattr(\"points\", \"pt:C\", \"{\\\"size\\\":\\\"large\\\",\\\"price\\\":25.99}\");\n      System.out.println(res26); // >>> true\n\n      boolean res27 = jedis.vsetattr(\"points\", \"pt:D\", \"{\\\"size\\\":\\\"small\\\",\\\"price\\\":21.00}\");\n      System.out.println(res27); // >>> true\n\n      boolean res28 = jedis.vsetattr(\"points\", \"pt:E\", \"{\\\"size\\\":\\\"small\\\",\\\"price\\\":17.75}\");\n      System.out.println(res28); // >>> true\n\n      List<String> res29 = jedis.vsimByElement(\"points\", \"pt:A\",\n        new VSimParams().filter(\".size == \\\"large\\\"\"));\n      System.out.println(res29); // >>> [\"pt:A\", \"pt:C\", \"pt:B\"]\n\n      List<String> res30 = jedis.vsimByElement(\"points\", \"pt:A\",\n        new VSimParams().filter(\".size == \\\"large\\\" && .price > 20.00\"));\n      System.out.println(res30); // >>> [\"pt:C\", \"pt:B\"]\n      // STEP_END\n      // REMOVE_START\n      assertTrue(res24);\n      assertTrue(res25);\n      assertTrue(res26);\n      assertTrue(res27);\n      assertTrue(res28);\n      assertEquals(Arrays.asList(\"pt:C\", \"pt:B\"), res30);\n      // REMOVE_END\n\n      // STEP_START add_quant\n      boolean res31 = jedis.vadd(\"quantSetQ8\", new float[] { 1.262185f, 1.958231f }, \"quantElement\",\n        new VAddParams().q8());\n      System.out.println(res31); // >>> true\n\n      List<Double> res32 = jedis.vemb(\"quantSetQ8\", \"quantElement\");\n      System.out.println(\"Q8: \" + res32);\n      // >>> Q8: [~1.264, ~1.958]\n\n      boolean res33 = jedis.vadd(\"quantSetNoQ\", new float[] { 1.262185f, 1.958231f },\n        \"quantElement\", new VAddParams().noQuant());\n      System.out.println(res33); // >>> true\n\n      List<Double> res34 = jedis.vemb(\"quantSetNoQ\", \"quantElement\");\n      System.out.println(\"NOQUANT: \" + res34);\n      // >>> NOQUANT: [~1.262185, ~1.958231]\n\n      boolean res35 = jedis.vadd(\"quantSetBin\", new float[] { 1.262185f, 1.958231f },\n        \"quantElement\", new VAddParams().bin());\n      System.out.println(res35); // >>> true\n\n      List<Double> res36 = jedis.vemb(\"quantSetBin\", \"quantElement\");\n      System.out.println(\"BIN: \" + res36);\n      // >>> BIN: [1, 1]\n      // STEP_END\n      // REMOVE_START\n      assertTrue(res31);\n      assertTrue(res33);\n      assertTrue(res35);\n      assertEquals(2, res32.size());\n      assertEquals(2, res34.size());\n      assertEquals(2, res36.size());\n      assertTrue(Math.abs(res32.get(0) - 1.26) < 0.05);\n      assertTrue(Math.abs(res32.get(1) - 1.958) < 0.01);\n      assertTrue(Math.abs(res34.get(0) - 1.2622) < 0.01);\n      assertTrue(Math.abs(res34.get(1) - 1.9582) < 0.01);\n      assertEquals(Arrays.asList(1.0, 1.0), res36);\n      // REMOVE_END\n\n      // STEP_START add_reduce\n      float[] values = new float[300];\n      for (int i = 0; i < 300; i++)\n        values[i] = i / 299.0f;\n\n      boolean res37 = jedis.vadd(\"setNotReduced\", values, \"element\");\n      System.out.println(res37); // >>> true\n\n      long res38 = jedis.vdim(\"setNotReduced\");\n      System.out.println(res38); // >>> 300\n\n      boolean res39 = jedis.vadd(\"setReduced\", values, \"element\", 100, new VAddParams());\n      System.out.println(res39); // >>> true\n\n      long res40 = jedis.vdim(\"setReduced\");\n      System.out.println(res40); // >>> 100\n      // STEP_END\n      // REMOVE_START\n      assertTrue(res37);\n      assertEquals(300L, res38);\n      assertTrue(res39);\n      assertEquals(100L, res40);\n      // REMOVE_END\n\n      // HIDE_START\n      jedis.close();\n    }\n  }\n}\n// HIDE_END\n"
  },
  {
    "path": "src/test/java/io/redis/test/annotations/ConditionalOnEnv.java",
    "content": "package io.redis.test.annotations;\n\nimport java.lang.annotation.*;\n\n/**\n * Annotation to conditionally enable or disable tests based on the test environment.\n * <p>\n * This unified annotation replaces both {@code @EnabledOnEnv} and {@code @SkipOnEnv} annotations,\n * providing a single way to control test execution based on environment.\n * <p>\n * The annotation can be applied at both method and class level. Method-level annotations take\n * precedence over class-level annotations.\n * <p>\n * Example usage:\n * \n * <pre>\n * // Enable test only in a specific environment\n * &#64;ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\n * public void testOnlyInDocker() {\n *   // This test only runs in Docker environment\n * }\n *\n * // Skip test in a specific environment\n * &#64;ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n * public void testNotInRedisEnterprise() {\n *   // This test is skipped in Redis Enterprise environment\n * }\n *\n * // Enable test in multiple environments\n * &#64;ConditionalOnEnv(value = { TestEnvUtil.ENV_OSS_DOCKER,\n *     TestEnvUtil.ENV_OSS_SOURCE }, enabled = true)\n * public void testInDockerOrSource() {\n *   // This test runs in Docker or Source environments\n * }\n * </pre>\n */\n@Inherited\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ ElementType.METHOD, ElementType.TYPE })\npublic @interface ConditionalOnEnv {\n  /**\n   * The environment(s) to match against. Valid values are defined in {@code TestEnvUtil} (e.g.,\n   * \"oss-docker\", \"oss-source\", \"re\").\n   * @return array of environment identifiers\n   */\n  String[] value();\n\n  /**\n   * Whether the test should be enabled or disabled when the current environment matches one of the\n   * specified values.\n   * <p>\n   * When {@code enabled = true}: the test runs ONLY when the current environment matches one of the\n   * specified values.\n   * <p>\n   * When {@code enabled = false}: the test is SKIPPED when the current environment matches one of\n   * the specified values.\n   * @return true to enable the test in matching environments, false to disable\n   */\n  boolean enabled();\n\n  /**\n   * Optional message to display when the test is disabled.\n   * @return the reason message\n   */\n  String message() default \"\";\n}\n"
  },
  {
    "path": "src/test/java/io/redis/test/annotations/EnabledOnCommand.java",
    "content": "package io.redis.test.annotations;\n\nimport java.lang.annotation.*;\n\n@Inherited\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD, ElementType.TYPE})\npublic @interface EnabledOnCommand {\n    String value();\n    String subCommand() default \"\";\n}"
  },
  {
    "path": "src/test/java/io/redis/test/annotations/SinceRedisVersion.java",
    "content": "package io.redis.test.annotations;\n\nimport java.lang.annotation.*;\n\n@Inherited\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD, ElementType.TYPE})\npublic @interface SinceRedisVersion {\n    String value();\n    String message() default \"\";\n}\n"
  },
  {
    "path": "src/test/java/io/redis/test/utils/RedisInfo.java",
    "content": "package io.redis.test.utils;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class RedisInfo {\n    private final Map<String, String> infoMap;\n\n    public RedisInfo() {\n        this.infoMap = new HashMap<>();\n    }\n\n    public void setField(String key, String value) {\n        infoMap.put(key, value);\n    }\n\n    public String getField(String key) {\n        return infoMap.get(key);\n    }\n\n    public String getRedisVersion() {\n        return infoMap.get(\"redis_version\");\n    }\n\n    public String getOs() {\n        return infoMap.get(\"os\");\n    }\n\n    public String getMode() {\n        return infoMap.get(\"redis_mode\");\n    }\n\n    public String getPorts() {\n        return infoMap.get(\"tcp_port\"); // Assuming \"tcp_port\" is the key for ports\n    }\n\n    @Override\n    public String toString() {\n        return \"RedisInfo{\" +\n                \"infoMap=\" + infoMap +\n                '}';\n    }\n\n    public static RedisInfo parseInfoServer(String infoOutput) {\n        RedisInfo redisInfo = new RedisInfo();\n\n        String[] lines = infoOutput.split(\"\\n\");\n\n        for (String line : lines) {\n            // Only parse lines that contain a colon (indicating a key-value pair)\n            if (line.contains(\":\")) {\n                String[] parts = line.split(\":\", 2);\n                if (parts.length == 2) {\n                    redisInfo.setField(parts[0].trim(), parts[1].trim());\n                }\n            }\n        }\n\n        // You can still check for required fields if necessary\n        // Example: Ensure that specific fields are set\n        if (redisInfo.getField(\"redis_version\") == null || redisInfo.getField(\"redis_mode\") == null) {\n            throw new IllegalArgumentException(\"Missing required fields in Redis server info.\");\n        }\n\n        return redisInfo;\n    }\n}"
  },
  {
    "path": "src/test/java/io/redis/test/utils/RedisVersion.java",
    "content": "package io.redis.test.utils;\n\npublic class RedisVersion implements Comparable<RedisVersion> {\n    public static final RedisVersion V6_0_0 = RedisVersion.of(\"6.0.0\");\n    public static final RedisVersion V7_0_0 = RedisVersion.of(\"7.0.0\");\n    public static final RedisVersion V7_2_0 = RedisVersion.of(\"7.2.0\");\n    public static final RedisVersion V7_4 = RedisVersion.of(\"7.4\");\n    public static final RedisVersion V8_0_0_PRE = RedisVersion.of(\"7.9.0\");\n    public static final RedisVersion V8_0_0 = RedisVersion.of(\"8.0.0\");\n    public static final String V8_4_0_STRING = \"8.4.0\";\n    public static final RedisVersion V8_4_0 = RedisVersion.of(\"8.4.0\");\n    public static final RedisVersion V8_6_0 = RedisVersion.of(\"8.6.0\");\n    public static final RedisVersion V8_6_1 = RedisVersion.of(\"8.6.1\");\n\n    private final int major;\n    private final int minor;\n    private final int patch;\n\n    // Private constructor to enforce use of the static factory method\n    private RedisVersion(int major, int minor, int patch) {\n        this.major = major;\n        this.minor = minor;\n        this.patch = patch;\n    }\n\n    // Static method to create a RedisVersion from a version string\n    public static RedisVersion of(String version) {\n        // Check for the \"redis_version:\" prefix and remove it if present\n        if (version.startsWith(\"redis_version:\")) {\n            version = version.substring(\"redis_version:\".length());\n        }\n\n        // Split the version string by the '.' character\n        String[] parts = version.split(\"\\\\.\");\n\n        // Parse each component, setting defaults for missing parts\n        int major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0;\n        int minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;\n        int patch = parts.length > 2 ? Integer.parseInt(parts[2]) : 0;\n\n        return new RedisVersion(major, minor, patch);\n    }\n\n    public int getMajor() {\n        return major;\n    }\n\n    public int getMinor() {\n        return minor;\n    }\n\n    public int getPatch() {\n        return patch;\n    }\n\n    @Override\n    public String toString() {\n        return \"RedisVersion{\" +\n                \"major=\" + major +\n                \", minor=\" + minor +\n                \", patch=\" + patch +\n                '}';\n    }\n\n    @Override\n    public int compareTo(RedisVersion other) {\n        // Compare major, minor, and patch versions\n        if (this.major != other.major) {\n            return Integer.compare(this.major, other.major);\n        }\n        if (this.minor != other.minor) {\n            return Integer.compare(this.minor, other.minor);\n        }\n        return Integer.compare(this.patch, other.patch);\n    }\n\n    public boolean is(RedisVersion other) {\n        return this.compareTo(other) == 0;\n    }\n\n    public boolean isLessThanOrEqualTo(RedisVersion other) {\n        return this.compareTo(other) <= 0;\n    }\n\n    public boolean isLessThan(RedisVersion other) {\n        return this.compareTo(other) < 0;\n    }\n\n    public boolean isGreaterThanOrEqualTo(RedisVersion other) {\n        return this.compareTo(other) >= 0;\n    }\n\n    public boolean isGreaterThan(RedisVersion other) {\n        return this.compareTo(other) > 0;\n    }\n\n    // Static method to compare two RedisVersion instances\n    public static int compare(RedisVersion v1, RedisVersion v2) {\n        return v1.compareTo(v2);\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ACLJedisPoolTest.java",
    "content": "package redis.clients.jedis;\n\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.time.Duration;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.InvalidURIException;\nimport redis.clients.jedis.exceptions.JedisAccessControlException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * This test class is a copy of {@link JedisPoolTest}.\n * <p>\n * This test is only executed when the server/cluster is Redis 6. or more.\n */\n@SinceRedisVersion(\"6.0.0\")\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\npublic class ACLJedisPoolTest {\n  private static EndpointConfig endpoint;\n\n  private static EndpointConfig endpointWithDefaultUser;\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @RegisterExtension\n  public static RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone0-acl\"));\n\n  @BeforeAll\n  public static void prepare() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0-acl\");\n    endpointWithDefaultUser = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n\n  @Test\n  public void checkConnections() {\n    JedisPool pool = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(),\n        endpoint.getUsername(), endpoint.getPassword());\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void checkCloseableConnections() throws Exception {\n    JedisPool pool = new JedisPool(endpoint.getHost(), endpoint.getPort(), endpoint.getUsername(),\n        endpoint.getPassword());\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void checkResourceIsClosableAndReusable() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisPool pool = new JedisPool(config, endpoint.getHost(), endpoint.getPort(),\n        Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, 0 /* infinite */, endpoint.getUsername(),\n        endpoint.getPassword(), Protocol.DEFAULT_DATABASE, \"closable-reusable-pool\", false, null, null, null)) {\n\n      Jedis jedis = pool.getResource();\n      jedis.set(\"hello\", \"jedis\");\n      jedis.close();\n\n      Jedis jedis2 = pool.getResource();\n      assertEquals(jedis, jedis2);\n      assertEquals(\"jedis\", jedis2.get(\"hello\"));\n      jedis2.close();\n    }\n  }\n\n  @Test\n  public void checkResourceWithConfigIsClosableAndReusable() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisPool pool = new JedisPool(config, endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().clientName(\"closable-reusable-pool\")\n        .build())) {\n\n      Jedis jedis = pool.getResource();\n      jedis.set(\"hello\", \"jedis\");\n      jedis.close();\n\n      Jedis jedis2 = pool.getResource();\n      assertEquals(jedis, jedis2);\n      assertEquals(\"jedis\", jedis2.get(\"hello\"));\n      assertEquals(\"closable-reusable-pool\", jedis2.clientGetname());\n      jedis2.close();\n    }\n  }\n\n  @Test\n  public void checkPoolRepairedWhenJedisIsBroken() {\n    JedisPool pool = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(),\n        Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, 0 /* infinite */, endpoint.getUsername(),\n        endpoint.getPassword(), Protocol.DEFAULT_DATABASE, \"repairable-pool\");\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"0\");\n      jedis.disconnect();\n    }\n\n    try (Jedis jedis = pool.getResource()) {\n      jedis.incr(\"foo\");\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void checkPoolOverflow() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisPool pool = new JedisPool(config, endpoint.getHost(), endpoint.getPort());\n        Jedis jedis = pool.getResource()) {\n      jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n\n      assertThrows(JedisException.class, () -> {\n        try (Jedis jedis2 = pool.getResource()) {\n          jedis2.auth(endpoint.getUsername(), endpoint.getPassword());\n        }\n      });\n    }\n  }\n\n  @Test\n  public void securePool() {\n    JedisPoolConfig config = new JedisPoolConfig();\n    config.setTestOnBorrow(true);\n    JedisPool pool = new JedisPool(config, endpoint.getHost(), endpoint.getPort(), 2000, endpoint.getUsername(),\n        endpoint.getPassword());\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"bar\");\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void securePoolNonSSL() {\n    JedisPoolConfig config = new JedisPoolConfig();\n    config.setTestOnBorrow(true);\n    JedisPool pool = new JedisPool(config, endpoint.getHost(), endpoint.getPort(), 2000, endpoint.getUsername(),\n        endpoint.getPassword(), false);\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"bar\");\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void nonDefaultDatabase() {\n    try (JedisPool pool0 = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(), 2000,\n        endpoint.getUsername(), endpoint.getPassword()); Jedis jedis0 = pool0.getResource()) {\n      jedis0.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis0.get(\"foo\"));\n    }\n\n    try (JedisPool pool1 = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(), 2000,\n        endpoint.getUsername(), endpoint.getPassword(), 1); Jedis jedis1 = pool1.getResource()) {\n      assertNull(jedis1.get(\"foo\"));\n    }\n  }\n\n  @Test\n  public void startWithUrlString() {\n    try (Jedis j = new Jedis(endpoint.getHost(), endpoint.getPort())) {\n      j.auth(endpoint.getUsername(), endpoint.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (JedisPool pool = new JedisPool(\n        endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build());\n        Jedis jedis = pool.getResource()) {\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  public void startWithUrl() throws URISyntaxException {\n    try (Jedis j = new Jedis(endpoint.getHost(), endpoint.getPort())) {\n      j.auth(endpoint.getUsername(), endpoint.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (JedisPool pool = new JedisPool(\n        endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build());\n        Jedis jedis = pool.getResource()) {\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n\n    try (JedisPool pool = new JedisPool(\n        endpointWithDefaultUser.getURIBuilder().defaultCredentials().path(\"/2\").build());\n        Jedis jedis = pool.getResource()) {\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  public void shouldThrowInvalidURIExceptionForInvalidURI() {\n    assertThrows(InvalidURIException.class, () -> {\n      new JedisPool(new URI(\"redis://localhost:\")).close();\n    });\n  }\n\n  @Test\n  public void allowUrlWithNoDBAndNoPassword() throws URISyntaxException {\n    new JedisPool(endpoint.getURI().toString()).close();\n    new JedisPool(endpoint.getURI()).close();\n  }\n\n  @Test\n  public void selectDatabaseOnActivation() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(), 2000,\n        endpoint.getUsername(), endpoint.getPassword())) {\n\n      Jedis jedis0 = pool.getResource();\n      assertEquals(0, jedis0.getDB());\n\n      jedis0.select(1);\n      assertEquals(1, jedis0.getDB());\n\n      jedis0.close();\n\n      Jedis jedis1 = pool.getResource();\n      assertSame(jedis1, jedis0);\n      assertEquals(0, jedis1.getDB());\n\n      jedis1.close();\n    }\n  }\n\n  @Test\n  public void customClientName() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(), 2000,\n        endpoint.getUsername(), endpoint.getPassword(), 0, \"my_shiny_client_name\"); Jedis jedis = pool.getResource()) {\n\n      assertEquals(\"my_shiny_client_name\", jedis.clientGetname());\n    }\n  }\n\n  @Test\n  public void customClientNameNoSSL() {\n    try (JedisPool pool0 = new JedisPool(new JedisPoolConfig(), endpoint.getHost(), endpoint.getPort(), 2000,\n        endpoint.getUsername(), endpoint.getPassword(), 0, \"my_shiny_client_name_no_ssl\", false);\n        Jedis jedis = pool0.getResource()) {\n\n      assertEquals(\"my_shiny_client_name_no_ssl\", jedis.clientGetname());\n    }\n  }\n\n  @Test\n  public void testCloseConnectionOnMakeObject() {\n    JedisPoolConfig config = new JedisPoolConfig();\n    config.setTestOnBorrow(true);\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpoint.getHost(),\n        endpoint.getPort(), 2000, endpoint.getUsername(), \"wrongpassword\");\n        Jedis jedis = new Jedis(endpointWithDefaultUser.getURIBuilder()\n            .credentials(\"\", endpointWithDefaultUser.getPassword()).build())) {\n      int currentClientCount = getClientCount(jedis.clientList());\n      assertThrows(JedisAccessControlException.class, pool::getResource);\n      // wait for the redis server to close the connection\n      await().pollDelay(Duration.ofMillis(10)).atMost(500, MILLISECONDS)\n          .until(() -> getClientCount(jedis.clientList()) == currentClientCount);\n      assertEquals(currentClientCount, getClientCount(jedis.clientList()));\n    }\n  }\n\n  private int getClientCount(final String clientList) {\n    return clientList.split(\"\\n\").length;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ACLJedisSentinelPoolTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n/**\n * This test class is mostly a copy of {@link JedisSentinelPoolTest}.\n * <p>\n * This tests are only executed when the server/cluster is Redis 6 or more.\n */\n@SinceRedisVersion(\"6.0.0\")\n@Tag(\"integration\")\npublic class ACLJedisSentinelPoolTest {\n\n  private static final String MASTER_NAME = \"aclmaster\";\n\n  protected static HostAndPort sentinel1;\n\n  protected Set<HostAndPort> sentinels = new HashSet<>();\n\n  @RegisterExtension\n  public static RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone0\"));\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    sentinel1 = Endpoints.getRedisEndpoint(\"sentinel-standalone0\").getHostAndPort();\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    sentinels.clear();\n    sentinels.add(sentinel1);\n  }\n\n  private static Set<String> toStrings(Set<HostAndPort> hostAndPorts) {\n    return hostAndPorts.stream().map(HostAndPort::toString).collect(Collectors.toSet());\n  }\n\n  @Test\n  public void repeatedSentinelPoolInitialization() {\n\n    for (int i = 0; i < 20; ++i) {\n      GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n\n      JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, toStrings(sentinels), config, 1000, 1000,\n          \"acljedis\", \"fizzbuzz\", 2, null, 1000, 1000, \"sentinel\", \"foobared\", null);\n      pool.getResource().close();\n      pool.destroy();\n    }\n  }\n\n  @Test\n  public void repeatedSentinelPoolInitializationWithConfig() {\n\n    for (int i = 0; i < 20; ++i) {\n\n      GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();\n\n      JedisClientConfig masterConfig = DefaultJedisClientConfig.builder()\n          .connectionTimeoutMillis(1000).socketTimeoutMillis(1000).database(2)\n          .user(\"acljedis\").password(\"fizzbuzz\").build();\n\n      JedisClientConfig sentinelConfig = DefaultJedisClientConfig.builder()\n          .connectionTimeoutMillis(1000).socketTimeoutMillis(1000)\n          .user(\"sentinel\").password(\"foobared\").build();\n\n      JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, poolConfig, masterConfig, sentinelConfig);\n      pool.getResource().close();\n      pool.destroy();\n    }\n  }\n\n  @Test\n  public void initializeWithNotAvailableSentinelsShouldThrowException() {\n\n    GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();\n\n    JedisClientConfig masterConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(1000).socketTimeoutMillis(1000).database(2).user(\"acljedis\")\n        .password(\"fizzbuzz\").build();\n\n    JedisClientConfig sentinelConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(1000).socketTimeoutMillis(1000).user(\"default\")\n        .password(\"wrongpassword\").build();\n    assertThrows(JedisConnectionException.class, () -> {\n      try (JedisSentinelPool ignored = new JedisSentinelPool(MASTER_NAME, sentinels, poolConfig,\n          masterConfig, sentinelConfig)) {\n        // do nothing\n      }\n    });\n  }\n\n  @Test\n  public void initializeWithNotMonitoredMasterNameShouldThrowException() {\n\n    GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();\n\n    JedisClientConfig masterConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(1000).socketTimeoutMillis(1000).database(2).user(\"acljedis\")\n        .password(\"fizzbuzz\").build();\n\n    JedisClientConfig sentinelConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(1000).socketTimeoutMillis(1000).user(\"sentinel\")\n        .password(\"foobared\").build();\n\n    assertThrows(JedisException.class, () -> {\n      try (JedisSentinelPool ignored = new JedisSentinelPool(\"wrongMasterName\", sentinels, poolConfig,\n          masterConfig, sentinelConfig)) {\n        // do nothing\n      }\n    });\n  }\n\n  @Test\n  public void checkCloseableConnections() throws Exception {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n\n    JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, toStrings(sentinels), config,\n        1000, 1000, \"acljedis\", \"fizzbuzz\", 2, null, 1000, 1000, \"sentinel\", \"foobared\", null);\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void returnResourceShouldResetState() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, toStrings(sentinels), config,\n        1000, 1000, \"acljedis\", \"fizzbuzz\", 2, null, 1000, 1000, \"sentinel\", \"foobared\", null)) {\n      Jedis jedis;\n      try (Jedis jedis1 = pool.getResource()) {\n        jedis = jedis1;\n        jedis1.set(\"hello\", \"jedis\");\n        Transaction t = jedis1.multi();\n        t.set(\"hello\", \"world\");\n      }\n\n      try (Jedis jedis2 = pool.getResource()) {\n\n        assertSame(jedis, jedis2);\n        assertEquals(\"jedis\", jedis2.get(\"hello\"));\n      }\n    }\n  }\n\n  @Test\n  public void checkResourceIsCloseable() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, toStrings(sentinels), config,\n        1000, 1000, \"acljedis\", \"fizzbuzz\", 2, null, 1000, 1000, \"sentinel\", \"foobared\", null)) {\n\n      Jedis jedis;\n      try (Jedis jedis1 = pool.getResource()) {\n        jedis = jedis1;\n        jedis1.set(\"hello\", \"jedis\");\n      }\n\n      try (Jedis jedis2 = pool.getResource()) {\n        assertEquals(jedis, jedis2);\n      }\n    }\n  }\n\n  @Test\n  public void customClientName() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, toStrings(sentinels), config,\n        1000, 1000, \"acljedis\", \"fizzbuzz\", 0, \"my_shiny_master_client\",\n        1000, 1000, \"sentinel\", \"foobared\", \"my_shiny_sentinel_client\");\n\n    try (Jedis jedis = pool.getResource()) {\n      assertEquals(\"my_shiny_master_client\", jedis.clientGetname());\n    } finally {\n      pool.close();\n    }\n\n    assertTrue(pool.isClosed());\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ACLJedisTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static redis.clients.jedis.util.RedisVersionUtil.getRedisVersion;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.utils.RedisVersion;\nimport java.net.URISyntaxException;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.commands.jedis.JedisCommandsTestBase;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * This test class is a copy of {@link JedisTest}.\n * <p>\n * This test is only executed when the server/cluster is Redis 6. or more.\n */\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ACLJedisTest extends JedisCommandsTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  /**\n   * Use to check if the ACL test should be ran. ACL are available only in 6.0 and later\n   */\n  @BeforeAll\n  public static void prepare() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0-acl\");\n    assumeTrue(getRedisVersion(endpoint).isGreaterThanOrEqualTo(RedisVersion.of(\"6.0.0\")),\n        \"Not running ACL test on this version of Redis\");\n  }\n\n  public ACLJedisTest(RedisProtocol redisProtocol) {\n    super(redisProtocol);\n  }\n\n  @Test\n  public void useWithoutConnecting() {\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      assertEquals(\"OK\", j.auth(endpoint.getUsername(), endpoint.getPassword()));\n      j.dbSize();\n    }\n  }\n\n  @Test\n  public void connectWithConfig() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), DefaultJedisClientConfig.builder().build())) {\n      jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void connectWithConfigInterface() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), new JedisClientConfig() {\n    })) {\n      jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), new JedisClientConfig() {\n      @Override\n      public String getUser() {\n        return endpoint.getUsername();\n      }\n\n      @Override\n      public String getPassword() {\n        return endpoint.getPassword();\n      }\n    })) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void startWithUrl() {\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      assertEquals(\"OK\", j.auth(endpoint.getUsername(), endpoint.getPassword()));\n      assertEquals(\"OK\", j.select(2));\n      j.set(\"foo\", \"bar\");\n    }\n    try (Jedis j2 = new Jedis(\n        endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build().toString())) {\n      assertEquals(\"PONG\", j2.ping());\n      assertEquals(\"bar\", j2.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void startWithUri() throws URISyntaxException {\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      assertEquals(\"OK\", j.auth(endpoint.getUsername(), endpoint.getPassword()));\n      assertEquals(\"OK\", j.select(2));\n      j.set(\"foo\", \"bar\");\n    }\n    try (Jedis j1 = new Jedis(endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build())) {\n      assertEquals(\"PONG\", j1.ping());\n      assertEquals(\"bar\", j1.get(\"foo\"));\n    }\n    try (Jedis j2 = new Jedis(endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build())) {\n      assertEquals(\"PONG\", j2.ping());\n      assertEquals(\"bar\", j2.get(\"foo\"));\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/BuilderTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.resps.StreamEntry;\nimport redis.clients.jedis.resps.StreamEntryBinary;\nimport redis.clients.jedis.util.RedisInputStream;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class BuilderTest {\n\n  // Helper methods for building test data from RESP protocol strings\n  private static Object parseRespResponse(String respResponse) {\n    InputStream is = new ByteArrayInputStream(respResponse.getBytes());\n    return Protocol.read(new RedisInputStream(is));\n  }\n  \n  @SuppressWarnings(\"unchecked\")\n  private static ArrayList<Object> createStreamEntryData(String id, String fieldKey,\n      String fieldValue, long millisElapsedFromDelivery, long deliveredCount) {\n    String respResponse =\n        \"*4\\r\\n\" +                                                    // Entry with 4 elements\n            \"$\" + id.length() + \"\\r\\n\" + id + \"\\r\\n\" +                   // Entry ID\n            \"*2\\r\\n\" +                                                    // 2 field-value pairs\n            \"$\" + fieldKey.length() + \"\\r\\n\" + fieldKey + \"\\r\\n\" +       // Field key\n            \"$\" + fieldValue.length() + \"\\r\\n\" + fieldValue + \"\\r\\n\" +   // Field value\n            \":\" + millisElapsedFromDelivery + \"\\r\\n\" +                    // millisElapsedFromDelivery\n            \":\" + deliveredCount + \"\\r\\n\";                               // deliveredCount\n\n    return (ArrayList<Object>) parseRespResponse(respResponse);\n  }\n  \n  @SuppressWarnings(\"unchecked\")\n  private static ArrayList<Object> createStreamEntryBinaryData(String id, String fieldKey,\n      byte[] fieldValue, long millisElapsedFromDelivery, long deliveredCount) {\n    // For binary data, we need to construct the RESP response with the actual byte length\n    String respResponse =\n        \"*4\\r\\n\" +                                                    // Entry with 4 elements\n            \"$\" + id.length() + \"\\r\\n\" + id + \"\\r\\n\" +                   // Entry ID\n            \"*2\\r\\n\" +                                                    // 2 field-value pairs\n            \"$\" + fieldKey.length() + \"\\r\\n\" + fieldKey + \"\\r\\n\" +       // Field key\n            \"$\" + fieldValue.length + \"\\r\\n\";                            // Field value length\n\n    // Manually construct the byte array with binary field value\n    byte[] respBytes = respResponse.getBytes();\n    byte[] crLf = \"\\r\\n\".getBytes();\n    byte[] metadataBytes = (\":\" + millisElapsedFromDelivery + \"\\r\\n\" + \":\" + deliveredCount\n        + \"\\r\\n\").getBytes();\n\n    byte[] fullResponse = new byte[respBytes.length + fieldValue.length + crLf.length\n        + metadataBytes.length];\n    System.arraycopy(respBytes, 0, fullResponse, 0, respBytes.length);\n    System.arraycopy(fieldValue, 0, fullResponse, respBytes.length, fieldValue.length);\n    System.arraycopy(crLf, 0, fullResponse, respBytes.length + fieldValue.length, crLf.length);\n    System.arraycopy(metadataBytes, 0, fullResponse,\n        respBytes.length + fieldValue.length + crLf.length, metadataBytes.length);\n\n    InputStream is = new ByteArrayInputStream(fullResponse);\n    return (ArrayList<Object>) Protocol.read(new RedisInputStream(is));\n  }\n\n  @Test\n  public void buildDouble() {\n    Double build = BuilderFactory.DOUBLE.build(\"1.0\".getBytes());\n    assertEquals(Double.valueOf(1.0), build);\n    build = BuilderFactory.DOUBLE.build(\"inf\".getBytes());\n    assertEquals(Double.valueOf(Double.POSITIVE_INFINITY), build);\n    build = BuilderFactory.DOUBLE.build(\"+inf\".getBytes());\n    assertEquals(Double.valueOf(Double.POSITIVE_INFINITY), build);\n    build = BuilderFactory.DOUBLE.build(\"-inf\".getBytes());\n    assertEquals(Double.valueOf(Double.NEGATIVE_INFINITY), build);\n\n    try {\n      BuilderFactory.DOUBLE.build(\"\".getBytes());\n      Assertions.fail(\"Empty String should throw NumberFormatException.\");\n    } catch (NumberFormatException expected) {\n      assertEquals(\"empty String\", expected.getMessage());\n    }\n  }\n\n  @Test\n  public void buildStreamEntryListWithClaimedEntryMetadata() {\n    // Simulate Redis response for a single claimed entry with metadata\n    // Format: [[id, [field, value], msSinceLastDelivery, redeliveryCount]]\n    List<Object> data = new ArrayList<>();\n    data.add(createStreamEntryData(\"1234-12\", \"key\", \"value\", 5000L, 2L));\n\n    List<StreamEntry> result = BuilderFactory.STREAM_ENTRY_LIST.build(data);\n\n    assertNotNull(result);\n    assertEquals(1, result.size());\n\n    StreamEntry streamEntry = result.get(0);\n    assertEquals(\"1234-12\", streamEntry.getID().toString());\n    assertEquals(\"value\", streamEntry.getFields().get(\"key\"));\n    assertEquals(Long.valueOf(5000), streamEntry.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(2), streamEntry.getDeliveredCount());\n  }\n\n  @Test\n  public void buildStreamEntryListWithFreshEntryZeroRedeliveries() {\n    // Simulate Redis response for a fresh entry (not claimed from PEL)\n    // Format: [[id, [field, value], 0, 0]]\n    List<Object> data = new ArrayList<>();\n    data.add(createStreamEntryData(\"1234-12\", \"key\", \"value\", 1000L, 0L));\n\n    List<StreamEntry> result = BuilderFactory.STREAM_ENTRY_LIST.build(data);\n\n    assertNotNull(result);\n    assertEquals(1, result.size());\n\n    StreamEntry streamEntry = result.get(0);\n    assertEquals(\"1234-12\", streamEntry.getID().toString());\n    assertEquals(Long.valueOf(1000), streamEntry.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(0), streamEntry.getDeliveredCount());\n  }\n\n  @Test\n  public void buildStreamEntryListWithMixedBatchClaimedFirstThenFresh() {\n    // Simulate Redis response with mixed entries: claimed entries first, then fresh entries\n    List<Object> data = new ArrayList<>();\n\n    // Entry #1 (claimed, redeliveryCount=2)\n    data.add(createStreamEntryData(\"1-0\", \"f1\", \"v1\", 1500L, 2L));\n\n    // Entry #2 (claimed, redeliveryCount=1)\n    data.add(createStreamEntryData(\"2-0\", \"f2\", \"v2\", 1200L, 1L));\n\n    // Entry #3 (fresh, redeliveryCount=0)\n    data.add(createStreamEntryData(\"3-0\", \"f3\", \"v3\", 10L, 0L));\n\n    List<StreamEntry> result = BuilderFactory.STREAM_ENTRY_LIST.build(data);\n\n    assertNotNull(result);\n    assertEquals(3, result.size());\n\n    StreamEntry m1 = result.get(0);\n    StreamEntry m2 = result.get(1);\n    StreamEntry m3 = result.get(2);\n\n    // Verify claimed entries\n    assertTrue(m1.getDeliveredCount() > 0);\n    assertTrue(m2.getDeliveredCount() > 0);\n    assertEquals(Long.valueOf(2), m1.getDeliveredCount());\n    assertEquals(Long.valueOf(1), m2.getDeliveredCount());\n\n    // Verify fresh entry\n    assertEquals(Long.valueOf(0), m3.getDeliveredCount());\n  }\n\n  @Test\n  public void buildStreamEntryBinaryListWithClaimedEntryMetadata() {\n    // Test binary version with claimed entry metadata\n    List<Object> data = new ArrayList<>();\n    data.add(\n        createStreamEntryBinaryData(\"1234-12\", \"key\", new byte[] { 0x00, 0x01, 0x02 }, 5000L, 2L));\n\n    List<StreamEntryBinary> result = BuilderFactory.STREAM_ENTRY_BINARY_LIST.build(data);\n\n    assertNotNull(result);\n    assertEquals(1, result.size());\n\n    StreamEntryBinary streamEntry = result.get(0);\n    assertEquals(\"1234-12\", streamEntry.getID().toString());\n    assertEquals(Long.valueOf(5000), streamEntry.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(2), streamEntry.getDeliveredCount());\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ClusterCommandObjectsTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Unit tests for ClusterCommandObjects class.\n */\npublic class ClusterCommandObjectsTest {\n\n  private ClusterCommandObjects clusterCommandObjects;\n\n  @BeforeEach\n  public void setUp() {\n    clusterCommandObjects = new ClusterCommandObjects();\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_singleSlot() {\n    String[] keysValues = { \"{user}:1\", \"value1\", \"{user}:2\", \"value2\", \"{user}:3\", \"value3\" };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n\n    assertEquals(1, result.size());\n    assertEquals(Protocol.Command.MSET, result.get(0).getCommand());\n\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{user}:1\"));\n    assertTrue(argsStrings.contains(\"value1\"));\n    assertTrue(argsStrings.contains(\"{user}:2\"));\n    assertTrue(argsStrings.contains(\"value2\"));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_multipleSlots() {\n    String[] keysValues = { \"key1\", \"value1\", \"key2\", \"value2\", \"key3\", \"value3\" };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n\n    Map<Integer, List<String>> expectedSlots = new HashMap<>();\n    for (int i = 0; i < keysValues.length; i += 2) {\n      int slot = JedisClusterCRC16.getSlot(keysValues[i]);\n      expectedSlots.computeIfAbsent(slot, k -> new ArrayList<>()).add(keysValues[i]);\n      expectedSlots.get(slot).add(keysValues[i + 1]);\n    }\n\n    assertEquals(expectedSlots.size(), result.size());\n    for (CommandArguments cmdArgs : result) {\n      assertEquals(Protocol.Command.MSET, cmdArgs.getCommand());\n    }\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_withParams() {\n    String[] keysValues = { \"{test}:key1\", \"value1\", \"{test}:key2\", \"value2\" };\n    CommandArguments args = new CommandArguments(Protocol.Command.SET);\n    SetParams params = SetParams.setParams().ex(100);\n\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, params);\n\n    assertEquals(1, result.size());\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.stream().anyMatch(s -> s.equalsIgnoreCase(\"EX\")));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_emptyKeysValues() {\n    String[] keysValues = {};\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n    assertEquals(0, result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_oddNumberOfElements() {\n    String[] keysValues = { \"key1\", \"value1\", \"key2\" };\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    assertThrows(IllegalArgumentException.class,\n      () -> clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args, keysValues, null));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_nullParams() {\n    String[] keysValues = { \"{slot}:key1\", \"value1\" };\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n    assertNotNull(result);\n    assertEquals(1, result.size());\n  }\n\n  // Tests for byte[] version\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlotBinary_singleSlot() {\n    byte[][] keysValues = { \"{user}:1\".getBytes(), \"value1\".getBytes(), \"{user}:2\".getBytes(),\n        \"value2\".getBytes(), \"{user}:3\".getBytes(), \"value3\".getBytes() };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n\n    assertEquals(1, result.size());\n    assertEquals(Protocol.Command.MSET, result.get(0).getCommand());\n\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{user}:1\"));\n    assertTrue(argsStrings.contains(\"value1\"));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlotBinary_multipleSlots() {\n    byte[][] keysValues = { \"key1\".getBytes(), \"value1\".getBytes(), \"key2\".getBytes(),\n        \"value2\".getBytes(), \"key3\".getBytes(), \"value3\".getBytes() };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n\n    Map<Integer, List<byte[]>> expectedSlots = new HashMap<>();\n    for (int i = 0; i < keysValues.length; i += 2) {\n      int slot = JedisClusterCRC16.getSlot(keysValues[i]);\n      expectedSlots.computeIfAbsent(slot, k -> new ArrayList<>()).add(keysValues[i]);\n    }\n\n    assertEquals(expectedSlots.size(), result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlotBinary_emptyKeysValues() {\n    byte[][] keysValues = {};\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n    assertEquals(0, result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlotBinary_oddNumberOfElements() {\n    byte[][] keysValues = { \"key1\".getBytes(), \"value1\".getBytes(), \"key2\".getBytes() };\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    assertThrows(IllegalArgumentException.class,\n      () -> clusterCommandObjects.groupArgumentsByKeyValueHashSlot(args, keysValues, null));\n  }\n\n  // Tests for groupArgumentsByKeyHashSlot (String version)\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_singleSlot() {\n    String[] keys = { \"{user}:1\", \"{user}:2\", \"{user}:3\" };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    assertEquals(1, result.size());\n    assertEquals(Protocol.Command.DEL, result.get(0).getCommand());\n\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{user}:1\"));\n    assertTrue(argsStrings.contains(\"{user}:2\"));\n    assertTrue(argsStrings.contains(\"{user}:3\"));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_multipleSlots() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    Map<Integer, List<String>> expectedSlots = new HashMap<>();\n    for (String key : keys) {\n      int slot = JedisClusterCRC16.getSlot(key);\n      expectedSlots.computeIfAbsent(slot, k -> new ArrayList<>()).add(key);\n    }\n\n    assertEquals(expectedSlots.size(), result.size());\n    for (CommandArguments cmdArgs : result) {\n      assertEquals(Protocol.Command.DEL, cmdArgs.getCommand());\n    }\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_withParams() {\n    String[] keys = { \"{test}:key1\", \"{test}:key2\" };\n    CommandArguments args = new CommandArguments(Protocol.Command.EXISTS);\n    SetParams params = SetParams.setParams().ex(100);\n\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      params);\n\n    assertEquals(1, result.size());\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.stream().anyMatch(s -> s.equalsIgnoreCase(\"EX\")));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_emptyKeys() {\n    String[] keys = {};\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n    assertEquals(0, result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_singleKey() {\n    String[] keys = { \"singleKey\" };\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    assertEquals(1, result.size());\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"singleKey\"));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_nullParams() {\n    String[] keys = { \"{slot}:key1\" };\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n    assertNotNull(result);\n    assertEquals(1, result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_noValuesIncluded() {\n    // Verify that key-only method doesn't include any extra elements\n    String[] keys = { \"{user}:1\", \"{user}:2\" };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MGET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    assertEquals(1, result.size());\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    // Should contain the command (MGET) and the 2 keys, no values\n    assertEquals(3, argsStrings.size());\n    assertTrue(argsStrings.contains(\"{user}:1\"));\n    assertTrue(argsStrings.contains(\"{user}:2\"));\n  }\n\n  // Tests for groupArgumentsByKeyHashSlot (byte[] version)\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlotBinary_singleSlot() {\n    byte[][] keys = { \"{user}:1\".getBytes(), \"{user}:2\".getBytes(), \"{user}:3\".getBytes() };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    assertEquals(1, result.size());\n    assertEquals(Protocol.Command.DEL, result.get(0).getCommand());\n\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{user}:1\"));\n    assertTrue(argsStrings.contains(\"{user}:2\"));\n    assertTrue(argsStrings.contains(\"{user}:3\"));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlotBinary_multipleSlots() {\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes() };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    Map<Integer, List<byte[]>> expectedSlots = new HashMap<>();\n    for (byte[] key : keys) {\n      int slot = JedisClusterCRC16.getSlot(key);\n      expectedSlots.computeIfAbsent(slot, k -> new ArrayList<>()).add(key);\n    }\n\n    assertEquals(expectedSlots.size(), result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlotBinary_emptyKeys() {\n    byte[][] keys = {};\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n    assertEquals(0, result.size());\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlotBinary_singleKey() {\n    byte[][] keys = { \"singleKey\".getBytes() };\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    assertEquals(1, result.size());\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"singleKey\"));\n  }\n\n  @Test\n  public void testGroupArgumentsByKeyHashSlotBinary_noValuesIncluded() {\n    // Verify that key-only method doesn't include any extra elements\n    byte[][] keys = { \"{user}:1\".getBytes(), \"{user}:2\".getBytes() };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MGET);\n    List<CommandArguments> result = clusterCommandObjects.groupArgumentsByKeyHashSlot(args, keys,\n      null);\n\n    assertEquals(1, result.size());\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    // Should contain the command (MGET) and the 2 keys, no values\n    assertEquals(3, argsStrings.size());\n    assertTrue(argsStrings.contains(\"{user}:1\"));\n    assertTrue(argsStrings.contains(\"{user}:2\"));\n  }\n\n  private List<String> extractArgsAsStrings(CommandArguments cmdArgs) {\n    List<String> result = new ArrayList<>();\n    for (Rawable rawable : cmdArgs) {\n      result.add(SafeEncoder.encode(rawable.getRaw()));\n    }\n    return result;\n  }\n\n  @Test\n  public void testGetKeyHashSlots_withRawableFromByteArray() {\n    CommandArguments args = new CommandArguments(Protocol.Command.GET);\n    Rawable rawableKey = redis.clients.jedis.args.RawableFactory.from(\"testkey\".getBytes());\n\n    // Using key() with a Rawable should store the Rawable object in the keys list\n    args.key(rawableKey);\n\n    // This should not throw an exception\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(1, slots.size());\n    assertTrue(slots.contains(JedisClusterCRC16.getSlot(\"testkey\".getBytes())));\n  }\n\n  @Test\n  public void testGetKeyHashSlots_withRawableFromString() {\n    CommandArguments args = new CommandArguments(Protocol.Command.GET);\n    Rawable rawableKey = redis.clients.jedis.args.RawableFactory.from(\"testkey\");\n\n    args.key(rawableKey);\n\n    // This should not throw an exception\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(1, slots.size());\n    assertTrue(slots.contains(JedisClusterCRC16.getSlot(\"testkey\")));\n  }\n\n  @Test\n  public void testGetKeyHashSlots_withMultipleRawableKeys() {\n    CommandArguments args = new CommandArguments(Protocol.Command.MGET);\n    Rawable rawableKey1 = redis.clients.jedis.args.RawableFactory.from(\"{user}:1\".getBytes());\n    Rawable rawableKey2 = redis.clients.jedis.args.RawableFactory.from(\"{user}:2\".getBytes());\n\n    args.key(rawableKey1);\n    args.key(rawableKey2);\n\n    // This should not throw an exception\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(1, slots.size());\n    assertTrue(slots.contains(JedisClusterCRC16.getSlot(\"{user}:1\")));\n  }\n\n  @Test\n  public void testGetKeyHashSlots_withMixedRawableAndStringKeys() {\n    CommandArguments args = new CommandArguments(Protocol.Command.MGET);\n\n    // First add a String key - this works fine\n    args.key(\"stringKey\");\n\n    // Then add a Rawable key\n    Rawable rawableKey = redis.clients.jedis.args.RawableFactory.from(\"rawableKey\".getBytes());\n    args.key(rawableKey);\n\n    // This should not throw an exception\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(2, slots.size());\n  }\n\n  /**\n   * Test that using a mix of Rawable and byte[] keys throws ClassCastException when computing hash\n   * slots.\n   */\n  @Test\n  public void testGetKeyHashSlots_withMixedRawableAndByteArrayKeys() {\n    CommandArguments args = new CommandArguments(Protocol.Command.MGET);\n\n    // First add a byte[] key - this works fine\n    args.key(\"byteKey\".getBytes());\n\n    // Then add a Rawable key\n    Rawable rawableKey = redis.clients.jedis.args.RawableFactory.from(\"rawableKey\".getBytes());\n    args.key(rawableKey);\n\n    // This should not throw an exception\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(2, slots.size());\n  }\n\n  /**\n   * Verify that String keys work correctly (baseline test).\n   */\n  @Test\n  public void testGetKeyHashSlots_withStringKey_worksCorrectly() {\n    CommandArguments args = new CommandArguments(Protocol.Command.GET);\n    String key = \"testkey\";\n\n    args.key(key);\n\n    // String keys should work correctly\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(1, slots.size());\n    assertTrue(slots.contains(JedisClusterCRC16.getSlot(key)));\n  }\n\n  /**\n   * Verify that byte[] keys work correctly (baseline test).\n   */\n  @Test\n  public void testGetKeyHashSlots_withByteArrayKey_worksCorrectly() {\n    CommandArguments args = new CommandArguments(Protocol.Command.GET);\n    byte[] key = \"testkey\".getBytes();\n\n    args.key(key);\n\n    // byte[] keys should work correctly\n    java.util.Set<Integer> slots = args.getKeyHashSlots();\n    assertEquals(1, slots.size());\n    assertTrue(slots.contains(JedisClusterCRC16.getSlot(key)));\n  }\n\n  // ==================== Key Preprocessor Hash Slot Tests ====================\n  // These tests verify that the keyPreProcessor is properly accounted for when\n  // calculating hash slots for multi-shard grouping.\n\n  /**\n   * Test that demonstrates the bug where groupArgumentsByKeyHashSlot calculates hash slots from the\n   * original keys but the keyPreProcessor transforms them into different keys with different hash\n   * slots. Bug: When a prefix like \"{prefix}:\" is added by keyPreProcessor, the original keys\n   * \"key1\" and \"key2\" might hash to different slots, but after prefixing they become\n   * \"{prefix}:key1\" and \"{prefix}:key2\" which hash to the same slot (due to the hash tag). The slot\n   * calculation should use the preprocessed keys.\n   */\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_withKeyPreProcessor_usesPreprocessedSlot() {\n    // Create ClusterCommandObjects with a key preprocessor that adds a hash tag prefix\n    ClusterCommandObjects clusterCmdObjects = new ClusterCommandObjects();\n    // The prefix \"{sameSlot}:\" ensures all keys hash to the same slot\n    clusterCmdObjects.setKeyArgumentPreProcessor(\n      new redis.clients.jedis.util.PrefixedKeyArgumentPreProcessor(\"{sameSlot}:\"));\n\n    // These keys hash to different slots without the prefix\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n    int slot1 = JedisClusterCRC16.getSlot(\"key1\");\n    int slot2 = JedisClusterCRC16.getSlot(\"key2\");\n    int slot3 = JedisClusterCRC16.getSlot(\"key3\");\n    // Verify the original keys hash to at least 2 different slots\n    assertTrue(slot1 != slot2 || slot2 != slot3 || slot1 != slot3,\n      \"Test setup error: original keys should hash to different slots\");\n\n    // After preprocessing, all keys should hash to the same slot because of the hash tag\n    int expectedSlot = JedisClusterCRC16.getSlot(\"{sameSlot}:key1\");\n    assertEquals(expectedSlot, JedisClusterCRC16.getSlot(\"{sameSlot}:key2\"),\n      \"Preprocessed keys should hash to the same slot due to hash tag\");\n    assertEquals(expectedSlot, JedisClusterCRC16.getSlot(\"{sameSlot}:key3\"),\n      \"Preprocessed keys should hash to the same slot due to hash tag\");\n\n    // Call groupArgumentsByKeyHashSlot\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCmdObjects.groupArgumentsByKeyHashSlot(args, keys, null);\n\n    // BUG REPRODUCTION: With the bug, this would return multiple CommandArguments\n    // because the slot calculation uses the original keys which hash to different slots.\n    // The fix should make this return 1 CommandArguments because after preprocessing,\n    // all keys hash to the same slot (due to the \"{sameSlot}:\" hash tag prefix).\n    assertEquals(1, result.size(),\n      \"After preprocessing with hash tag prefix, all keys should group into one slot. \" + \"Got \"\n          + result.size()\n          + \" groups instead of 1, indicating slot calculation used original keys.\");\n\n    // Verify the preprocessed keys are in the result\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key1\"));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key2\"));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key3\"));\n  }\n\n  /**\n   * Test that groupArgumentsByKeyValueHashSlot also accounts for keyPreProcessor when calculating\n   * hash slots for key-value pairs.\n   */\n  @Test\n  public void testGroupArgumentsByKeyValueHashSlot_withKeyPreProcessor_usesPreprocessedSlot() {\n    ClusterCommandObjects clusterCmdObjects = new ClusterCommandObjects();\n    clusterCmdObjects.setKeyArgumentPreProcessor(\n      new redis.clients.jedis.util.PrefixedKeyArgumentPreProcessor(\"{sameSlot}:\"));\n\n    // These key-value pairs have keys that hash to different slots without the prefix\n    String[] keysValues = { \"key1\", \"value1\", \"key2\", \"value2\", \"key3\", \"value3\" };\n\n    // Verify original keys hash to different slots\n    int slot1 = JedisClusterCRC16.getSlot(\"key1\");\n    int slot2 = JedisClusterCRC16.getSlot(\"key2\");\n    int slot3 = JedisClusterCRC16.getSlot(\"key3\");\n    assertTrue(slot1 != slot2 || slot2 != slot3 || slot1 != slot3,\n      \"Test setup error: original keys should hash to different slots\");\n\n    CommandArguments args = new CommandArguments(Protocol.Command.MSET);\n    List<CommandArguments> result = clusterCmdObjects.groupArgumentsByKeyValueHashSlot(args,\n      keysValues, null);\n\n    // With the fix, all keys should group into one slot\n    assertEquals(1, result.size(),\n      \"After preprocessing with hash tag prefix, all keys should group into one slot\");\n\n    // Verify the preprocessed keys and values are in the result\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key1\"));\n    assertTrue(argsStrings.contains(\"value1\"));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key2\"));\n    assertTrue(argsStrings.contains(\"value2\"));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key3\"));\n    assertTrue(argsStrings.contains(\"value3\"));\n  }\n\n  /**\n   * Test the reverse case: keys that originally hash to the same slot but after preprocessing hash\n   * to different slots.\n   */\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_withKeyPreProcessor_differentSlotsAfterPreprocess() {\n    ClusterCommandObjects clusterCmdObjects = new ClusterCommandObjects();\n    // This preprocessor changes the hash tag, causing keys to hash to different slots\n    clusterCmdObjects.setKeyArgumentPreProcessor(key -> {\n      String keyStr = (String) key;\n      // Remove the hash tag, making keys hash based on their full content\n      return keyStr.replace(\"{same}:\", \"different:\");\n    });\n\n    // These keys all hash to the same slot due to the {same} hash tag\n    String[] keys = { \"{same}:key1\", \"{same}:key2\", \"{same}:key3\" };\n    int originalSlot = JedisClusterCRC16.getSlot(\"{same}:key1\");\n    assertEquals(originalSlot, JedisClusterCRC16.getSlot(\"{same}:key2\"));\n    assertEquals(originalSlot, JedisClusterCRC16.getSlot(\"{same}:key3\"));\n\n    // After preprocessing, keys become \"different:key1\", etc. which hash to different slots\n    int slot1 = JedisClusterCRC16.getSlot(\"different:key1\");\n    int slot2 = JedisClusterCRC16.getSlot(\"different:key2\");\n    int slot3 = JedisClusterCRC16.getSlot(\"different:key3\");\n    // At least some should be different (depending on actual hash values)\n    assertTrue(slot1 != slot2 || slot2 != slot3 || slot1 != slot3,\n      \"Test setup: preprocessed keys should hash to different slots\");\n\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCmdObjects.groupArgumentsByKeyHashSlot(args, keys, null);\n\n    // With the fix, keys should be grouped by their preprocessed slot values\n    // which should result in multiple groups (not just 1 as with original keys)\n    assertTrue(result.size() > 1,\n      \"After preprocessing removes hash tag, keys should be in different slots. \" + \"Got \"\n          + result.size() + \" groups.\");\n  }\n\n  /**\n   * Test with byte[] keys and keyPreProcessor.\n   */\n  @Test\n  public void testGroupArgumentsByKeyHashSlot_withKeyPreProcessor_binaryKeys() {\n    ClusterCommandObjects clusterCmdObjects = new ClusterCommandObjects();\n    clusterCmdObjects.setKeyArgumentPreProcessor(\n      new redis.clients.jedis.util.PrefixedKeyArgumentPreProcessor(\"{sameSlot}:\"));\n\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes() };\n\n    CommandArguments args = new CommandArguments(Protocol.Command.DEL);\n    List<CommandArguments> result = clusterCmdObjects.groupArgumentsByKeyHashSlot(args, keys, null);\n\n    // With the fix, all keys should group into one slot due to the hash tag prefix\n    assertEquals(1, result.size(),\n      \"After preprocessing with hash tag prefix, all binary keys should group into one slot\");\n\n    // Verify the preprocessed keys are in the result\n    List<String> argsStrings = extractArgsAsStrings(result.get(0));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key1\"));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key2\"));\n    assertTrue(argsStrings.contains(\"{sameSlot}:key3\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ClusterPipeliningTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.Protocol.CLUSTER_HASHSLOTS;\n\nimport java.util.*;\n\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\n\nimport org.junit.jupiter.api.*;\nimport redis.clients.jedis.args.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\nimport redis.clients.jedis.resps.StreamEntry;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.GeoCoordinateMatcher;\nimport redis.clients.jedis.util.GeoRadiusResponseMatcher;\nimport redis.clients.jedis.util.JedisClusterTestUtil;\nimport redis.clients.jedis.util.SafeEncoder;\n\n@Tag(\"integration\")\npublic class ClusterPipeliningTest {\n\n  private static EndpointConfig endpoint;\n\n  private static DefaultJedisClientConfig DEFAULT_CLIENT_CONFIG;\n\n  private static Jedis node1;\n  private static Jedis node2;\n  private static Jedis node3;\n\n  private static HostAndPort nodeInfo1;\n  private static HostAndPort nodeInfo2;\n  private static HostAndPort nodeInfo3;\n  private static Set<HostAndPort> nodes;\n\n  @BeforeAll\n  public static void setUp() throws InterruptedException {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-unbound\");\n    DEFAULT_CLIENT_CONFIG = endpoint.getClientConfigBuilder().build();\n    nodeInfo1 = endpoint.getHostsAndPorts().get(0);\n    nodeInfo2 = endpoint.getHostsAndPorts().get(1);\n    nodeInfo3 = endpoint.getHostsAndPorts().get(2);\n    nodes = new HashSet<>(Arrays.asList(nodeInfo1, nodeInfo2, nodeInfo3));\n\n    node1 = new Jedis(nodeInfo1);\n    node1.auth(endpoint.getPassword());\n    node1.flushAll();\n\n    node2 = new Jedis(nodeInfo2);\n    node2.auth(endpoint.getPassword());\n    node2.flushAll();\n\n    node3 = new Jedis(nodeInfo3);\n    node3.auth(endpoint.getPassword());\n    node3.flushAll();\n\n    // add nodes to cluster\n    node1.clusterMeet(nodeInfo2.getHost(), nodeInfo2.getPort());\n    node1.clusterMeet(nodeInfo2.getHost(), nodeInfo3.getPort());\n\n    // split available slots across the three nodes\n    int slotsPerNode = CLUSTER_HASHSLOTS / 3;\n    int[] node1Slots = new int[slotsPerNode];\n    int[] node2Slots = new int[slotsPerNode + 1];\n    int[] node3Slots = new int[slotsPerNode];\n    for (int i = 0, slot1 = 0, slot2 = 0, slot3 = 0; i < CLUSTER_HASHSLOTS; i++) {\n      if (i < slotsPerNode) {\n        node1Slots[slot1++] = i;\n      } else if (i > slotsPerNode * 2) {\n        node3Slots[slot3++] = i;\n      } else {\n        node2Slots[slot2++] = i;\n      }\n    }\n\n    node1.clusterAddSlots(node1Slots);\n    node2.clusterAddSlots(node2Slots);\n    node3.clusterAddSlots(node3Slots);\n\n    JedisClusterTestUtil.waitForClusterReady(node1, node2, node3);\n  }\n\n  @BeforeEach\n  public void prepare() {\n    node1.flushAll();\n    node2.flushAll();\n    node3.flushAll();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    node1.flushDB();\n    node2.flushDB();\n    node3.flushDB();\n  }\n\n  @AfterAll\n  public static void tearDown() throws InterruptedException {\n    if (node1 != null) node1.clusterReset(ClusterResetType.SOFT);\n    if (node2 != null) node2.clusterReset(ClusterResetType.SOFT);\n    if (node3 != null) node3.clusterReset(ClusterResetType.SOFT);\n  }\n\n  @Test\n  public void constructorClientConfig() {\n    try (ClusterPipeline pipe = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      Response<String> r1 = pipe.set(\"key1\", \"value1\");\n      Response<String> r2 = pipe.set(\"key2\", \"value2\");\n      Response<String> r3 = pipe.set(\"key3\", \"value3\");\n      Response<String> r4 = pipe.get(\"key1\");\n      Response<String> r5 = pipe.get(\"key2\");\n      Response<String> r6 = pipe.get(\"key3\");\n\n      pipe.sync();\n      assertEquals(\"OK\", r1.get());\n      assertEquals(\"OK\", r2.get());\n      assertEquals(\"OK\", r3.get());\n      assertEquals(\"value1\", r4.get());\n      assertEquals(\"value2\", r5.get());\n      assertEquals(\"value3\", r6.get());\n    }\n  }\n\n  @Test\n  public void constructorPoolConfig() {\n    try (ClusterPipeline pipe = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG, new ConnectionPoolConfig())) {\n      Response<String> r1 = pipe.set(\"key1\", \"value1\");\n      Response<String> r2 = pipe.set(\"key2\", \"value2\");\n      Response<String> r3 = pipe.set(\"key3\", \"value3\");\n      Response<String> r4 = pipe.get(\"key1\");\n      Response<String> r5 = pipe.get(\"key2\");\n      Response<String> r6 = pipe.get(\"key3\");\n\n      pipe.sync();\n      assertEquals(\"OK\", r1.get());\n      assertEquals(\"OK\", r2.get());\n      assertEquals(\"OK\", r3.get());\n      assertEquals(\"value1\", r4.get());\n      assertEquals(\"value2\", r5.get());\n      assertEquals(\"value3\", r6.get());\n    }\n  }\n\n  @Test\n  public void constructorConnectionProvider() {\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n        ClusterPipeline pipeline = new ClusterPipeline(provider)) {\n\n      Response<String> r1 = pipeline.set(\"key1\", \"value1\");\n      Response<String> r2 = pipeline.set(\"key2\", \"value2\");\n      Response<String> r3 = pipeline.set(\"key3\", \"value3\");\n      Response<String> r4 = pipeline.get(\"key1\");\n      Response<String> r5 = pipeline.get(\"key2\");\n      Response<String> r6 = pipeline.get(\"key3\");\n\n      pipeline.sync();\n      assertEquals(\"OK\", r1.get());\n      assertEquals(\"OK\", r2.get());\n      assertEquals(\"OK\", r3.get());\n      assertEquals(\"value1\", r4.get());\n      assertEquals(\"value2\", r5.get());\n      assertEquals(\"value3\", r6.get());\n    }\n  }\n\n  @Test\n  public void clusterPipelined() {\n    try (RedisClusterClient cluster = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build();\n        ClusterPipeline pipeline = cluster.pipelined()) {\n\n      Response<String> r1 = pipeline.set(\"key1\", \"value1\");\n      Response<String> r2 = pipeline.set(\"key2\", \"value2\");\n      Response<String> r3 = pipeline.set(\"key3\", \"value3\");\n      Response<String> r4 = pipeline.get(\"key1\");\n      Response<String> r5 = pipeline.get(\"key2\");\n      Response<String> r6 = pipeline.get(\"key3\");\n\n      pipeline.sync();\n\n      assertEquals(\"OK\", r1.get());\n      assertEquals(\"OK\", r2.get());\n      assertEquals(\"OK\", r3.get());\n      assertEquals(\"value1\", r4.get());\n      assertEquals(\"value2\", r5.get());\n      assertEquals(\"value3\", r6.get());\n    }\n  }\n\n  @Test\n  public void intermediateSync() {\n    try (RedisClusterClient cluster = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build();\n        ClusterPipeline pipeline = cluster.pipelined()) {\n\n      Response<String> r1 = pipeline.set(\"key1\", \"value1\");\n      Response<String> r2 = pipeline.set(\"key2\", \"value2\");\n      Response<String> r3 = pipeline.set(\"key3\", \"value3\");\n\n      pipeline.sync();\n\n      assertEquals(\"OK\", r1.get());\n      assertEquals(\"OK\", r2.get());\n      assertEquals(\"OK\", r3.get());\n\n      Response<String> r4 = pipeline.get(\"key1\");\n      Response<String> r5 = pipeline.get(\"key2\");\n      Response<String> r6 = pipeline.get(\"key3\");\n\n      pipeline.sync();\n\n      assertEquals(\"value1\", r4.get());\n      assertEquals(\"value2\", r5.get());\n      assertEquals(\"value3\", r6.get());\n    }\n  }\n\n  @Test\n  public void intermediateSyncs() {\n    try (RedisClusterClient cluster = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build();\n        ClusterPipeline pipeline = cluster.pipelined()) {\n\n      Response<String> r1 = pipeline.set(\"key1\", \"value1\");\n      Response<String> r2 = pipeline.set(\"key2\", \"value2\");\n      Response<String> r3 = pipeline.set(\"key3\", \"value3\");\n\n      for (int i = 0; i < 100; i++) pipeline.sync();\n\n      assertEquals(\"OK\", r1.get());\n      assertEquals(\"OK\", r2.get());\n      assertEquals(\"OK\", r3.get());\n\n      Response<String> r4 = pipeline.get(\"key1\");\n      Response<String> r5 = pipeline.get(\"key2\");\n      Response<String> r6 = pipeline.get(\"key3\");\n\n      for (int i = 0; i < 100; i++) pipeline.sync();\n\n      assertEquals(\"value1\", r4.get());\n      assertEquals(\"value2\", r5.get());\n      assertEquals(\"value3\", r6.get());\n    }\n  }\n\n  @Test\n  public void pipelineResponse() {\n    try (RedisClusterClient jc = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      jc.set(\"string\", \"foo\");\n      jc.lpush(\"list\", \"foo\");\n      jc.hset(\"hash\", \"foo\", \"bar\");\n      jc.zadd(\"zset\", 1, \"foo\");\n      jc.sadd(\"set\", \"foo\");\n      jc.setrange(\"setrange\", 0, \"0123456789\");\n      byte[] bytesForSetRange = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n      jc.setrange(\"setrangebytes\".getBytes(), 0, bytesForSetRange);\n    }\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n\n      Response<String> string = p.get(\"string\");\n      Response<String> list = p.lpop(\"list\");\n      Response<String> hash = p.hget(\"hash\", \"foo\");\n      Response<List<String>> zset = p.zrange(\"zset\", 0, -1);\n      Response<String> set = p.spop(\"set\");\n      Response<Boolean> blist = p.exists(\"list\");\n      Response<Double> zincrby = p.zincrby(\"zset\", 1, \"foo\");\n      Response<Long> zcard = p.zcard(\"zset\");\n      p.lpush(\"list\", \"bar\");\n      Response<List<String>> lrange = p.lrange(\"list\", 0, -1);\n      Response<Map<String, String>> hgetAll = p.hgetAll(\"hash\");\n      p.sadd(\"set\", \"foo\");\n      Response<Set<String>> smembers = p.smembers(\"set\");\n      Response<List<Tuple>> zrangeWithScores = p.zrangeWithScores(\"zset\", 0, -1);\n      Response<String> getrange = p.getrange(\"setrange\", 1, 3);\n      Response<byte[]> getrangeBytes = p.getrange(\"setrangebytes\".getBytes(), 6, 8);\n      p.sync();\n\n      assertEquals(\"foo\", string.get());\n      assertEquals(\"foo\", list.get());\n      assertEquals(\"bar\", hash.get());\n      assertEquals(\"foo\", zset.get().iterator().next());\n      assertEquals(\"foo\", set.get());\n      assertEquals(false, blist.get());\n      assertEquals(Double.valueOf(2), zincrby.get());\n      assertEquals(Long.valueOf(1), zcard.get());\n      assertEquals(1, lrange.get().size());\n      assertNotNull(hgetAll.get().get(\"foo\"));\n      assertEquals(1, smembers.get().size());\n      assertEquals(1, zrangeWithScores.get().size());\n      assertEquals(\"123\", getrange.get());\n      byte[] expectedGetRangeBytes = {6, 7, 8};\n      assertArrayEquals(expectedGetRangeBytes, getrangeBytes.get());\n    }\n  }\n\n  @Test\n  public void pipelineBinarySafeHashCommands() {\n    try (RedisClusterClient jc = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      jc.hset(\"key\".getBytes(), \"f1\".getBytes(), \"v111\".getBytes());\n      jc.hset(\"key\".getBytes(), \"f22\".getBytes(), \"v2222\".getBytes());\n    }\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<Map<byte[], byte[]>> fmap = p.hgetAll(\"key\".getBytes());\n      Response<Set<byte[]>> fkeys = p.hkeys(\"key\".getBytes());\n      Response<List<byte[]>> fordered = p.hmget(\"key\".getBytes(), \"f22\".getBytes(), \"f1\".getBytes());\n      Response<List<byte[]>> fvals = p.hvals(\"key\".getBytes());\n      p.sync();\n\n      assertNotNull(fmap.get());\n      // we have to do these strange contortions because byte[] is not a very good key for a java\n      // Map. It only works with equality (you need the exact key object to retrieve the value).\n      // I recommend we switch to using ByteBuffer or something similar:\n      // http://stackoverflow.com/questions/1058149/using-a-byte-array-as-hashmap-key-java\n      Map<byte[], byte[]> map = fmap.get();\n      Set<byte[]> mapKeys = map.keySet();\n      Iterator<byte[]> iterMap = mapKeys.iterator();\n      byte[] firstMapKey = iterMap.next();\n      byte[] secondMapKey = iterMap.next();\n      assertFalse(iterMap.hasNext());\n      verifyHasBothValues(firstMapKey, secondMapKey, \"f1\".getBytes(), \"f22\".getBytes());\n      byte[] firstMapValue = map.get(firstMapKey);\n      byte[] secondMapValue = map.get(secondMapKey);\n      verifyHasBothValues(firstMapValue, secondMapValue, \"v111\".getBytes(), \"v2222\".getBytes());\n\n      assertNotNull(fkeys.get());\n      Iterator<byte[]> iter = fkeys.get().iterator();\n      byte[] firstKey = iter.next();\n      byte[] secondKey = iter.next();\n      assertFalse(iter.hasNext());\n      verifyHasBothValues(firstKey, secondKey, \"f1\".getBytes(), \"f22\".getBytes());\n\n      assertNotNull(fordered.get());\n      assertArrayEquals(\"v2222\".getBytes(), fordered.get().get(0));\n      assertArrayEquals(\"v111\".getBytes(), fordered.get().get(1));\n\n      assertNotNull(fvals.get());\n      assertEquals(2, fvals.get().size());\n      byte[] firstValue = fvals.get().get(0);\n      byte[] secondValue = fvals.get().get(1);\n      verifyHasBothValues(firstValue, secondValue, \"v111\".getBytes(), \"v2222\".getBytes());\n    }\n  }\n\n  private void verifyHasBothValues(byte[] firstKey, byte[] secondKey, byte[] value1, byte[] value2) {\n    assertFalse(Arrays.equals(firstKey, secondKey));\n    assertTrue(Arrays.equals(firstKey, value1) || Arrays.equals(firstKey, value2));\n    assertTrue(Arrays.equals(secondKey, value1) || Arrays.equals(secondKey, value2));\n  }\n\n  @Test\n  public void pipelineResponseWithinPipeline() {\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<String> string = p.get(\"string\");\n      assertThrows(IllegalStateException.class,string::get);\n      p.sync();\n    }\n  }\n\n  @Test\n  public void pipelineWithPubSub() {\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline pipelined = new ClusterPipeline(provider);\n      Response<Long> p1 = pipelined.publish(\"foo\", \"bar\");\n      Response<Long> p2 = pipelined.publish(\"foo\".getBytes(), \"bar\".getBytes());\n      pipelined.sync();\n      assertEquals(0, p1.get().longValue());\n      assertEquals(0, p2.get().longValue());\n    }\n  }\n\n  @Test\n  public void canRetrieveUnsetKey() {\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<String> shouldNotExist = p.get(UUID.randomUUID().toString());\n      p.sync();\n      assertNull(shouldNotExist.get());\n    }\n  }\n\n  @Test\n  public void piplineWithError() {\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      p.set(\"foo\", \"bar\");\n      Response<Set<String>> error = p.smembers(\"foo\");\n      Response<String> r = p.get(\"foo\");\n      p.sync();\n      try {\n        error.get();\n        fail();\n      } catch (JedisDataException e) {\n        // that is fine we should be here\n      }\n      assertEquals(r.get(), \"bar\");\n    }\n  }\n\n  @Test\n  public void getSetParams() {\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<String> r1 = p.set(\"key1\", \"value1\");\n    Response<String> r2 = p.set(\"key2\", \"value2\");\n    Response<String> r3 = p.set(\"key3\", \"value3\");\n    Response<String> r4 = p.set(\"key3\", \"value4\", new SetParams().nx()); // Should not be updated\n    Response<String> r5 = p.get(\"key1\");\n    Response<String> r6 = p.get(\"key2\");\n    Response<String> r7 = p.get(\"key3\");\n\n    p.sync();\n    assertEquals(\"OK\", r1.get());\n    assertEquals(\"OK\", r2.get());\n    assertEquals(\"OK\", r3.get());\n    assertNull(r4.get());\n    assertEquals(\"value1\", r5.get());\n    assertEquals(\"value2\", r6.get());\n    assertEquals(\"value3\", r7.get());\n  }\n\n  @Test\n  public void clusterPipelineSort() {\n    List<String> sorted = new ArrayList<>();\n    sorted.add(\"1\");\n    sorted.add(\"2\");\n    sorted.add(\"3\");\n    sorted.add(\"4\");\n    sorted.add(\"5\");\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.rpush(\"key1\", \"2\", \"3\", \"5\", \"1\", \"4\");\n    Response<List<String>> r2 = p.sort(\"key1\");\n    Response<Long> r3 = p.sort(\"key1\", \"key1\");\n    Response<List<String>> r4 = p.lrange(\"key1\", 0, 4);\n    Response<List<String>> r5 = p.sort(\"key1\", new SortingParams().limit(0, 2));\n    Response<Long> r6 = p.sort(\"key1\", new SortingParams().desc(), \"key1\");\n    Response<List<String>> r7 = p.lrange(\"key1\", 0, 4);\n\n    p.sync();\n    assertEquals(Long.valueOf(5), r1.get());\n    assertEquals(sorted, r2.get());\n    assertEquals(Long.valueOf(5), r3.get());\n    assertEquals(sorted, r4.get());\n    assertEquals(2, r5.get().size());\n    assertEquals(Long.valueOf(5), r6.get());\n    Collections.reverse(sorted);\n    assertEquals(sorted, r7.get());\n  }\n\n  @Test\n  public void clusterPipelineList() {\n    List<String> vals = new ArrayList<>();\n    vals.add(\"foobar\");\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.lpush(\"my{list}\", \"hello\", \"hello\", \"foo\", \"foo\"); // [\"foo\", \"foo\", \"hello\", \"hello\"]\n    Response<Long> r2 = p.rpush(\"my{newlist}\", \"hello\", \"hello\", \"foo\", \"foo\");  // [\"hello\", \"hello\", \"foo\", \"foo\"]\n    Response<Long> r3 = p.lpos(\"my{list}\", \"foo\");\n    Response<Long> r4 = p.lpos(\"my{list}\", \"foo\", new LPosParams().maxlen(1));\n    Response<List<Long>> r5 = p.lpos(\"my{list}\", \"foo\", new LPosParams().maxlen(1), 2);\n    Response<String> r6 = p.ltrim(\"my{list}\", 2, 3); // [\"hello\", \"hello\"]\n    Response<Long> r7 = p.llen(\"my{list}\");\n    Response<String> r8 = p.lindex(\"my{list}\", -1);\n    Response<String> r9 = p.lset(\"my{list}\", 1, \"foobar\"); // [\"hello\", \"foobar\"]\n    Response<Long> r10 = p.lrem(\"my{list}\", 1, \"hello\"); // [\"foobar\"]\n    Response<List<String>> r11 = p.lrange(\"my{list}\", 0, 10);\n    Response<String> r12 = p.rpop(\"my{newlist}\"); // [\"hello\", \"hello\", \"foo\"]\n    Response<List<String>> r13 = p.lpop(\"my{list}\", 1); // [\"foobar\"]\n    Response<List<String>> r14 = p.rpop(\"my{newlist}\", 2); // [\"hello\"]\n    Response<Long> r15 = p.linsert(\"my{newlist}\", ListPosition.AFTER, \"hello\", \"world\"); // [\"hello\", \"world\"]\n    Response<Long> r16 = p.lpushx(\"myother{newlist}\", \"foo\", \"bar\");\n    Response<Long> r17 = p.rpushx(\"myother{newlist}\", \"foo\", \"bar\");\n    Response<String> r18 = p.rpoplpush(\"my{newlist}\", \"myother{newlist}\");\n    Response<String> r19 = p.lmove(\"my{newlist}\", \"myother{newlist}\", ListDirection.LEFT, ListDirection.RIGHT);\n\n    p.sync();\n    assertEquals(Long.valueOf(4), r1.get());\n    assertEquals(Long.valueOf(4), r2.get());\n    assertEquals(Long.valueOf(0), r3.get());\n    assertEquals(Long.valueOf(0), r4.get());\n    assertEquals(1, r5.get().size());\n    assertEquals(\"OK\", r6.get());\n    assertEquals(Long.valueOf(2), r7.get());\n    assertEquals(\"hello\", r8.get());\n    assertEquals(\"OK\", r9.get());\n    assertEquals(Long.valueOf(1), r10.get());\n    assertEquals(vals, r11.get());\n    assertEquals(\"foo\", r12.get());\n    assertEquals(vals, r13.get());\n    assertEquals(2, r14.get().size());\n    assertEquals(Long.valueOf(2), r15.get());\n    assertEquals(Long.valueOf(0), r16.get());\n    assertEquals(Long.valueOf(0), r17.get());\n    assertEquals(\"world\", r18.get());\n    assertEquals(\"hello\", r19.get());\n  }\n\n  @Test\n  public void clusterPipelineSet() {\n    Set<String> diff = new HashSet<>();\n    diff.add(\"bar\");\n    diff.add(\"foo\");\n\n    Set<String> union = new HashSet<>();\n    union.add(\"hello\");\n    union.add(\"world\");\n    union.add(\"bar\");\n    union.add(\"foo\");\n\n    Set<String> inter = new HashSet<>();\n    inter.add(\"world\");\n    inter.add(\"hello\");\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.sadd(\"my{set}\", \"hello\", \"hello\", \"world\", \"foo\", \"bar\");\n    p.sadd(\"mynew{set}\", \"hello\", \"hello\", \"world\");\n    Response<Set<String>> r2 = p.sdiff(\"my{set}\", \"mynew{set}\");\n    Response<Long> r3deprecated = p.sdiffStore(\"diffset{set}deprecated\", \"my{set}\", \"mynew{set}\");\n    Response<Long> r3 = p.sdiffstore(\"diffset{set}\", \"my{set}\", \"mynew{set}\");\n    Response<Set<String>> r4 = p.smembers(\"diffset{set}\");\n    Response<Set<String>> r5 = p.sinter(\"my{set}\", \"mynew{set}\");\n    Response<Long> r6 = p.sinterstore(\"interset{set}\", \"my{set}\", \"mynew{set}\");\n    Response<Set<String>> r7 = p.smembers(\"interset{set}\");\n    Response<Set<String>> r8 = p.sunion(\"my{set}\", \"mynew{set}\");\n    Response<Long> r9 = p.sunionstore(\"unionset{set}\", \"my{set}\", \"mynew{set}\");\n    Response<Set<String>> r10 = p.smembers(\"unionset{set}\");\n    Response<Boolean> r11 = p.sismember(\"my{set}\", \"foo\");\n    Response<List<Boolean>> r12 = p.smismember(\"my{set}\", \"foo\", \"foobar\");\n    Response<Long> r13 = p.srem(\"my{set}\", \"foo\");\n    Response<Set<String>> r14 = p.spop(\"my{set}\", 1);\n    Response<Long> r15 = p.scard(\"my{set}\");\n    Response<String> r16 = p.srandmember(\"my{set}\");\n    Response<List<String>> r17 = p.srandmember(\"my{set}\", 2);\n//    Response<Long> r18 = p.smove(\"my{set}\", \"mynew{set}\", \"hello\");\n\n    p.sync();\n    assertEquals(Long.valueOf(4), r1.get());\n    assertEquals(diff, r2.get());\n    assertEquals(Long.valueOf(diff.size()), r3deprecated.get());\n    assertEquals(Long.valueOf(diff.size()), r3.get());\n    assertEquals(diff, r4.get());\n    assertEquals(inter, r5.get());\n    assertEquals(Long.valueOf(inter.size()), r6.get());\n    assertEquals(inter, r7.get());\n    assertEquals(union, r8.get());\n    assertEquals(Long.valueOf(union.size()), r9.get());\n    assertEquals(union, r10.get());\n    assertTrue(r11.get());\n    assertTrue(r12.get().get(0) && !r12.get().get(1));\n    assertEquals(Long.valueOf(1), r13.get());\n    assertTrue(union.containsAll(r14.get()));\n    assertEquals(Long.valueOf(2), r15.get());\n    assertTrue(union.contains(r16.get()));\n    assertTrue(union.containsAll(r17.get()));\n//    assertEquals(Long.valueOf(1), r18.get());\n  }\n\n  @Test\n  public void clusterPipelineSortedSet() {\n    Map<String, Double> hm = new HashMap<>();\n    hm.put(\"a1\", 1d);\n    hm.put(\"a2\", 2d);\n    hm.put(\"a3\", 3d);\n\n    Set<String> members = new HashSet<>(hm.keySet());\n\n    Tuple max = new Tuple(\"a3\", 3d);\n    Tuple min = new Tuple(\"a1\", 1d);\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.zadd(\"myset\", hm);\n    Response<Long> r2 = p.zrank(\"myset\", \"a3\");\n    Response<Long> r3 = p.zrevrank(\"myset\", \"a3\");\n    Response<Long> r4 = p.zrem(\"myset\", \"a1\");\n    Response<Long> r5 = p.zadd(\"myset\", 1d, \"a1\");\n    Response<Long> r6 = p.zadd(\"myotherset\", 2d, \"a1\", new ZAddParams().nx());\n    Response<Double> r7 = p.zaddIncr(\"myset\", 3d, \"a4\", new ZAddParams().xx()); // Should not update\n    Response<List<String>> r8 = p.zrevrange(\"myset\", 0, 0);\n    Response<List<Tuple>> r9 = p.zrevrangeWithScores(\"myset\", 0, 0);\n    Response<String> r10 = p.zrandmember(\"myset\");\n    Response<List<String>> r11 = p.zrandmember(\"myset\", 2);\n    Response<List<Tuple>> r12 = p.zrandmemberWithScores(\"myset\", 1);\n    Response<Double> r13 = p.zscore(\"myset\", \"a1\");\n    Response<List<Double>> r14 = p.zmscore(\"myset\", \"a1\", \"a2\");\n    Response<Tuple> r15 = p.zpopmax(\"myset\");\n    Response<Tuple> r16 = p.zpopmin(\"myset\");\n    Response<Long> r17 = p.zcount(\"myotherset\", 2, 5);\n    Response<Long> r18 = p.zcount(\"myotherset\", \"(2\", \"5\");\n    p.zadd(\"myset\", hm, new ZAddParams().nx()); // return the elements that were popped\n    Response<List<Tuple>> r19 = p.zpopmax(\"myset\", 2);\n    Response<List<Tuple>> r20 = p.zpopmin(\"myset\", 1);\n\n    p.sync();\n    assertEquals(Long.valueOf(3), r1.get());\n    assertEquals(Long.valueOf(2), r2.get());\n    assertEquals(Long.valueOf(0), r3.get());\n    assertEquals(Long.valueOf(1), r4.get());\n    assertEquals(Long.valueOf(1), r5.get());\n    assertEquals(Long.valueOf(1), r6.get());\n    assertNull(r7.get());\n    assertTrue(r8.get().size() == 1 && r8.get().contains(\"a3\"));\n    assertTrue(r9.get().size() == 1 && r9.get().contains(max));\n    assertTrue(members.contains(r10.get()));\n    assertTrue(members.containsAll(r11.get()));\n    assertEquals(1, r12.get().size());\n    assertEquals(Double.valueOf(1), r13.get());\n    assertTrue(hm.values().containsAll(r14.get()));\n    assertEquals(max, r15.get());\n    assertEquals(min, r16.get());\n    assertEquals(Long.valueOf(1), r17.get());\n    assertEquals(Long.valueOf(0), r18.get());\n    assertTrue(r19.get().size() == 2 && r19.get().contains(max));\n    assertTrue(r20.get().size() == 1 && r20.get().contains(min));\n  }\n\n  @Test\n  public void clusterPipelineHash() {\n    Map<String, String> hm = new HashMap<>();\n    hm.put(\"field2\", \"2\");\n    hm.put(\"field3\", \"5\");\n\n    Set<String> keys = new HashSet<>();\n    keys.add(\"field2\");\n\n    List<String> vals = new ArrayList<>();\n    vals.add(\"3.5\");\n\n    List<String> vals2 = new ArrayList<>();\n    vals2.add(\"hello\");\n    vals2.add(null);\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.hset(\"myhash\", \"field1\", \"hello\");\n    Response<Long> r2 = p.hsetnx(\"myhash\", \"field1\", \"hello\");\n    Response<String> r3 = p.hget(\"myhash\", \"field1\");\n    Response<Long> r4 = p.hset(\"myotherhash\", hm);\n    Response<String> r5 = p.hmset(\"mynewhash\", hm);\n    p.hincrBy(\"mynewhash\", \"field2\", 1);\n    Response<Double> r6 = p.hincrByFloat(\"mynewhash\", \"field2\", 0.5);\n    Response<Long> r7 = p.hlen(\"myhash\");\n    Response<Long> r8 = p.hdel(\"mynewhash\", \"field3\");\n    Response<Boolean> r9 = p.hexists(\"mynewhash\", \"field3\");\n    Response<Set<String>> r10 = p.hkeys(\"mynewhash\");\n    Response<List<String>> r11 = p.hvals(\"mynewhash\");\n    Response<List<String>> r12 = p.hmget(\"myhash\", \"field1\", \"field2\");\n    Response<String> r13 = p.hrandfield(\"myotherhash\");\n    Response<List<String>> r14 = p.hrandfield(\"myotherhash\", 4);\n    Response<List<String>> r15 = p.hrandfield(\"myotherhash\", -4);\n    Response<Long> r16 = p.hstrlen(\"myhash\", \"field1\");\n    Response<List<Map.Entry<String, String>>> r17 = p.hrandfieldWithValues(\"myotherhash\", 4);\n    Response<List<Map.Entry<String, String>>> r18 = p.hrandfieldWithValues(\"myotherhash\", -4);\n\n    p.sync();\n    assertEquals(Long.valueOf(1), r1.get());\n    assertEquals(Long.valueOf(0), r2.get());\n    assertEquals(\"hello\", r3.get());\n    assertEquals(Long.valueOf(2), r4.get());\n    assertEquals(\"OK\", r5.get());\n    assertEquals(Double.valueOf(3.5), r6.get());\n    assertEquals(Long.valueOf(1), r7.get());\n    assertEquals(Long.valueOf(1), r8.get());\n    assertFalse(r9.get());\n    assertEquals(keys, r10.get());\n    assertEquals(vals, r11.get());\n    assertEquals(vals2, r12.get());\n    AssertUtil.assertCollectionContains(hm.keySet(), r13.get());\n    assertEquals(2, r14.get().size());\n    assertEquals(4, r15.get().size());\n    assertEquals(Long.valueOf(5), r16.get());\n    assertEquals(2, r17.get().size());\n    assertEquals(4, r18.get().size());\n  }\n\n  @Test\n  public void clusterPipelineGeo() {\n    Map<String, GeoCoordinate> hm = new HashMap<>();\n    hm.put(\"place1\", new GeoCoordinate(2.1909389952632, 41.433791470673));\n    hm.put(\"place2\", new GeoCoordinate(2.1873744593677, 41.406342043777));\n\n    List<String> hashValues = new ArrayList<>();\n    hashValues.add(\"sp3e9yg3kd0\");\n    hashValues.add(\"sp3e9cbc3t0\");\n    hashValues.add(null);\n\n    GeoRadiusParam params = new GeoRadiusParam().withCoord().withHash().withDist();\n    GeoRadiusParam params2 = new GeoRadiusParam().count(1, true);\n    GeoRadiusStoreParam storeParams = new GeoRadiusStoreParam().store(\"radius{#}\");\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.geoadd(\"barcelona\", hm);\n    p.geoadd(\"barcelona{#}\", new GeoAddParams().nx(), hm);\n    Response<Double> r2 = p.geodist(\"barcelona\", \"place1\", \"place2\");\n    Response<Double> r3 = p.geodist(\"barcelona\", \"place1\", \"place2\", GeoUnit.KM);\n    Response<List<String>> r4 = p.geohash(\"barcelona\", \"place1\", \"place2\", \"place3\");\n    Response<List<GeoCoordinate>> r5 = p.geopos(\"barcelona\", \"place1\", \"place2\");\n    Response<List<GeoRadiusResponse>> r6 = p.georadius(\"barcelona\", 2.191, 41.433, 1000, GeoUnit.M);\n    Response<List<GeoRadiusResponse>> r7 = p.georadiusReadonly(\"barcelona\", 2.191, 41.433, 1000, GeoUnit.M);\n    Response<List<GeoRadiusResponse>> r8 = p.georadius(\"barcelona\", 2.191, 41.433, 1, GeoUnit.KM, params);\n    Response<List<GeoRadiusResponse>> r9 = p.georadiusReadonly(\"barcelona\", 2.191, 41.433, 1, GeoUnit.KM, params);\n    Response<Long> r10 = p.georadiusStore(\"barcelona{#}\", 2.191, 41.433, 1000, GeoUnit.M, params2, storeParams);\n    Response<List<String>> r11 = p.zrange(\"radius{#}\", 0, -1);\n    Response<List<GeoRadiusResponse>> r12 = p.georadiusByMember(\"barcelona\", \"place1\", 4, GeoUnit.KM);\n    Response<List<GeoRadiusResponse>> r13 = p.georadiusByMemberReadonly(\"barcelona\", \"place1\", 4, GeoUnit.KM);\n    Response<List<GeoRadiusResponse>> r14 = p.georadiusByMember(\"barcelona\", \"place1\", 4, GeoUnit.KM, params2);\n    Response<List<GeoRadiusResponse>> r15 = p.georadiusByMemberReadonly(\"barcelona\", \"place1\", 4, GeoUnit.KM, params2);\n    Response<Long> r16 = p.georadiusByMemberStore(\"barcelona{#}\", \"place1\", 4, GeoUnit.KM, params2, storeParams);\n    Response<List<String>> r17 = p.zrange(\"radius{#}\", 0, -1);\n\n    p.sync();\n    assertEquals(Long.valueOf(2), r1.get());\n    assertEquals(Double.valueOf(3067.4157), r2.get());\n    assertEquals(Double.valueOf(3.0674), r3.get());\n    assertEquals(hashValues, r4.get());\n    assertThat(r5.get(), contains(\n            GeoCoordinateMatcher.atCoordinates(2.19093829393386841, 41.43379028184083523),\n            GeoCoordinateMatcher.atCoordinates(2.18737632036209106, 41.40634178640635099))\n    );\n    assertTrue(r6.get().size() == 1 && r6.get().get(0).getMemberByString().equals(\"place1\"));\n    assertTrue(r7.get().size() == 1 && r7.get().get(0).getMemberByString().equals(\"place1\"));\n\n    GeoRadiusResponse expectedResponse = new GeoRadiusResponse(\"place1\".getBytes());\n    expectedResponse.setCoordinate(new GeoCoordinate(2.19093829393386841, 41.43379028184083523));\n    expectedResponse.setDistance(0.0881);\n    expectedResponse.setRawScore(3471609698139488L);\n\n    assertThat(r8.get().get(0), GeoRadiusResponseMatcher.ofResponse(expectedResponse));\n    assertThat(r9.get().get(0), GeoRadiusResponseMatcher.ofResponse(expectedResponse));\n\n    assertEquals(Long.valueOf(1), r10.get());\n    assertTrue(r11.get().size() == 1 && r11.get().contains(\"place1\"));\n    assertTrue(r12.get().size() == 2 && r12.get().get(0).getMemberByString().equals(\"place2\"));\n    assertTrue(r13.get().size() == 2 && r13.get().get(0).getMemberByString().equals(\"place2\"));\n    assertTrue(r14.get().size() == 1 && r14.get().get(0).getMemberByString().equals(\"place2\"));\n    assertTrue(r15.get().size() == 1 && r15.get().get(0).getMemberByString().equals(\"place2\"));\n    assertEquals(Long.valueOf(1), r16.get());\n    assertTrue(r17.get().size() == 1 && r17.get().contains(\"place2\"));\n  }\n\n  @Test\n  public void clusterPipelineHyperLogLog() {\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<Long> r1 = p.pfadd(\"{hll}_1\", \"foo\", \"bar\", \"zap\", \"a\");\n    Response<Long> r2 = p.pfadd(\"{hll}_2\", \"foo\", \"bar\", \"zap\");\n    Response<Long> r3 = p.pfcount(\"{hll}_1\", \"{hll}_2\");\n    Response<String> r4 = p.pfmerge(\"{hll}3\", \"{hll}_1\", \"{hll}_2\");\n    Response<Long> r5 = p.pfcount(\"{hll}3\");\n\n    p.sync();\n    assertEquals(Long.valueOf(1), r1.get());\n    assertEquals(Long.valueOf(1), r2.get());\n    assertEquals(Long.valueOf(4), r3.get());\n    assertEquals(\"OK\", r4.get());\n    assertEquals(Long.valueOf(4), r5.get());\n  }\n\n  @Test\n  public void clusterPipelineStringsAndBits() {\n    List<Long> fieldRes = new ArrayList<>();\n    fieldRes.add(1L);\n    fieldRes.add(0L);\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<String> r1 = p.set(\"{mykey}\", \"foobar\"); // foobar = 66 6f 6f 62 61 72\n    p.set(\"my{otherkey}\", \"foo\");\n    Response<String> r2 = p.substr(\"{mykey}\", 0, 2);\n    Response<Long> r3 = p.strlen(\"{mykey}\");\n    Response<Long> r4 = p.bitcount(\"my{otherkey}\");\n    Response<Long> r5 = p.bitcount(\"my{otherkey}\", 1, 1);\n    Response<Long> r6 = p.bitpos(\"{mykey}\", true);\n    Response<Long> r7 = p.bitpos(\"{mykey}\", false, new BitPosParams(1, 2));\n    Response<List<Long>> r8 = p.bitfield(\"mynew{key}\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    Response<List<Long>> r9 = p.bitfieldReadonly(\"hello\", \"GET\", \"i8\", \"17\");\n    p.set(\"myother{mykey}\", \"abcdef\");\n    Response<Long> r10 = p.bitop(BitOP.AND, \"dest{mykey}\", \"{mykey}\", \"myother{mykey}\");\n    Response<String> r11 = p.get(\"dest{mykey}\");\n    Response<Boolean> r12 = p.setbit(\"my{otherkey}\", 7, true);\n    Response<Boolean> r13 = p.getbit(\"my{otherkey}\", 7);\n\n    p.sync();\n    assertEquals(\"OK\", r1.get());\n    assertEquals(\"foo\", r2.get());\n    assertEquals(Long.valueOf(6), r3.get());\n    assertEquals(Long.valueOf(16), r4.get());\n    assertEquals(Long.valueOf(6), r5.get());\n    assertEquals(Long.valueOf(1), r6.get());\n    assertEquals(Long.valueOf(8), r7.get());\n    assertEquals(fieldRes, r8.get());\n    assertEquals(fieldRes.subList(1, 2), r9.get());\n    assertEquals(Long.valueOf(6), r10.get());\n    assertEquals(\"`bc`ab\", r11.get());\n    assertFalse(r12.get());\n    assertTrue(r13.get());\n  }\n\n  @Test\n  public void clusterPipelineStream() {\n    Map<String, String> hm = new HashMap<>();\n    hm.put(\"one\", \"one\");\n    hm.put(\"two\", \"two\");\n    hm.put(\"three\", \"three\");\n\n    StreamEntryID streamId1 = new StreamEntryID(\"1638277876711-0\");\n    StreamEntryID streamId2 = new StreamEntryID(\"1638277959731-0\");\n\n    ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG);\n    ClusterPipeline p = new ClusterPipeline(provider);\n\n    Response<StreamEntryID> r1 = p.xadd(\"mystream\", streamId1, hm);\n    Response<StreamEntryID> r2 = p.xadd(\"mystream\", new XAddParams().id(new StreamEntryID(\"1638277959731-0\")).maxLen(2).approximateTrimming(), hm);\n    Response<Long> r3 = p.xlen(\"mystream\");\n    Response<List<StreamEntry>> r4 = p.xrange(\"mystream\", streamId1, streamId2);\n    Response<List<StreamEntry>> r5 = p.xrange(\"mystream\", streamId1, streamId2, 1);\n    Response<List<StreamEntry>> r6 = p.xrevrange(\"mystream\", streamId2, streamId1);\n    Response<List<StreamEntry>> r7 = p.xrevrange(\"mystream\", streamId2, streamId1, 1);\n    Response<String> r8 = p.xgroupCreate(\"mystream\", \"group\", streamId1, false);\n    Response<String> r9 = p.xgroupSetID(\"mystream\", \"group\", streamId2);\n    // More stream commands are missing\n\n    p.sync();\n    assertEquals(streamId1, r1.get());\n    assertEquals(streamId2, r2.get());\n    assertEquals(Long.valueOf(2), r3.get());\n    assertTrue(r4.get().size() == 2\n        && r4.get().get(0).getID().compareTo(streamId1) == 0\n        && r4.get().get(1).getID().compareTo(streamId2) == 0);\n    assertTrue(r5.get().size() == 1 && r5.get().get(0).getID().compareTo(streamId1) == 0);\n    assertTrue(r6.get().size() == 2\n        && r6.get().get(1).getID().compareTo(streamId1) == 0\n        && r6.get().get(0).getID().compareTo(streamId2) == 0);\n    assertTrue(r7.get().size() == 1 && r7.get().get(0).getID().compareTo(streamId2) == 0);\n    assertEquals(\"OK\", r8.get());\n    assertEquals(\"OK\", r9.get());\n  }\n\n  @Test\n  public void testEval() {\n    String script = \"return 'success!'\";\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<Object> result = p.eval(script);\n      p.sync();\n\n      assertEquals(\"success!\", result.get());\n    }\n  }\n\n  @Test\n  public void testEvalWithBinary() {\n    String script = \"return 'success!'\";\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<Object> result = p.eval(SafeEncoder.encode(script));\n      p.sync();\n\n      assertArrayEquals(SafeEncoder.encode(\"success!\"), (byte[]) result.get());\n    }\n  }\n\n  @Test\n  public void testEvalKeyAndArg() {\n    String key = \"test\";\n    String arg = \"3\";\n    String script = \"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\";\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      p.set(key, \"0\");\n      Response<Object> result0 = p.eval(script, Collections.singletonList(key),\n          Collections.singletonList(arg));\n      p.incr(key);\n      Response<Object> result1 = p.eval(script, Collections.singletonList(key),\n          Collections.singletonList(arg));\n      Response<String> result2 = p.get(key);\n      p.sync();\n\n      assertNull(result0.get());\n      assertNull(result1.get());\n      assertEquals(\"13\", result2.get());\n    }\n  }\n\n  @Test\n  public void testEvalKeyAndArgWithBinary() {\n    // binary\n    byte[] bKey = SafeEncoder.encode(\"test\");\n    byte[] bArg = SafeEncoder.encode(\"3\");\n    byte[] bScript = SafeEncoder.encode(\"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\");\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline bP = new ClusterPipeline(provider);\n      bP.set(bKey, SafeEncoder.encode(\"0\"));\n      Response<Object> bResult0 = bP.eval(bScript, Collections.singletonList(bKey),\n          Collections.singletonList(bArg));\n      bP.incr(bKey);\n      Response<Object> bResult1 = bP.eval(bScript, Collections.singletonList(bKey),\n          Collections.singletonList(bArg));\n      Response<byte[]> bResult2 = bP.get(bKey);\n      bP.sync();\n\n      assertNull(bResult0.get());\n      assertNull(bResult1.get());\n      assertArrayEquals(SafeEncoder.encode(\"13\"), bResult2.get());\n    }\n  }\n\n  @Test\n  public void testEvalNestedLists() {\n    String script = \"return { {KEYS[1]} , {2} }\";\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<Object> result = p.eval(script, 1, \"key1\");\n      p.sync();\n\n      List<?> results = (List<?>) result.get();\n      MatcherAssert.assertThat((List<String>) results.get(0), Matchers.hasItem(\"key1\"));\n      MatcherAssert.assertThat((List<Long>) results.get(1), Matchers.hasItem(2L));\n    }\n  }\n\n  @Test\n  public void testEvalNestedListsWithBinary() {\n    byte[] bScript = SafeEncoder.encode(\"return { {KEYS[1]} , {2} }\");\n    byte[] bKey = SafeEncoder.encode(\"key1\");\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<Object> result = p.eval(bScript, 1, bKey);\n      p.sync();\n\n      List<?> results = (List<?>) result.get();\n      MatcherAssert.assertThat((List<byte[]>) results.get(0), Matchers.hasItem(bKey));\n      MatcherAssert.assertThat((List<Long>) results.get(1), Matchers.hasItem(2L));\n    }\n  }\n\n  @Test\n  public void testEvalsha() {\n    String script = \"return 'success!'\";\n    String sha1;\n    try (RedisClusterClient jc = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      sha1 = jc.scriptLoad(script, \"sampleKey\");\n      assertTrue(jc.scriptExists(sha1, \"sampleKey\"));\n    }\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      Response<Object> result = p.evalsha(sha1, 1, \"sampleKey\");\n      p.sync();\n\n      assertEquals(\"success!\", result.get());\n    }\n  }\n\n  @Test\n  public void testEvalshaKeyAndArg() {\n    String key = \"test\";\n    String arg = \"3\";\n    String script = \"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\";\n    String sha1;\n    try (RedisClusterClient jc = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      sha1 = jc.scriptLoad(script, key);\n      assertTrue(jc.scriptExists(sha1, key));\n    }\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      p.set(key, \"0\");\n      Response<Object> result0 = p.evalsha(sha1, Arrays.asList(key), Arrays.asList(arg));\n      p.incr(key);\n      Response<Object> result1 = p.evalsha(sha1, Arrays.asList(key), Arrays.asList(arg));\n      Response<String> result2 = p.get(key);\n      p.sync();\n\n      assertNull(result0.get());\n      assertNull(result1.get());\n      assertEquals(\"13\", result2.get());\n    }\n  }\n\n  @Test\n  public void testEvalshaKeyAndArgWithBinary() {\n    byte[] bKey = SafeEncoder.encode(\"test\");\n    byte[] bArg = SafeEncoder.encode(\"3\");\n    String script = \"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\";\n    byte[] bScript = SafeEncoder.encode(script);\n    byte[] bSha1;\n    try (RedisClusterClient jc = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      bSha1 = jc.scriptLoad(bScript, bKey);\n      assertTrue(jc.scriptExists(bSha1, bKey));\n    }\n\n    try (ClusterConnectionProvider provider = new ClusterConnectionProvider(nodes, DEFAULT_CLIENT_CONFIG)) {\n      ClusterPipeline p = new ClusterPipeline(provider);\n      p.set(bKey, SafeEncoder.encode(\"0\"));\n      Response<Object> result0 = p.evalsha(bSha1, Arrays.asList(bKey), Arrays.asList(bArg));\n      p.incr(bKey);\n      Response<Object> result1 = p.evalsha(bSha1, Arrays.asList(bKey), Arrays.asList(bArg));\n      Response<byte[]> result2 = p.get(bKey);\n      p.sync();\n\n      assertNull(result0.get());\n      assertNull(result1.get());\n      assertArrayEquals(SafeEncoder.encode(\"13\"), result2.get());\n    }\n  }\n\n  @Test\n  public void spublishInPipeline() {\n    try (RedisClusterClient jedis = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      ClusterPipeline pipelined = jedis.pipelined();\n      Response<Long> p1 = pipelined.publish(\"foo\", \"bar\");\n      Response<Long> p2 = pipelined.publish(\"foo\".getBytes(), \"bar\".getBytes());\n      pipelined.sync();\n      assertEquals(0, p1.get().longValue());\n      assertEquals(0, p2.get().longValue());\n    }\n  }\n\n  @Test\n  public void simple() { // TODO: move into 'redis.clients.jedis.commands.unified.cluster' package\n    try (RedisClusterClient jedis = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      final int count = 10;\n      int totalCount = 0;\n      for (int i = 0; i < count; i++) {\n        jedis.set(\"foo\" + i, \"bar\" + i);\n      }\n      totalCount += count;\n      for (int i = 0; i < count; i++) {\n        jedis.rpush(\"foobar\" + i, \"foo\" + i, \"bar\" + i);\n      }\n      totalCount += count;\n\n      List<Response<?>> responses = new ArrayList<>(totalCount);\n      List<Object> expected = new ArrayList<>(totalCount);\n      try (ClusterPipeline pipeline = jedis.pipelined()) {\n        for (int i = 0; i < count; i++) {\n          responses.add(pipeline.get(\"foo\" + i));\n          expected.add(\"bar\" + i);\n        }\n        for (int i = 0; i < count; i++) {\n          responses.add(pipeline.lrange(\"foobar\" + i, 0, -1));\n          expected.add(Arrays.asList(\"foo\" + i, \"bar\" + i));\n        }\n      }\n\n      for (int i = 0; i < totalCount; i++) {\n        assertEquals(expected.get(i), responses.get(i).get());\n      }\n    }\n  }\n\n  @Test\n  public void transaction() {\n    try (RedisClusterClient cluster = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n      assertThrows(UnsupportedOperationException.class, () -> cluster.multi());\n    }\n  }\n\n  @Test\n  @Timeout(10)\n  public void multiple() {\n    final int maxTotal = 100;\n    ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();\n    poolConfig.setMaxTotal(maxTotal);\n    try (RedisClusterClient cluster = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).maxAttempts(5).poolConfig(poolConfig).build()) {\n      for (int i = 0; i < maxTotal; i++) {\n        assertThreadsCount();\n        String s = Integer.toString(i);\n        try (ClusterPipeline pipeline = cluster.pipelined()) {\n          pipeline.set(s, s);\n          pipeline.sync();\n        }\n        assertThreadsCount();\n      }\n    }\n  }\n\n  @Test\n  public void testPipelineKeysAtSameNode() {\n    try (RedisClusterClient cluster = RedisClusterClient.builder().nodes(nodes).clientConfig(DEFAULT_CLIENT_CONFIG).build()) {\n\n      // test simple key\n      cluster.set(\"foo\", \"bar\");\n\n      try (ClusterPipeline pipeline = cluster.pipelined()) {\n        Response<String> foo = pipeline.get(\"foo\");\n        pipeline.sync();\n\n        assertEquals(\"bar\", foo.get());\n      }\n\n      // test multi key but at same node\n      int cnt = 3;\n      String prefix = \"{foo}:\";\n      for (int i = 0; i < cnt; i++) {\n        String key = prefix + i;\n        cluster.set(key, String.valueOf(i));\n      }\n\n      try (ClusterPipeline pipeline = cluster.pipelined()) {\n        List<Response<String>> results = new ArrayList<>();\n        for (int i = 0; i < cnt; i++) {\n          String key = prefix + i;\n          results.add(pipeline.get(key));\n        }\n\n        Response<Object> foo = pipeline.eval(\"return redis.call('get', KEYS[1])\",\n            Collections.singletonList(\"foo\"), Collections.emptyList());\n\n        pipeline.sync();\n        int idx = 0;\n        for (Response<String> res : results) {\n          assertEquals(String.valueOf(idx), res.get());\n          idx++;\n        }\n        assertEquals(\"bar\", String.valueOf(foo.get()));\n      }\n    }\n  }\n\n  private static void assertThreadsCount() {\n    // Get the root thread group\n    final ThreadGroup rootGroup = Thread.currentThread().getThreadGroup().getParent();\n\n    // Create a buffer to store the thread information\n    final Thread[] threads = new Thread[rootGroup.activeCount()];\n\n    // Enumerate all threads into the buffer\n    rootGroup.enumerate(threads);\n\n    // Assert information about threads\n    final int count = (int) Arrays.stream(threads)\n        .filter(thread -> thread != null && thread.getName() != null\n            && thread.getName().startsWith(\"pool-\"))\n        .count();\n    MatcherAssert.assertThat(count, Matchers.lessThanOrEqualTo(20));\n  }\n\n  @Test\n  public void testAllShardsCommandRejected() {\n    try (ClusterPipeline pipeline = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      // KEYS has ALL_SHARDS policy with pattern argument (not a key), should be rejected\n      UnsupportedOperationException ex = assertThrows(\n          UnsupportedOperationException.class,\n          () -> pipeline.keys(\"*\"));\n\n      assertTrue(ex.getMessage().contains(\"ALL_SHARDS\"),\n          \"Error message should mention ALL_SHARDS policy\");\n      assertTrue(ex.getMessage().contains(\"KEYS\"),\n          \"Error message should mention the command name\");\n      assertTrue(ex.getMessage().contains(\"no keys\"),\n          \"Error message should mention that command has no keys\");\n    }\n  }\n\n  @Test\n  public void testAllShardsPolicyWithSampleKeyAllowed() {\n    try (ClusterPipeline pipeline = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      // SCRIPT EXISTS has ALL_SHARDS policy but with sampleKey it routes to single slot, should work\n      String dummySha1 = \"0000000000000000000000000000000000000000\";\n      Response<List<Boolean>> existsResponse = pipeline.scriptExists(\"samplekey\", dummySha1);\n      pipeline.sync();\n\n      // The response should be valid (list with one boolean indicating if script exists)\n      assertNotNull(existsResponse.get(), \"SCRIPT EXISTS with sampleKey should be allowed in pipeline\");\n      assertEquals(1, existsResponse.get().size(), \"SCRIPT EXISTS should return list with one element\");\n      assertFalse(existsResponse.get().get(0), \"Dummy script should not exist\");\n    }\n  }\n\n  @Test\n  public void testMultiShardCommandRejected() {\n    try (ClusterPipeline pipeline = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      // MGET with keys in different slots should be rejected\n      UnsupportedOperationException ex = assertThrows(\n          UnsupportedOperationException.class,\n          () -> pipeline.mget(\"key1\", \"key2\", \"key3\"));\n\n      assertTrue(ex.getMessage().contains(\"MULTI_SHARD\"),\n          \"Error message should mention MULTI_SHARD policy\");\n    }\n  }\n\n  @Test\n  public void testDefaultPolicyCommandAllowed() {\n    try (ClusterPipeline pipeline = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      // SET with single key - DEFAULT policy, should work\n      Response<String> setResponse = pipeline.set(\"testkey\", \"testvalue\");\n      Response<String> getResponse = pipeline.get(\"testkey\");\n      pipeline.sync();\n\n      assertEquals(\"OK\", setResponse.get());\n      assertEquals(\"testvalue\", getResponse.get());\n    }\n  }\n\n  @Test\n  public void testMultiShardPolicyWithSingleKeyAllowed() {\n    try (ClusterPipeline pipeline = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      // EXISTS with single key has MULTI_SHARD policy but routes to single slot, should work\n      pipeline.set(\"existskey\", \"value\");\n      Response<Boolean> existsResponse = pipeline.exists(\"existskey\");\n      pipeline.sync();\n\n      assertTrue(existsResponse.get(), \"EXISTS with single key should be allowed in pipeline\");\n    }\n  }\n\n  @Test\n  public void testMultiShardPolicyWithMultipleKeysRejected() {\n    try (ClusterPipeline pipeline = new ClusterPipeline(nodes, DEFAULT_CLIENT_CONFIG)) {\n      // EXISTS with multiple keys in different slots has MULTI_SHARD policy, should be rejected\n      UnsupportedOperationException ex = assertThrows(\n          UnsupportedOperationException.class,\n          () -> pipeline.exists(\"key1\", \"key2\", \"key3\"));\n\n      assertTrue(ex.getMessage().contains(\"MULTI_SHARD\") || ex.getMessage().contains(\"multiple slots\"),\n          \"Error message should mention MULTI_SHARD policy or multiple slots\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ConnectionTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport org.hamcrest.Matchers;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\n@Tag(\"integration\")\npublic class ConnectionTest {\n\n  private static EndpointConfig endpoint;\n\n  private Connection client;\n\n  @BeforeAll\n  public static void setUp() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    if (client != null) {\n      client.close();\n    }\n  }\n\n  @Test\n  public void checkUnknownHost() {\n    client = new Connection(\"someunknownhost\", Protocol.DEFAULT_PORT);\n    assertThrows(JedisConnectionException.class, ()->client.connect());\n  }\n\n  @Test\n  public void checkWrongPort() {\n    client = new Connection(Protocol.DEFAULT_HOST, 55665);\n    assertThrows(JedisConnectionException.class, ()->client.connect());\n  }\n\n  @Test\n  public void connectIfNotConnectedWhenSettingTimeoutInfinite() {\n    client = new Connection(endpoint.getHost(), endpoint.getPort());\n    client.setTimeoutInfinite();\n  }\n\n  @Test\n  public void checkCloseable() {\n    client = new Connection(endpoint.getHost(), endpoint.getPort());\n    client.connect();\n    client.close();\n  }\n\n  @Test\n  public void checkIdentityString() {\n    client = new Connection(endpoint.getHost(), endpoint.getPort());\n\n    String idString = \"id: 0x\" + Integer.toHexString(client.hashCode()).toUpperCase();\n\n    String identityString = client.toIdentityString();\n    assertThat(identityString, Matchers.startsWith(\"Connection{\"));\n    assertThat(identityString, Matchers.endsWith(\"}\"));\n    assertThat(identityString, Matchers.containsString(idString));\n\n    client.connect();\n    identityString = client.toIdentityString();\n    assertThat(identityString, Matchers.startsWith(\"Connection{\"));\n    assertThat(identityString, Matchers.endsWith(\"}\"));\n    assertThat(identityString, Matchers.containsString(idString));\n    assertThat(identityString, Matchers.containsString(\", L:\"));\n    assertThat(identityString, Matchers.containsString(\" - R:\"));\n\n    client.close();\n    identityString = client.toIdentityString();\n    assertThat(identityString, Matchers.startsWith(\"Connection{\"));\n    assertThat(identityString, Matchers.endsWith(\"}\"));\n    assertThat(identityString, Matchers.containsString(idString));\n    assertThat(identityString, Matchers.containsString(\", L:\"));\n    assertThat(identityString, Matchers.containsString(\" ! R:\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/DefaultJedisClientConfigTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.net.URI;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nclass DefaultJedisClientConfigTest {\n\n  @Nested\n  class BuilderTests {\n\n    @Test\n    void builderFromUri_credentials() {\n      URI uri = URI.create(\"redis://testuser:testpass@localhost:6379/0\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      assertThat(config.getUser(), equalTo(\"testuser\"));\n      assertThat(config.getPassword(), equalTo(\"testpass\"));\n      assertThat(config.getDatabase(), equalTo(0));\n    }\n\n    @Test\n    void builderFromUri_database() {\n      URI uri = URI.create(\"redis://localhost:6379/5\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      assertThat(config.getDatabase(), equalTo(5));\n    }\n\n    @Test\n    void builderFromUri_ssl() {\n      URI uri = URI.create(\"rediss://localhost:6380\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      assertTrue(config.isSsl());\n    }\n\n    @Test\n    void builderFromUri_nossl() {\n      URI uri = URI.create(\"redis://localhost:6380\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      assertFalse(config.isSsl());\n    }\n\n    @Test\n    void builderFromUri_protocol() {\n      URI uri = URI.create(\"redis://localhost:6379?protocol=3\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      assertThat(config.getRedisProtocol(), equalTo(RedisProtocol.RESP3));\n    }\n\n    @Test\n    void builderFromUri_combined() {\n      URI uri = URI.create(\"rediss://admin:secret123@localhost:6380/2?protocol=3\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      assertThat(config.getUser(), equalTo(\"admin\"));\n      assertThat(config.getPassword(), equalTo(\"secret123\"));\n      assertThat(config.getDatabase(), equalTo(2));\n      assertThat(config.isSsl(), equalTo(true));\n      assertThat(config.getRedisProtocol(), equalTo(RedisProtocol.RESP3));\n    }\n\n    @Test\n    void builderFromUri_noCredentials() {\n      URI uri = URI.create(\"redis://localhost:6379\");\n\n      DefaultJedisClientConfig config = DefaultJedisClientConfig.builder(uri).build();\n\n      // Should have default/null credentials\n      assertThat(config.getDatabase(), equalTo(Protocol.DEFAULT_DATABASE));\n      assertThat(config.isSsl(), equalTo(false));\n    }\n\n    @Test\n    void builderFromUri_passwordOnly() {\n      URI uri = URI.create(\"redis://:password@localhost:6379\");\n\n      assertThat(DefaultJedisClientConfig.builder(uri).build().getPassword(), equalTo(\"password\"));\n      assertNull(DefaultJedisClientConfig.builder(uri).build().getUser());\n    }\n\n    @Test\n    void builderFromUri_usernameOnlyThrowsException() {\n      URI uri = URI.create(\"redis://onlyuser@localhost:6379\");\n      IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n        () -> DefaultJedisClientConfig.builder(uri));\n\n      assertThat(ex.getMessage(), containsString(\"Password not provided in uri\"));\n    }\n\n    @Test\n    void builderFromUri_InvalidUri() {\n      URI uri = URI.create(\"localhost:6379/0\");\n\n      Exception ex = assertThrows(IllegalArgumentException.class,\n        () -> DefaultJedisClientConfig.builder(uri).build());\n      assertThat(ex.getMessage(), containsString(\"Invalid Redis URI\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/EndpointConfig.java",
    "content": "package redis.clients.jedis;\n\nimport com.google.gson.FieldNamingPolicy;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\nimport redis.clients.jedis.util.JedisURIHelper;\nimport redis.clients.jedis.util.TlsUtil;\n\nimport java.io.FileReader;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\n\npublic class EndpointConfig {\n\n    private final boolean tls;\n    private final String username;\n    private final String password;\n    private final int bdbId;\n    private final List<URI> endpoints;\n    private final String tlsCertPath;\n\n    public EndpointConfig(HostAndPort hnp, String username, String password, boolean tls, String tlsCertPath) {\n        this.tls = tls;\n        this.username = username;\n        this.password = password;\n        this.bdbId = 0;\n        this.endpoints = Collections.singletonList(\n            URI.create(getURISchema(tls) + hnp.getHost() + \":\" + hnp.getPort()));\n        this.tlsCertPath = tlsCertPath;\n    }\n\n    public HostAndPort getHostAndPort() {\n        return JedisURIHelper.getHostAndPort(endpoints.get(0));\n    }\n\n    public HostAndPort getHostAndPort(int index) {\n        return JedisURIHelper.getHostAndPort(endpoints.get(index));\n    }\n\n    public List<HostAndPort> getHostsAndPorts() {\n        List<HostAndPort> result = new ArrayList<>();\n        for (URI endpoint : endpoints) {\n            result.add(JedisURIHelper.getHostAndPort(endpoint));\n        }\n        return result;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public String getUsername() {\n        return username == null? \"default\" : username;\n    }\n\n    public String getHost() {\n        return getHostAndPort().getHost();\n    }\n\n    public int getPort() {\n        return getHostAndPort().getPort();\n    }\n\n    public Boolean isTls() {\n        return tls;\n    }\n\n    public Path getCertificatesLocation() {\n        return Paths.get(tlsCertPath);\n    }\n\n    public int getBdbId() { return bdbId; }\n\n    public URI getURI() {\n        return endpoints.get(0);\n    }\n\n    public class EndpointURIBuilder {\n        private boolean tls;\n\n        private String username;\n\n        private String password;\n\n        private String path;\n\n        public EndpointURIBuilder() {\n            this.username = \"\";\n            this.password = \"\";\n            this.path = \"\";\n            this.tls = EndpointConfig.this.tls;\n        }\n\n        public EndpointURIBuilder defaultCredentials() {\n            this.username = EndpointConfig.this.username == null ? \"\" : getUsername();\n            this.password = EndpointConfig.this.getPassword();\n            return this;\n        }\n\n        public EndpointURIBuilder tls(boolean v) {\n            this.tls = v;\n            return this;\n        }\n\n        public EndpointURIBuilder path(String v) {\n            this.path = v;\n            return this;\n        }\n\n        public EndpointURIBuilder credentials(String u, String p) {\n            this.username = u;\n            this.password = p;\n            return this;\n        }\n\n        public URI build() {\n            String userInfo = !(this.username.isEmpty() && this.password.isEmpty()) ?\n                this.username + ':' + this.password + '@' :\n                \"\";\n            return URI.create(\n                getURISchema(this.tls) + userInfo + getHost() + \":\" + getPort() + this.path);\n        }\n    }\n\n    public EndpointURIBuilder getURIBuilder() {\n        return new EndpointURIBuilder();\n    }\n\n    public DefaultJedisClientConfig.Builder getClientConfigBuilder() {\n      DefaultJedisClientConfig.Builder builder = DefaultJedisClientConfig.builder()\n          .password(password).ssl(tls);\n\n        if (tls && tlsCertPath != null) {\n          builder.sslSocketFactory(TlsUtil.sslSocketFactoryForEnv(Paths.get(tlsCertPath)));\n        }\n\n        if (username != null) {\n          return builder.user(username);\n        }\n\n        return builder;\n    }\n\n    protected String getURISchema(boolean tls) {\n        return (tls ? \"rediss\" : \"redis\") + \"://\";\n    }\n\n    public static HashMap<String, EndpointConfig> loadFromJSON(String filePath) throws Exception {\n        Gson gson = new GsonBuilder().setFieldNamingPolicy(\n            FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();\n\n        HashMap<String, EndpointConfig> configs;\n        try (FileReader reader = new FileReader(filePath)) {\n            configs = gson.fromJson(reader, new TypeToken<HashMap<String, EndpointConfig>>() {\n            }.getType());\n        }\n        return configs;\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/Endpoints.java",
    "content": "package redis.clients.jedis;\n\nimport org.opentest4j.TestAbortedException;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport java.util.HashMap;\n\npublic final class Endpoints {\n\n  private static final HashMap<String, EndpointConfig> endpointConfigs;\n\n  static {\n    try {\n      endpointConfigs = EndpointConfig.loadFromJSON(TestEnvUtil.getEndpointsConfigPath());\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static EndpointConfig getRedisEndpoint(String endpointName) {\n    if (!endpointConfigs.containsKey(endpointName)) {\n      throw new TestAbortedException(\"Unavailable Redis endpoint: \" + endpointName);\n    }\n\n    return endpointConfigs.get(endpointName);\n  }\n\n  private Endpoints() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/HostAndPortTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\npublic class HostAndPortTest {\n\n  @Test\n  public void checkFrom() throws Exception {\n    String host = \"2a11:1b1:0:111:e111:1f11:1111:1f1e:1999\";\n    int port = 6379;\n    HostAndPort hp = HostAndPort.from(host + \":\" + Integer.toString(port));\n    assertEquals(host, hp.getHost());\n    assertEquals(port, hp.getPort());\n  }\n\n  @Test\n  public void checkFromWithoutPort() throws Exception {\n    assertThrows(IllegalArgumentException.class, () -> HostAndPort.from(\"localhost:\"));\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisClusterCommandTest.java",
    "content": "//package redis.clients.jedis;\n//\n//import static org.junit.jupiter.api.Assertions.assertEquals;\n//import static org.junit.jupiter.api.Assertions.assertNotNull;\n//import static org.junit.jupiter.api.Assertions.assertTrue;\n//import static org.junit.jupiter.api.Assertions.fail;\n//import static org.mockito.ArgumentMatchers.any;\n//import static org.mockito.ArgumentMatchers.anyInt;\n//import static org.mockito.ArgumentMatchers.anyLong;\n//import static org.mockito.Mockito.doAnswer;\n//import static org.mockito.Mockito.inOrder;\n//import static org.mockito.Mockito.mock;\n//import static org.mockito.Mockito.times;\n//import static org.mockito.Mockito.when;\n//\n//import java.time.Duration;\n//import java.util.concurrent.atomic.AtomicLong;\n//import java.util.function.LongConsumer;\n//import org.junit.Test;\n//import org.mockito.InOrder;\n//import org.mockito.invocation.InvocationOnMock;\n//import org.mockito.stubbing.Answer;\n//import redis.clients.jedis.HostAndPort;\n//import redis.clients.jedis.Jedis;\n//import redis.clients.jedis.JedisClusterCommand;\n//import redis.clients.jedis.JedisClusterConnectionHandler;\n//import redis.clients.jedis.JedisSlotBasedConnectionHandler;\n//import redis.clients.jedis.exceptions.JedisAskDataException;\n//import redis.clients.jedis.exceptions.JedisClusterMaxAttemptsException;\n//import redis.clients.jedis.exceptions.JedisClusterOperationException;\n//import redis.clients.jedis.exceptions.JedisConnectionException;\n//import redis.clients.jedis.exceptions.JedisMovedDataException;\n//import redis.clients.jedis.exceptions.JedisNoReachableClusterNodeException;\n//\n//public class JedisClusterCommandTest {\n//\n//  private static final Duration ONE_SECOND = Duration.ofSeconds(1);\n//\n//  @Test\n//  public void runSuccessfulExecute() {\n//    JedisClusterConnectionHandler connectionHandler = mock(JedisClusterConnectionHandler.class);\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 10,\n//        Duration.ZERO) {\n//      @Override\n//      public String execute(Jedis connection) {\n//        return \"foo\";\n//      }\n//\n//      @Override\n//      protected void sleep(long ignored) {\n//        throw new RuntimeException(\"This test should never sleep\");\n//      }\n//    };\n//    String actual = testMe.run(\"\");\n//    assertEquals(\"foo\", actual);\n//  }\n//\n//  @Test\n//  public void runFailOnFirstExecSuccessOnSecondExec() {\n//    JedisClusterConnectionHandler connectionHandler = mock(JedisClusterConnectionHandler.class);\n//\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 10,\n//        ONE_SECOND) {\n//      boolean isFirstCall = true;\n//\n//      @Override\n//      public String execute(Jedis connection) {\n//        if (isFirstCall) {\n//          isFirstCall = false;\n//          throw new JedisConnectionException(\"Borkenz\");\n//        }\n//\n//        return \"foo\";\n//      }\n//\n//      @Override\n//      protected void sleep(long ignored) {\n//        throw new RuntimeException(\"This test should never sleep\");\n//      }\n//    };\n//\n//    String actual = testMe.run(\"\");\n//    assertEquals(\"foo\", actual);\n//  }\n//\n//  @Test\n//  public void runAlwaysFailing() {\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//\n//    final LongConsumer sleep = mock(LongConsumer.class);\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 3,\n//        ONE_SECOND) {\n//      @Override\n//      public String execute(Jedis connection) {\n//        throw new JedisConnectionException(\"Connection failed\");\n//      }\n//\n//      @Override\n//      protected void sleep(long sleepMillis) {\n//        sleep.accept(sleepMillis);\n//      }\n//    };\n//\n//    try {\n//      testMe.run(\"\");\n//      fail(\"cluster command did not fail\");\n//    } catch (JedisClusterMaxAttemptsException e) {\n//      // expected\n//    }\n//    InOrder inOrder = inOrder(connectionHandler, sleep);\n//    inOrder.verify(connectionHandler, times(2)).getConnectionFromSlot(anyInt());\n//    inOrder.verify(sleep).accept(anyLong());\n//    inOrder.verify(connectionHandler).renewSlotCache();\n//    inOrder.verify(connectionHandler).getConnectionFromSlot(anyInt());\n//    inOrder.verifyNoMoreInteractions();\n//  }\n//\n//  @Test\n//  public void runMovedSuccess() {\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//\n//    final HostAndPort movedTarget = new HostAndPort(null, 0);\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 10,\n//        ONE_SECOND) {\n//      boolean isFirstCall = true;\n//\n//      @Override\n//      public String execute(Jedis connection) {\n//        if (isFirstCall) {\n//          isFirstCall = false;\n//\n//          // Slot 0 moved\n//          throw new JedisMovedDataException(\"\", movedTarget, 0);\n//        }\n//\n//        return \"foo\";\n//      }\n//\n//      @Override\n//      protected void sleep(long ignored) {\n//        throw new RuntimeException(\"This test should never sleep\");\n//      }\n//    };\n//\n//    String actual = testMe.run(\"\");\n//    assertEquals(\"foo\", actual);\n//\n//    InOrder inOrder = inOrder(connectionHandler);\n//    inOrder.verify(connectionHandler).getConnectionFromSlot(anyInt());\n//    inOrder.verify(connectionHandler).renewSlotCache(any());\n//    inOrder.verify(connectionHandler).getConnectionFromNode(movedTarget);\n//    inOrder.verifyNoMoreInteractions();\n//  }\n//\n//  @Test\n//  public void runAskSuccess() {\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//    Jedis connection = mock(Jedis.class);\n//    final HostAndPort askTarget = new HostAndPort(null, 0);\n//    when(connectionHandler.getConnectionFromNode(askTarget)).thenReturn(connection);\n//\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 10,\n//        ONE_SECOND) {\n//      boolean isFirstCall = true;\n//\n//      @Override\n//      public String execute(Jedis connection) {\n//        if (isFirstCall) {\n//          isFirstCall = false;\n//\n//          // Slot 0 moved\n//          throw new JedisAskDataException(\"\", askTarget, 0);\n//        }\n//\n//        return \"foo\";\n//      }\n//\n//      @Override\n//      protected void sleep(long ignored) {\n//        throw new RuntimeException(\"This test should never sleep\");\n//      }\n//    };\n//\n//    String actual = testMe.run(\"\");\n//    assertEquals(\"foo\", actual);\n//\n//    InOrder inOrder = inOrder(connectionHandler, connection);\n//    inOrder.verify(connectionHandler).getConnectionFromSlot(anyInt());\n//    inOrder.verify(connectionHandler).getConnectionFromNode(askTarget);\n//    inOrder.verify(connection).asking();\n//    inOrder.verify(connection).close(); // From the finally clause in runWithRetries()\n//    inOrder.verifyNoMoreInteractions();\n//  }\n//\n//  @Test\n//  public void runMovedThenAllNodesFailing() {\n//    // Test:\n//    // First attempt is a JedisMovedDataException() move, because we asked the wrong node.\n//    // All subsequent attempts are JedisConnectionExceptions, because all nodes are now down.\n//    // In response to the JedisConnectionExceptions, run() retries random nodes until maxAttempts is\n//    // reached.\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//\n//    final Jedis redirecter = mock(Jedis.class);\n//    when(connectionHandler.getConnectionFromSlot(anyInt())).thenReturn(redirecter);\n//\n//    final Jedis failer = mock(Jedis.class);\n//    when(connectionHandler.getConnectionFromNode(any(HostAndPort.class))).thenReturn(failer);\n//    doAnswer((Answer) (InvocationOnMock invocation) -> {\n//      when(connectionHandler.getConnectionFromSlot(anyInt())).thenReturn(failer);\n//      return null;\n//    }).when(connectionHandler).renewSlotCache();\n//\n//    final LongConsumer sleep = mock(LongConsumer.class);\n//    final HostAndPort movedTarget = new HostAndPort(null, 0);\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 5,\n//        ONE_SECOND) {\n//      @Override\n//      public String execute(Jedis connection) {\n//        if (redirecter == connection) {\n//          // First attempt, report moved\n//          throw new JedisMovedDataException(\"Moved\", movedTarget, 0);\n//        }\n//\n//        if (failer == connection) {\n//          // Second attempt in response to the move, report failure\n//          throw new JedisConnectionException(\"Connection failed\");\n//        }\n//\n//        throw new IllegalStateException(\"Should have thrown jedis exception\");\n//      }\n//\n//      @Override\n//      protected void sleep(long sleepMillis) {\n//        sleep.accept(sleepMillis);\n//      }\n//    };\n//\n//    try {\n//      testMe.run(\"\");\n//      fail(\"cluster command did not fail\");\n//    } catch (JedisClusterMaxAttemptsException e) {\n//      // expected\n//    }\n//    InOrder inOrder = inOrder(connectionHandler, sleep);\n//    inOrder.verify(connectionHandler).getConnectionFromSlot(anyInt());\n//    inOrder.verify(connectionHandler).renewSlotCache(redirecter);\n//    inOrder.verify(connectionHandler, times(2)).getConnectionFromNode(movedTarget);\n//    inOrder.verify(sleep).accept(anyLong());\n//    inOrder.verify(connectionHandler).renewSlotCache();\n//    inOrder.verify(connectionHandler, times(2)).getConnectionFromSlot(anyInt());\n//    inOrder.verify(sleep).accept(anyLong());\n//    inOrder.verify(connectionHandler).renewSlotCache();\n//    inOrder.verifyNoMoreInteractions();\n//  }\n//\n//  @Test\n//  public void runMasterFailingReplicaRecovering() {\n//    // We have two nodes, master and replica, and master has just gone down permanently.\n//    //\n//    // Test:\n//    // 1. We try to contact master => JedisConnectionException\n//    // 2. We try to contact master => JedisConnectionException\n//    // 3. sleep and renew\n//    // 4. We try to contact replica => Success, because it has now failed over\n//\n//    final Jedis master = mock(Jedis.class);\n//    when(master.toString()).thenReturn(\"master\");\n//\n//    final Jedis replica = mock(Jedis.class);\n//    when(replica.toString()).thenReturn(\"replica\");\n//\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//\n//    when(connectionHandler.getConnectionFromSlot(anyInt())).thenReturn(master);\n//\n//    doAnswer((Answer) (InvocationOnMock invocation) -> {\n//      when(connectionHandler.getConnectionFromSlot(anyInt())).thenReturn(replica);\n//      return null;\n//    }).when(connectionHandler).renewSlotCache();\n//\n//    final AtomicLong totalSleepMs = new AtomicLong();\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 10,\n//        ONE_SECOND) {\n//\n//      @Override\n//      public String execute(Jedis connection) {\n//        assertNotNull(connection);\n//\n//        if (connection == master) {\n//          throw new JedisConnectionException(\"Master is down\");\n//        }\n//\n//        assert connection == replica;\n//\n//        return \"Success!\";\n//      }\n//\n//      @Override\n//      protected void sleep(long sleepMillis) {\n//        assert sleepMillis > 0;\n//        totalSleepMs.addAndGet(sleepMillis);\n//      }\n//    };\n//\n//    assertEquals(\"Success!\", testMe.run(\"\"));\n//    InOrder inOrder = inOrder(connectionHandler);\n//    inOrder.verify(connectionHandler, times(2)).getConnectionFromSlot(anyInt());\n//    inOrder.verify(connectionHandler).renewSlotCache();\n//    inOrder.verify(connectionHandler).getConnectionFromSlot(anyInt());\n//    inOrder.verifyNoMoreInteractions();\n//    assertTrue(totalSleepMs.get() > 0);\n//  }\n//\n//  @Test(expected = JedisNoReachableClusterNodeException.class)\n//  public void runRethrowsJedisNoReachableClusterNodeException() {\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//    when(connectionHandler.getConnectionFromSlot(anyInt())).thenThrow(\n//      JedisNoReachableClusterNodeException.class);\n//\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 10,\n//        Duration.ZERO) {\n//      @Override\n//      public String execute(Jedis connection) {\n//        return null;\n//      }\n//\n//      @Override\n//      protected void sleep(long ignored) {\n//        throw new RuntimeException(\"This test should never sleep\");\n//      }\n//    };\n//\n//    testMe.run(\"\");\n//  }\n//\n//  @Test\n//  public void runStopsRetryingAfterTimeout() {\n//    JedisSlotBasedConnectionHandler connectionHandler = mock(JedisSlotBasedConnectionHandler.class);\n//\n//    final LongConsumer sleep = mock(LongConsumer.class);\n//    JedisClusterCommand<String> testMe = new JedisClusterCommand<String>(connectionHandler, 3,\n//        Duration.ZERO) {\n//      @Override\n//      public String execute(Jedis connection) {\n//        try {\n//          // exceed deadline\n//          Thread.sleep(2L);\n//        } catch (InterruptedException e) {\n//          throw new RuntimeException(e);\n//        }\n//        throw new JedisConnectionException(\"Connection failed\");\n//      }\n//\n//      @Override\n//      protected void sleep(long sleepMillis) {\n//        sleep.accept(sleepMillis);\n//      }\n//    };\n//\n//    try {\n//      testMe.run(\"\");\n//      fail(\"cluster command did not fail\");\n//    } catch (JedisClusterOperationException e) {\n//      // expected\n//    }\n//    InOrder inOrder = inOrder(connectionHandler, sleep);\n//    inOrder.verify(connectionHandler).getConnectionFromSlot(anyInt());\n//    inOrder.verifyNoMoreInteractions();\n//  }\n//}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisClusterInfoCacheTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.aMapWithSize;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.when;\nimport static redis.clients.jedis.JedisClusterInfoCache.getNodeKey;\nimport static redis.clients.jedis.Protocol.Command.CLUSTER;\nimport static redis.clients.jedis.util.CommandArgumentsMatchers.commandWithArgs;\n\n@Tag(\"unit\")\n@ExtendWith(MockitoExtension.class)\npublic class JedisClusterInfoCacheTest {\n\n  private static final HostAndPort MASTER_HOST = new HostAndPort(\"127.0.0.1\", 7000);\n  private static final HostAndPort REPLICA_1_HOST = new HostAndPort(\"127.0.0.1\", 7001);\n  private static final HostAndPort REPLICA_2_HOST = new HostAndPort(\"127.0.0.1\", 7002);\n  private static final int TEST_SLOT = 0;\n\n  @Mock\n  private Connection mockConnection;\n\n  @Test\n  public void testReplicaNodeRemovalAndRediscovery() {\n    // Create client config with read-only replicas enabled\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .readOnlyForRedisClusterReplicas().build();\n\n    Set<HostAndPort> startNodes = new HashSet<>();\n    startNodes.add(MASTER_HOST);\n\n    JedisClusterInfoCache cache = new JedisClusterInfoCache(clientConfig, startNodes);\n\n    // Mock the cluster slots responses\n    when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, \"SLOTS\")))).thenReturn(\n            masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST)).thenReturn(masterOnlySlotsResponse())\n        .thenReturn(masterReplica2SlotsResponse());\n\n    // Initial discovery with one master and one replica (replica-1)\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertMasterNodeAvailable(cache);\n    assertReplicasAvailable(cache, REPLICA_1_HOST);\n\n    // Simulate rediscovery - master only\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    // Master should still be available\n    // Replica should be cleared\n    assertMasterNodeAvailable(cache);\n    assertNoReplicasAvailable(cache);\n\n    // Simulate rediscovery - another replica (replica-2) coming back\n    cache.reset();\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertReplicasAvailable(cache, REPLICA_2_HOST);\n  }\n\n  @Test\n  public void testResetWithReplicaSlots() {\n    // This test verifies that reset() properly clears replica slots\n\n    JedisClusterInfoCache cache = createCacheWithReplicasEnabled();\n\n    // Mock the cluster slots responses\n    when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, \"SLOTS\")))).thenReturn(\n        masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST));\n\n    // Initial discovery\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertReplicasAvailable(cache, REPLICA_1_HOST);\n\n    // Call reset() - this should clear and nullify replica slots\n    cache.reset();\n\n    assertNoReplicasAvailable(cache);\n\n    // Rediscovery should work correctly\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertReplicasAvailable(cache, REPLICA_1_HOST);\n  }\n\n  @Test\n  public void getPrimaryNodesAfterReplicaNodeRemovalAndRediscovery() {\n    // Create client config with read-only replicas enabled\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n            .readOnlyForRedisClusterReplicas().build();\n\n    Set<HostAndPort> startNodes = new HashSet<>();\n    startNodes.add(MASTER_HOST);\n\n    JedisClusterInfoCache cache = new JedisClusterInfoCache(clientConfig, startNodes);\n\n    // Mock the cluster slots responses\n    when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, \"SLOTS\")))).thenReturn(\n                    masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST)).thenReturn(masterOnlySlotsResponse())\n            .thenReturn(masterReplica2SlotsResponse());\n\n    // Initial discovery with one master and one replica (replica-1)\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertThat(cache.getPrimaryNodes(),aMapWithSize(1));\n    assertThat(cache.getPrimaryNodes(),\n                    hasEntry(equalTo(getNodeKey(MASTER_HOST)), equalTo(cache.getNode(MASTER_HOST))));\n\n    // Simulate rediscovery - master only\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertThat(  cache.getPrimaryNodes(),aMapWithSize(1));\n    assertThat(cache.getPrimaryNodes(),\n            hasEntry(equalTo(getNodeKey(MASTER_HOST)), equalTo(cache.getNode(MASTER_HOST))));\n  }\n\n  @Test\n  public void getPrimaryNodesAfterMasterReplicaFailover() {\n    // Create client config with read-only replicas enabled\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n            .readOnlyForRedisClusterReplicas().build();\n\n    Set<HostAndPort> startNodes = new HashSet<>();\n    startNodes.add(MASTER_HOST);\n\n    JedisClusterInfoCache cache = new JedisClusterInfoCache(clientConfig, startNodes);\n\n    // Mock the cluster slots responses\n    when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, \"SLOTS\"))))\n            .thenReturn(masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST))\n            .thenReturn(masterReplicaSlotsResponse(REPLICA_1_HOST, MASTER_HOST));\n\n    // Initial discovery with one master and one replica (replica-1)\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertThat(cache.getPrimaryNodes(),aMapWithSize(1));\n    assertThat(cache.getPrimaryNodes(),\n            hasEntry(equalTo(getNodeKey(MASTER_HOST)), equalTo(cache.getNode(MASTER_HOST))));\n\n    // Simulate rediscovery - master only\n    cache.discoverClusterNodesAndSlots(mockConnection);\n    assertThat(  cache.getPrimaryNodes(),aMapWithSize(1));\n    assertThat(cache.getPrimaryNodes(),\n            hasEntry(equalTo(getNodeKey(REPLICA_1_HOST)), equalTo(cache.getNode(REPLICA_1_HOST))));\n  }\n\n  private List<Object> masterReplicaSlotsResponse(HostAndPort masterHost, HostAndPort replicaHost) {\n    return createClusterSlotsResponse(\n            new SlotRange.Builder(0, 16383).master(masterHost, masterHost.toString() + \"-id\")\n                    .replica(replicaHost, replicaHost.toString() + \"-id\").build());\n  }\n\n  private List<Object> masterOnlySlotsResponse() {\n    return createClusterSlotsResponse(\n        new SlotRange.Builder(0, 16383).master(MASTER_HOST, \"master-id-1\").build());\n  }\n\n  private List<Object> masterReplica2SlotsResponse() {\n    return createClusterSlotsResponse(\n        new SlotRange.Builder(0, 16383).master(MASTER_HOST, \"master-id-1\")\n            .replica(REPLICA_2_HOST, \"replica-id-2\").build());\n  }\n\n  private JedisClusterInfoCache createCacheWithReplicasEnabled() {\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .readOnlyForRedisClusterReplicas().build();\n\n    return new JedisClusterInfoCache(clientConfig,\n        new HashSet<>(Collections.singletonList(MASTER_HOST)));\n  }\n\n  private void assertNoReplicasAvailable(JedisClusterInfoCache cache) {\n    List<ConnectionPool> caheReplicaNodePools = cache.getSlotReplicaPools(TEST_SLOT);\n    assertNull(caheReplicaNodePools);\n  }\n\n  private void assertReplicasAvailable(JedisClusterInfoCache cache, HostAndPort... replicaNodes) {\n    List<ConnectionPool> caheReplicaNodePools = cache.getSlotReplicaPools(TEST_SLOT);\n    assertEquals(replicaNodes.length, caheReplicaNodePools.size());\n    for (HostAndPort expectedReplica : replicaNodes) {\n      ConnectionPool expectedNodePool = cache.getNode(expectedReplica);\n      assertThat(caheReplicaNodePools, hasItem(expectedNodePool));\n    }\n  }\n\n  private void assertMasterNodeAvailable(JedisClusterInfoCache cache) {\n    HostAndPort masterNode = cache.getSlotNode(TEST_SLOT);\n    assertNotNull(masterNode);\n    assertEquals(MASTER_HOST, masterNode);\n  }\n\n  /**\n   * Helper method to create a cluster slots response with master and replica nodes\n   */\n  private List<Object> createClusterSlotsResponse(SlotRange... slotRanges) {\n    return Arrays.stream(slotRanges).map(this::clusterSlotRange).collect(Collectors.toList());\n  }\n\n  private List<Object> clusterSlotRange(SlotRange slotRange) {\n    List<Object> slotInfo = new ArrayList<>();\n    slotInfo.add((long) slotRange.start);\n    slotInfo.add((long) slotRange.end);\n    Node master = slotRange.master();\n    slotInfo.add(\n        Arrays.asList(master.getHost().getBytes(), (long) master.getPort(), master.id.getBytes()));\n    // Add replicas\n    slotRange.replicas().forEach(r -> slotInfo.add(\n        Arrays.asList(r.getHost().getBytes(), (long) r.getPort(), r.id.getBytes())));\n    return slotInfo;\n  }\n\n  static class SlotRange {\n    private final int start;\n    private final int end;\n    private final List<Node> nodes;\n\n    private SlotRange(int start, int end, List<Node> nodes) {\n      this.start = start;\n      this.end = end;\n      this.nodes = nodes;\n    }\n\n    public SlotRange.Builder builder(int start, int end) {\n      return new SlotRange.Builder(start, end);\n    }\n\n    public Node master() {\n      return nodes.get(0);\n    }\n\n    public List<Node> replicas() {\n      return nodes.subList(1, nodes.size());\n    }\n\n    static class Builder {\n      private final int start;\n      private final int end;\n      private final List<Node> nodes = new ArrayList<>();\n\n      public Builder(int start, int end) {\n        this.start = start;\n        this.end = end;\n      }\n\n      public Builder master(Node node) {\n        if (!nodes.isEmpty()) {\n          nodes.set(0, node);\n        } else {\n          nodes.add(node);\n        }\n        return this;\n      }\n\n      public Builder master(HostAndPort hostPort, String id) {\n        return master(new Node(hostPort, id));\n      }\n\n      public Builder replica(HostAndPort hostPort, String id) {\n        return replica(new Node(hostPort, id));\n      }\n\n      public Builder replica(Node node) {\n        if (nodes.isEmpty()) {\n          throw new IllegalStateException(\"Master node must be added before adding replicas\");\n        }\n        nodes.add(node);\n        return this;\n      }\n\n      public SlotRange build() {\n        return new SlotRange(start, end, nodes);\n      }\n\n    }\n\n  }\n\n  static class Node {\n    private final HostAndPort hostPort;\n    private final String id;\n\n    public Node(HostAndPort hostPort, String id) {\n      this.hostPort = hostPort;\n      this.id = id;\n    }\n\n    public HostAndPort getHostPort() {\n      return hostPort;\n    }\n\n    public String getHost() {\n      return hostPort.getHost();\n    }\n\n    public int getPort() {\n      return hostPort.getPort();\n    }\n\n    public String getId() {\n      return id;\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisClusterWithoutSetupTest.java",
    "content": "package redis.clients.jedis;\n\nimport static java.util.Collections.emptySet;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\n\npublic class JedisClusterWithoutSetupTest {\n\n  @Test\n  public void noStartNodes() {\n    JedisClusterOperationException operationException = assertThrows(\n        JedisClusterOperationException.class, () -> new JedisCluster(emptySet()));\n    assertEquals(\"No nodes to initialize cluster slots cache.\", operationException.getMessage());\n    assertEquals(0, operationException.getSuppressed().length);\n  }\n\n  @Test\n  public void uselessStartNodes() {\n    JedisClusterOperationException operationException = assertThrows(\n        JedisClusterOperationException.class,\n        () -> new JedisCluster(new HostAndPort(\"localhost\", 7378)));\n    assertEquals(\"Could not initialize cluster slots cache.\", operationException.getMessage());\n    assertEquals(1, operationException.getSuppressed().length);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisPoolTest.java",
    "content": "package redis.clients.jedis;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.time.Duration;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.apache.commons.pool2.PooledObject;\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.DefaultPooledObject;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.InvalidURIException;\nimport redis.clients.jedis.exceptions.JedisAccessControlException;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\npublic class JedisPoolTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  private static EndpointConfig endpointStandalone0;\n\n  private static EndpointConfig endpointStandalone1;\n\n  private String testKey;\n  private String testValue;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    endpointStandalone0 = Endpoints.getRedisEndpoint(\"standalone0\");\n    endpointStandalone1 = Endpoints.getRedisEndpoint(\"standalone1\");\n  }\n\n  @BeforeEach\n  public void setUpTestKey(TestInfo testInfo) {\n    testKey = testInfo.getDisplayName() + \"-key\";\n    testValue = testInfo.getDisplayName() + \"-value\";\n  }\n\n  @Test\n  public void checkConnections() {\n    JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000);\n    try (Jedis jedis = pool.getResource()) {\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void checkResourceWithConfig() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n    try (JedisPool pool = new JedisPool(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().socketTimeoutMillis(5000).build())) {\n\n      try (Jedis jedis = pool.getResource()) {\n        assertEquals(\"PONG\", jedis.ping());\n        assertEquals(5000, jedis.getClient().getSoTimeout());\n      }\n    }\n  }\n\n  @Test\n  public void checkCloseableConnections() throws Exception {\n    JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000);\n    try (Jedis jedis = pool.getResource()) {\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void checkConnectionWithDefaultHostAndPort() {\n    JedisPool pool = new JedisPool(new JedisPoolConfig());\n    try (Jedis jedis = pool.getResource()) {\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void checkResourceIsClosableAndReusable() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisPool pool = new JedisPool(config, endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000, endpointStandalone0.getPassword(), 0,\n        \"closable-reusable-pool\", false, null, null, null)) {\n\n      Jedis jedis = pool.getResource();\n      jedis.set(\"hello\", \"jedis\");\n      jedis.close();\n\n      Jedis jedis2 = pool.getResource();\n      assertSame(jedis, jedis2);\n      assertEquals(\"jedis\", jedis2.get(\"hello\"));\n      jedis2.close();\n    }\n  }\n\n  @Test\n  public void checkPoolRepairedWhenJedisIsBroken() {\n    JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort());\n    try (Jedis jedis = pool.getResource()) {\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.set(\"foo\", \"0\");\n      jedis.disconnect();\n    }\n\n    try (Jedis jedis = pool.getResource()) {\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.incr(\"foo\");\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void checkPoolOverflow() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisPool pool = new JedisPool(config, endpointStandalone0.getHost(), endpointStandalone0.getPort());\n        Jedis jedis = pool.getResource()) {\n      jedis.auth(endpointStandalone0.getPassword());\n\n      assertThrows(JedisException.class, pool::getResource);\n    }\n  }\n\n  @Test\n  public void securePool() {\n    JedisPoolConfig config = new JedisPoolConfig();\n    config.setTestOnBorrow(true);\n    JedisPool pool = new JedisPool(config, endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000, endpointStandalone0.getPassword());\n    try (Jedis jedis = pool.getResource()) {\n      jedis.set(\"foo\", \"bar\");\n    }\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void nonDefaultDatabase() {\n    try (JedisPool pool0 = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000,\n        endpointStandalone0.getPassword()); Jedis jedis0 = pool0.getResource()) {\n      jedis0.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis0.get(\"foo\"));\n    }\n\n    try (JedisPool pool1 = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000,\n        endpointStandalone0.getPassword(), 1); Jedis jedis1 = pool1.getResource()) {\n      assertNull(jedis1.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void startWithUrlString() {\n    try (Jedis j = new Jedis(endpointStandalone1.getHostAndPort())) {\n      j.auth(endpointStandalone1.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (JedisPool pool = new JedisPool(\n        endpointStandalone1.getURIBuilder().credentials(\"\", endpointStandalone1.getPassword()).path(\"/2\").build());\n        Jedis jedis = pool.getResource()) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void startWithUrl() throws URISyntaxException {\n    try (Jedis j = new Jedis(endpointStandalone1.getHostAndPort())) {\n      j.auth(endpointStandalone1.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (JedisPool pool = new JedisPool(\n        endpointStandalone1.getURIBuilder().credentials(\"\", endpointStandalone1.getPassword()).path(\"/2\").build());\n        Jedis jedis = pool.getResource()) {\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  public void shouldThrowInvalidURIExceptionForInvalidURI() throws URISyntaxException {\n    assertThrows(InvalidURIException.class, ()->new JedisPool(new URI(\"localhost:6380\")).close());\n  }\n\n  @Test\n  public void allowUrlWithNoDBAndNoPassword() throws URISyntaxException {\n    new JedisPool(endpointStandalone1.getURI().toString()).close();\n    new JedisPool(endpointStandalone1.getURI()).close();\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void selectDatabaseOnActivation() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000,\n        endpointStandalone0.getPassword())) {\n\n      Jedis jedis0 = pool.getResource();\n      assertEquals(0, jedis0.getDB());\n\n      jedis0.select(1);\n      assertEquals(1, jedis0.getDB());\n\n      jedis0.close();\n\n      Jedis jedis1 = pool.getResource();\n      assertSame(jedis1, jedis0);\n      assertEquals(0, jedis1.getDB());\n\n      jedis1.close();\n    }\n  }\n\n  @Test\n  public void customClientName() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000,\n        endpointStandalone0.getPassword(), 0, \"my_shiny_client_name\"); Jedis jedis = pool.getResource()) {\n\n      assertEquals(\"my_shiny_client_name\", jedis.clientGetname());\n    }\n  }\n\n  @Test\n  public void invalidClientName() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000,\n        endpointStandalone0.getPassword(), 0, \"invalid client name\"); Jedis jedis = pool.getResource()) {\n    } catch (Exception e) {\n      if (!e.getMessage().startsWith(\"client info cannot contain space\")) {\n       fail(\"invalid client name test fail\");\n      }\n    }\n  }\n\n  @Test\n  public void returnResourceDestroysResourceOnException() {\n\n    class CrashingJedis extends Jedis {\n      @Override\n      public void resetState() {\n        throw new RuntimeException();\n      }\n    }\n\n    final AtomicInteger destroyed = new AtomicInteger(0);\n\n    class CrashingJedisPooledObjectFactory implements PooledObjectFactory<Jedis> {\n\n      @Override\n      public PooledObject<Jedis> makeObject() throws Exception {\n        return new DefaultPooledObject<Jedis>(new CrashingJedis());\n      }\n\n      @Override\n      public void destroyObject(PooledObject<Jedis> p) throws Exception {\n        destroyed.incrementAndGet();\n      }\n\n      @Override\n      public boolean validateObject(PooledObject<Jedis> p) {\n        return true;\n      }\n\n      @Override\n      public void activateObject(PooledObject<Jedis> p) throws Exception {\n      }\n\n      @Override\n      public void passivateObject(PooledObject<Jedis> p) throws Exception {\n      }\n    }\n\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    JedisPool pool = new JedisPool(config, new CrashingJedisPooledObjectFactory());\n    Jedis crashingJedis = pool.getResource();\n\n    try {\n      crashingJedis.close();\n    } catch (Exception ignored) {\n    }\n\n    assertEquals(1, destroyed.get());\n  }\n\n  @Test\n  public void returnResourceShouldResetState() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    JedisPool pool = new JedisPool(config, endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000, endpointStandalone0.getPassword());\n\n    Jedis jedis = pool.getResource();\n    try {\n      jedis.set(\"hello\", \"jedis\");\n      Transaction t = jedis.multi();\n      t.set(\"hello\", \"world\");\n    } finally {\n      jedis.close();\n    }\n\n    try (Jedis jedis2 = pool.getResource()) {\n      assertSame(jedis, jedis2);\n      assertEquals(\"jedis\", jedis2.get(\"hello\"));\n    }\n\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void getNumActiveWhenPoolIsClosed() {\n    JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000,\n        endpointStandalone0.getPassword(), 0, \"my_shiny_client_name\");\n\n    try (Jedis j = pool.getResource()) {\n      j.ping();\n    }\n\n    pool.close();\n    assertEquals(0, pool.getNumActive());\n  }\n\n  @Test\n  public void getNumActiveReturnsTheCorrectNumber() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000)) {\n      Jedis jedis = pool.getResource();\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n\n      assertEquals(1, pool.getNumActive());\n\n      Jedis jedis2 = pool.getResource();\n      jedis.auth(endpointStandalone0.getPassword());\n      jedis.set(\"foo\", \"bar\");\n\n      assertEquals(2, pool.getNumActive());\n\n      jedis.close();\n      assertEquals(1, pool.getNumActive());\n\n      jedis2.close();\n\n      assertEquals(0, pool.getNumActive());\n    }\n  }\n\n  @Test\n  public void testAddObject() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000)) {\n      pool.addObjects(1);\n      assertEquals(1, pool.getNumIdle());\n    }\n  }\n\n  @Test\n  public void closeResourceTwice() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000)) {\n      Jedis j = pool.getResource();\n      j.auth(endpointStandalone0.getPassword());\n      j.ping();\n      j.close();\n      j.close();\n    }\n  }\n\n  @Test\n  public void closeBrokenResourceTwice() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(), endpointStandalone0.getPort(), 2000)) {\n      Jedis j = pool.getResource();\n      try {\n        // make connection broken\n        j.getClient().getOne();\n        fail();\n      } catch (Exception e) {\n        assertInstanceOf(JedisConnectionException.class, e);\n      }\n      assertTrue(j.isBroken());\n      j.close();\n      j.close();\n    }\n  }\n\n  @Test\n  public void testCloseConnectionOnMakeObject() {\n    JedisPoolConfig config = new JedisPoolConfig();\n    config.setTestOnBorrow(true);\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHost(),\n        endpointStandalone0.getPort(), 2000, \"wrong pass\");\n        Jedis jedis = new Jedis(endpointStandalone0.getURIBuilder().defaultCredentials().build())) {\n      int currentClientCount = getClientCount(jedis.clientList());\n      assertThrows(JedisAccessControlException.class, pool::getResource);\n      // wait for the redis server to close the connection\n      await().pollDelay(Duration.ofMillis(10)).atMost(500, MILLISECONDS)\n          .until(() -> getClientCount(jedis.clientList()) == currentClientCount);\n      assertEquals(currentClientCount, getClientCount(jedis.clientList()));\n    }\n  }\n\n  private int getClientCount(final String clientList) {\n    return clientList.split(\"\\n\").length;\n  }\n\n  @Test\n  public void testResetInvalidCredentials() {\n    DefaultRedisCredentialsProvider credentialsProvider\n        = new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, endpointStandalone0.getPassword()));\n    JedisFactory factory = new JedisFactory(endpointStandalone0.getHostAndPort(), DefaultJedisClientConfig.builder()\n        .credentialsProvider(credentialsProvider).clientName(\"my_shiny_client_name\").build());\n\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), factory)) {\n      Jedis obj1_ref;\n      try (Jedis obj1_1 = pool.getResource()) {\n        obj1_ref = obj1_1;\n        obj1_1.set(\"foo\", \"bar\");\n        assertEquals(\"bar\", obj1_1.get(\"foo\"));\n        assertEquals(1, pool.getNumActive());\n      }\n      assertEquals(0, pool.getNumActive());\n      try (Jedis obj1_2 = pool.getResource()) {\n        assertSame(obj1_ref, obj1_2);\n        assertEquals(1, pool.getNumActive());\n        credentialsProvider.setCredentials(new DefaultRedisCredentials(null, \"wrong password\"));\n        try (Jedis obj2 = pool.getResource()) {\n          fail(\"Should not get resource from pool\");\n        } catch (JedisException e) {\n          //ignore\n        }\n        assertEquals(1, pool.getNumActive());\n      }\n      assertEquals(0, pool.getNumActive());\n    }\n  }\n\n  @Test\n  public void testResetValidCredentials() {\n    DefaultRedisCredentialsProvider credentialsProvider\n        = new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, \"bad password\"));\n    JedisFactory factory = new JedisFactory(endpointStandalone0.getHostAndPort(), DefaultJedisClientConfig.builder()\n        .credentialsProvider(credentialsProvider).clientName(\"my_shiny_client_name\").build());\n\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), factory)) {\n      try (Jedis obj1 = pool.getResource()) {\n        fail(\"Should not get resource from pool\");\n      } catch (JedisException e) {\n        //ignore\n      }\n      assertEquals(0, pool.getNumActive());\n\n      credentialsProvider.setCredentials(new DefaultRedisCredentials(null, endpointStandalone0.getPassword()));\n      try (Jedis obj2 = pool.getResource()) {\n        obj2.set(\"foo\", \"bar\");\n        assertEquals(\"bar\", obj2.get(\"foo\"));\n      }\n    }\n  }\n\n  @Test\n  public void testWithResource() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHostAndPort(),\n        endpointStandalone0.getClientConfigBuilder().build())) {\n\n      pool.withResource(jedis -> {\n        jedis.set(testKey, testValue);\n      });\n\n      pool.withResource(jedis -> {\n        assertEquals(testValue, jedis.get(testKey));\n      });\n    }\n  }\n\n  @Test\n  public void testWithResourceReturnsConnectionToPool() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHostAndPort(),\n        endpointStandalone0.getClientConfigBuilder().build())) {\n\n      pool.withResource(jedis -> {\n        assertThat(pool.getNumActive(), equalTo(1));\n        jedis.set(\"foo\", \"bar\");\n      });\n\n      assertThat(pool.getNumActive(), equalTo(0));\n    }\n  }\n\n  @Test\n  public void testWithResourceGet() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHostAndPort(),\n        endpointStandalone0.getClientConfigBuilder().build())) {\n\n      String result = pool.withResourceGet(jedis -> {\n        jedis.set(testKey, testValue);\n        return jedis.get(testKey);\n      });\n\n      assertEquals(testValue, result);\n    }\n  }\n\n  @Test\n  public void testWithResourceGetReturnsConnectionToPool() {\n    try (JedisPool pool = new JedisPool(new JedisPoolConfig(), endpointStandalone0.getHostAndPort(),\n        endpointStandalone0.getClientConfigBuilder().build())) {\n\n      String result = pool.withResourceGet(jedis -> {\n        assertThat(pool.getNumActive(), equalTo(1));\n        jedis.set(\"foo\", \"bar\");\n        return jedis.get(\"foo\");\n      });\n\n      assertThat(result, equalTo(\"bar\"));\n      assertThat(pool.getNumActive(), equalTo(0));\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisPoolUnitTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.DefaultPooledObject;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\npublic class JedisPoolUnitTest {\n\n  private JedisPool pool;\n  private Jedis mockJedis;\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    mockJedis = mock(Jedis.class);\n    PooledObjectFactory<Jedis> mockFactory = mock(PooledObjectFactory.class);\n\n    when(mockFactory.makeObject()).thenReturn(new DefaultPooledObject<>(mockJedis));\n\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    pool = spy(new JedisPool(config, mockFactory));\n\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (pool != null && !pool.isClosed()) {\n      pool.close();\n    }\n  }\n\n  @Test\n  public void testWithResourceClosesConnection() {\n    pool.withResource(jedis -> assertEquals(mockJedis, jedis));\n\n    verify(mockJedis, times(1)).close();\n  }\n\n  @Test\n  public void testWithResourceGetClosesConnection() {\n    String result = pool.withResourceGet(jedis -> \"test-result\");\n\n    verify(mockJedis, times(1)).close();\n  }\n\n  @Test\n  public void testWithResourceGetReturnsResult() {\n    when(mockJedis.get(eq(\"test-key\"))).thenReturn(\"test-result\");\n    String result = pool.withResourceGet(jedis -> jedis.get(\"test-key\"));\n\n    verify(mockJedis, times(1)).close();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisPubSubBaseTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static redis.clients.jedis.Protocol.ResponseKeyword.MESSAGE;\nimport static redis.clients.jedis.Protocol.ResponseKeyword.SUBSCRIBE;\n\npublic class JedisPubSubBaseTest  {\n    @Test\n    public void testProceed_givenThreadInterrupt_exitLoop() throws InterruptedException {\n        // setup\n        final JedisPubSubBase<String> pubSub = new JedisPubSubBase<String>() {\n\n            @Override\n            public void onMessage(String channel, String message) {\n                fail(\"this should not happen when thread is interrupted\");\n            }\n\n            @Override\n            protected String encode(byte[] raw) {\n                return SafeEncoder.encode(raw);\n            }\n        };\n\n        final Connection mockConnection = mock(Connection.class);\n        final List<Object> mockSubscribe = Arrays.asList(\n                SUBSCRIBE.getRaw(), \"channel\".getBytes(), 1L\n        );\n        final List<Object> mockResponse = Arrays.asList(\n                MESSAGE.getRaw(), \"channel\".getBytes(), \"message\".getBytes()\n        );\n\n        when(mockConnection.getUnflushedObject()).\n\n                thenReturn(mockSubscribe, mockResponse);\n\n\n        final CountDownLatch countDownLatch = new CountDownLatch(1);\n        // action\n        final Thread thread = new Thread(() -> {\n            Thread.currentThread().interrupt();\n            pubSub.proceed(mockConnection, \"channel\");\n\n            countDownLatch.countDown();\n        });\n        thread.start();\n\n        assertTrue(countDownLatch.await(30, TimeUnit.MILLISECONDS));\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisSentinelPoolTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.assertSame;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\n\n@Tag(\"integration\")\npublic class JedisSentinelPoolTest {\n\n  private static final String MASTER_NAME = \"mymaster\";\n\n  protected static HostAndPort sentinel1;\n  protected static HostAndPort sentinel2;\n\n  protected final Set<String> sentinels = new HashSet<>();\n\n  @BeforeAll\n  public static void prepare() {\n    sentinel1 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort();\n    sentinel2 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-3\").getHostAndPort();\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    sentinels.clear();\n\n    sentinels.add(sentinel1.toString());\n    sentinels.add(sentinel2.toString());\n  }\n\n  @Test\n  public void repeatedSentinelPoolInitialization() {\n\n    for (int i = 0; i < 20; ++i) {\n      GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n\n      JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,\n          \"foobared\", 2);\n      pool.getResource().close();\n      pool.destroy();\n    }\n  }\n\n  @Test\n  public void initializeWithNotAvailableSentinelsShouldThrowException() {\n    Set<String> wrongSentinels = new HashSet<String>();\n    wrongSentinels.add(new HostAndPort(\"localhost\", 65432).toString());\n    wrongSentinels.add(new HostAndPort(\"localhost\", 65431).toString());\n\n    assertThrows(JedisConnectionException.class,\n        () -> new JedisSentinelPool(MASTER_NAME, wrongSentinels).close());\n  }\n\n  @Test\n  public void initializeWithNotMonitoredMasterNameShouldThrowException() {\n    final String wrongMasterName = \"wrongMasterName\";\n    assertThrows(JedisException.class, ()-> new JedisSentinelPool(wrongMasterName, sentinels).close());\n  }\n\n  @Test\n  public void checkCloseableConnections() throws Exception {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n\n    JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,\n        \"foobared\", 2);\n    Jedis jedis = pool.getResource();\n    jedis.auth(\"foobared\");\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    jedis.close();\n    pool.close();\n    assertTrue(pool.isClosed());\n  }\n\n  @Test\n  public void returnResourceShouldResetState() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,\n        \"foobared\", 2)) {\n\n      Jedis jedis = null;\n      try (Jedis jedis1 = pool.getResource()) {\n        jedis = jedis1;\n        jedis1.set(\"hello\", \"jedis\");\n        Transaction t = jedis1.multi();\n        t.set(\"hello\", \"world\");\n      }\n\n      try (Jedis jedis2 = pool.getResource()) {\n        assertSame(jedis, jedis2);\n        assertEquals(\"jedis\", jedis2.get(\"hello\"));\n      }\n    }\n  }\n\n  @Test\n  public void checkResourceIsCloseable() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,\n        \"foobared\", 2);\n\n    Jedis jedis = pool.getResource();\n    try {\n      jedis.set(\"hello\", \"jedis\");\n    } finally {\n      jedis.close();\n    }\n\n    Jedis jedis2 = pool.getResource();\n    try {\n      assertEquals(jedis, jedis2);\n    } finally {\n      jedis2.close();\n    }\n  }\n\n  @Test\n  public void customClientName() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,\n        \"foobared\", 0, \"my_shiny_client_name\");\n\n    Jedis jedis = pool.getResource();\n\n    try {\n      assertEquals(\"my_shiny_client_name\", jedis.clientGetname());\n    } finally {\n      jedis.close();\n      pool.destroy();\n    }\n\n    assertTrue(pool.isClosed());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisSentinelTest.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.JedisSentinelTestUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\npublic class JedisSentinelTest {\n\n  private static final String MASTER_NAME = \"mymaster\";\n  private static final String MONITOR_MASTER_NAME = \"mymastermonitor\";\n  private static final String REMOVE_MASTER_NAME = \"mymasterremove\";\n  private static final String FAILOVER_MASTER_NAME = \"mymasterfailover\";\n\n  protected static EndpointConfig master;\n  protected static HostAndPort sentinel;\n\n  protected static HostAndPort sentinelForFailover;\n\n  @BeforeAll\n  public static void prepare() {\n    master = Endpoints.getRedisEndpoint(\"standalone2-primary\");\n    sentinel = Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort();\n    sentinelForFailover = Endpoints.getRedisEndpoint(\"sentinel-failover\").getHostAndPort();\n  }\n\n  @BeforeEach\n  public void setup() throws InterruptedException {\n  }\n\n  @AfterEach\n  public void clear() throws InterruptedException {\n    // New Sentinel (after 2.8.1)\n    // when slave promoted to master (slave of no one), New Sentinel force\n    // to restore it (demote)\n    // so, promote(slaveof) slave to master has no effect, not same to old\n    // Sentinel's behavior\n    ensureRemoved(MONITOR_MASTER_NAME);\n    ensureRemoved(REMOVE_MASTER_NAME);\n  }\n\n  @Test\n  public void sentinel() {\n    Jedis j = new Jedis(sentinel);\n\n    try {\n      List<Map<String, String>> masters = j.sentinelMasters();\n\n      boolean inMasters = false;\n      for (Map<String, String> master : masters)\n        if (MASTER_NAME.equals(master.get(\"name\"))) inMasters = true;\n\n      assertTrue(inMasters);\n\n      List<String> masterHostAndPort = j.sentinelGetMasterAddrByName(MASTER_NAME);\n      HostAndPort masterFromSentinel = new HostAndPort(masterHostAndPort.get(0),\n          Integer.parseInt(masterHostAndPort.get(1)));\n      assertEquals(master.getPort(), masterFromSentinel.getPort());\n\n      List<Map<String, String>> slaves = j.sentinelReplicas(MASTER_NAME);\n      assertFalse(slaves.isEmpty());\n      assertEquals(master.getPort(), Integer.parseInt(slaves.get(0).get(\"master-port\")));\n\n      // DO NOT RE-RUN TEST TOO FAST, RESET TAKES SOME TIME TO... RESET\n      assertEquals(Long.valueOf(1), j.sentinelReset(MASTER_NAME));\n      assertEquals(Long.valueOf(0), j.sentinelReset(\"woof\" + MASTER_NAME));\n    } finally {\n      j.close();\n    }\n  }\n\n  @Test\n  public void sentinelFailover() throws InterruptedException {\n    Jedis j = new Jedis(sentinelForFailover);\n    Jedis j2 = new Jedis(sentinelForFailover);\n\n    try {\n      List<String> masterHostAndPort = j.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);\n      HostAndPort currentMaster = new HostAndPort(masterHostAndPort.get(0),\n          Integer.parseInt(masterHostAndPort.get(1)));\n\n      JedisSentinelTestUtil.waitForNewPromotedMaster(FAILOVER_MASTER_NAME, j, j2);\n\n      masterHostAndPort = j.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);\n      HostAndPort newMaster = new HostAndPort(masterHostAndPort.get(0),\n          Integer.parseInt(masterHostAndPort.get(1)));\n\n      assertNotEquals(newMaster, currentMaster);\n    } finally {\n      j.close();\n      j2.close();\n    }\n  }\n\n  @Test\n  public void sentinelMonitor() {\n    Jedis j = new Jedis(sentinel);\n\n    try {\n      // monitor new master\n      String result = j.sentinelMonitor(MONITOR_MASTER_NAME, master.getHost(), master.getPort(), 1);\n      assertEquals(\"OK\", result);\n\n      // already monitored\n      try {\n        j.sentinelMonitor(MONITOR_MASTER_NAME, master.getHost(), master.getPort(), 1);\n        fail();\n      } catch (JedisDataException e) {\n        // pass\n      }\n    } finally {\n      j.close();\n    }\n  }\n\n  @Test\n  public void sentinelRemove() {\n    Jedis j = new Jedis(sentinel);\n\n    try {\n      ensureMonitored(sentinel, REMOVE_MASTER_NAME, master.getHost(), master.getPort(), 1);\n\n      String result = j.sentinelRemove(REMOVE_MASTER_NAME);\n      assertEquals(\"OK\", result);\n\n      // not exist\n      try {\n        result = j.sentinelRemove(REMOVE_MASTER_NAME);\n        assertNotEquals(\"OK\", result);\n        fail();\n      } catch (JedisDataException e) {\n        // pass\n      }\n    } finally {\n      j.close();\n    }\n  }\n\n  @Test\n  public void sentinelSet() {\n    Jedis j = new Jedis(sentinel);\n\n    try {\n      Map<String, String> parameterMap = new HashMap<String, String>();\n      parameterMap.put(\"down-after-milliseconds\", String.valueOf(1234));\n      parameterMap.put(\"parallel-syncs\", String.valueOf(3));\n      parameterMap.put(\"quorum\", String.valueOf(2));\n      j.sentinelSet(MASTER_NAME, parameterMap);\n\n      List<Map<String, String>> masters = j.sentinelMasters();\n      for (Map<String, String> master : masters) {\n        if (master.get(\"name\").equals(MASTER_NAME)) {\n          assertEquals(1234, Integer.parseInt(master.get(\"down-after-milliseconds\")));\n          assertEquals(3, Integer.parseInt(master.get(\"parallel-syncs\")));\n          assertEquals(2, Integer.parseInt(master.get(\"quorum\")));\n        }\n      }\n\n      parameterMap.put(\"quorum\", String.valueOf(1));\n      j.sentinelSet(MASTER_NAME, parameterMap);\n    } finally {\n      j.close();\n    }\n  }\n\n  private void ensureMonitored(HostAndPort sentinel, String masterName, String ip, int port,\n      int quorum) {\n\n    try (Jedis j = new Jedis(sentinel)) {\n      j.sentinelMonitor(masterName, ip, port, quorum);\n    } catch (JedisDataException e) {\n      // ignore\n    }\n  }\n\n  private void ensureRemoved(String masterName) {\n    Jedis j = new Jedis(sentinel);\n    try {\n      j.sentinelRemove(masterName);\n    } catch (JedisDataException e) {\n    } finally {\n      j.close();\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisShardedPubSubBaseTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static redis.clients.jedis.Protocol.ResponseKeyword.SMESSAGE;\nimport static redis.clients.jedis.Protocol.ResponseKeyword.SSUBSCRIBE;\n\npublic class JedisShardedPubSubBaseTest {\n\n    @Test\n    public void testProceed_givenThreadInterrupt_exitLoop() throws InterruptedException {\n        // setup\n        final JedisShardedPubSubBase<String> pubSub = new JedisShardedPubSubBase<String>() {\n\n            @Override\n            public void onSMessage(String channel, String message) {\n                fail(\"this should not happen when thread is interrupted\");\n            }\n\n            @Override\n            protected String encode(byte[] raw) {\n                return new String(raw);\n            }\n\n        };\n\n        final Connection mockConnection = mock(Connection.class);\n        final List<Object> mockSubscribe = Arrays.asList(\n                SSUBSCRIBE.getRaw(), \"channel\".getBytes(), 1L\n        );\n        final List<Object> mockResponse = Arrays.asList(\n                SMESSAGE.getRaw(), \"channel\".getBytes(), \"message\".getBytes()\n        );\n        when(mockConnection.getUnflushedObject()).thenReturn(mockSubscribe, mockResponse);\n\n\n        final CountDownLatch countDownLatch = new CountDownLatch(1);\n        // action\n        final Thread thread = new Thread(() -> {\n            Thread.currentThread().interrupt();\n            pubSub.proceed(mockConnection, \"channel\");\n\n            countDownLatch.countDown();\n        });\n        thread.start();\n\n        assertTrue(countDownLatch.await(100, TimeUnit.MILLISECONDS));\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/JedisTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.exceptions.InvalidURIException;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.commands.jedis.JedisCommandsTestBase;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class JedisTest extends JedisCommandsTestBase {\n\n  public JedisTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void useWithoutConnecting() {\n    try (Jedis j = new Jedis()) {\n      j.auth(endpoint.getPassword());\n      j.dbSize();\n    }\n  }\n\n  @Test\n  public void checkBinaryData() {\n    byte[] bigdata = new byte[1777];\n    for (int b = 0; b < bigdata.length; b++) {\n      bigdata[b] = (byte) ((byte) b % 255);\n    }\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"data\", SafeEncoder.encode(bigdata));\n\n    assertEquals(\"OK\", jedis.hmset(\"foo\", hash));\n    assertEquals(hash, jedis.hgetAll(\"foo\"));\n  }\n\n  @Test\n  public void connectWithConfig() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().build())) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void connectWithEmptyConfigInterface() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), new JedisClientConfig() {\n    })) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void connectWithConfigInterface() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), new JedisClientConfig() {\n      @Override\n      public String getPassword() {\n        return endpoint.getPassword();\n      }\n    })) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void connectOnResp3Protocol() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().protocol(RedisProtocol.RESP3).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertEquals(RedisProtocol.RESP3, jedis.getConnection().getRedisProtocol());\n    }\n  }\n\n  @Test\n  public void connectOnResp3ProtocolShortcut() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().resp3().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertEquals(RedisProtocol.RESP3, jedis.getConnection().getRedisProtocol());\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void timeoutConnection() throws Exception {\n    final String TIMEOUT_STR = \"timeout\";\n\n    Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(15000).build());\n\n    // read current config\n    final String timeout = jedis.configGet(TIMEOUT_STR).get(TIMEOUT_STR);\n    try {\n      jedis.configSet(\"timeout\", \"1\");\n      Thread.sleep(5000);\n      try {\n        jedis.hmget(\"foobar\", \"foo\");\n        fail(\"Operation should throw JedisConnectionException\");\n      } catch (JedisConnectionException jce) {\n        // expected\n      }\n      jedis.close();\n    } finally {\n      // reset config\n      jedis = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build());\n      jedis.configSet(TIMEOUT_STR, timeout);\n      jedis.close();\n    }\n  }\n\n  @Test\n  public void infiniteTimeout() throws Exception {\n    try (Jedis timeoutJedis = new Jedis(endpoint.getHost(), endpoint.getPort(), 200, 200, 200)) {\n      timeoutJedis.auth(endpoint.getPassword());\n      try {\n        timeoutJedis.blpop(0, \"foo\");\n        fail(\"SocketTimeoutException should occur\");\n      } catch (JedisConnectionException jce) {\n        assertSame(java.net.SocketTimeoutException.class, jce.getCause().getClass());\n        assertEquals(\"Read timed out\", jce.getCause().getMessage());\n        assertTrue(timeoutJedis.isBroken());\n      }\n    }\n  }\n\n  @Test\n  public void failWhenSendingNullValues() {\n    assertThrows(IllegalArgumentException.class, () -> jedis.set(\"foo\", null));\n  }\n\n  @Test\n  public void shouldThrowInvalidURIExceptionForInvalidURI() {\n    assertThrows(InvalidURIException.class, () -> new Jedis(new URI(\"redis://localhost\")).close());\n  }\n\n  //\n  // @Test\n  // public void shouldReconnectToSameDB() throws IOException {\n  // jedis.select(1);\n  // jedis.set(\"foo\", \"bar\");\n  // jedis.getClient().getSocket().shutdownInput();\n  // jedis.getClient().getSocket().shutdownOutput();\n  // assertEquals(\"bar\", jedis.get(\"foo\"));\n  // }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void connectWithUrl() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone1\");\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      j.auth(endpoint.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (Jedis j2 = new Jedis(\n        endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build().toString())) {\n      assertEquals(\"PONG\", j2.ping());\n      assertEquals(\"bar\", j2.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void connectWithUri() throws URISyntaxException {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone1\");\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      j.auth(endpoint.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (\n        Jedis jedis = new Jedis(endpoint.getURIBuilder().defaultCredentials().path(\"/2\").build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void connectWithUrlOnResp3() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone1\");\n\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      j.auth(endpoint.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (Jedis j2 = new Jedis(\n        endpoint.getURIBuilder().defaultCredentials().path(\"/2?protocol=3\").build().toString())) {\n      assertEquals(\"PONG\", j2.ping());\n      assertEquals(\"bar\", j2.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void connectWithUriOnResp3() throws URISyntaxException {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone1\");\n\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      j.auth(endpoint.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (Jedis jedis = new Jedis(\n        endpoint.getURIBuilder().defaultCredentials().path(\"/2?protocol=3\").build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  public void shouldNotUpdateDbIndexIfSelectFails() {\n    int currentDb = jedis.getDB();\n    try {\n      int invalidDb = -1;\n      jedis.select(invalidDb);\n\n      fail(\"Should throw an exception if tried to select invalid db\");\n    } catch (JedisException e) {\n      assertEquals(currentDb, jedis.getDB());\n    }\n  }\n\n  @Test\n  public void allowUrlWithNoDBAndNoPassword() {\n    EndpointConfig endpointStandalone1 = Endpoints.getRedisEndpoint(\"standalone1\");\n\n    try (Jedis j1 = new Jedis(endpointStandalone1.getURI().toString())) {\n      j1.auth(endpointStandalone1.getPassword());\n      // assertEquals(\"localhost\", j1.getClient().getHost());\n      // assertEquals(6380, j1.getClient().getPort());\n      assertEquals(0, j1.getDB());\n    }\n\n    try (Jedis j2 = new Jedis(endpointStandalone1.getURI().toString())) {\n      j2.auth(endpointStandalone1.getPassword());\n      // assertEquals(\"localhost\", j2.getClient().getHost());\n      // assertEquals(6380, j2.getClient().getPort());\n      assertEquals(0, j2.getDB());\n    }\n  }\n\n  @Test\n  public void uriWithDBindexShouldUseTimeout() throws URISyntaxException, IOException {\n    int fakePort = 6378;\n    int timeoutMillis = 3250;\n    int deltaMillis = 500;\n    URI uri = new URI(String.format(\"redis://localhost:%d/1\", fakePort));\n    Instant start = Instant.now();\n\n    try (ServerSocket server = new ServerSocket(fakePort);\n        Jedis jedis = new Jedis(uri, timeoutMillis)) {\n      fail(\"Jedis should fail to connect to a fake port\");\n    } catch (JedisConnectionException ex) {\n      assertSame(SocketTimeoutException.class, ex.getCause().getClass());\n      assertEquals(timeoutMillis, Duration.between(start, Instant.now()).toMillis(), deltaMillis);\n    }\n  }\n\n  @Test\n  public void checkCloseable() {\n    Jedis bj = new Jedis();\n    bj.close();\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void checkCloseableAfterConnect() {\n    Jedis bj = new Jedis();\n    bj.connect();\n    bj.close();\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void checkCloseableAfterCommand() {\n    Jedis bj = new Jedis();\n    bj.auth(endpoint.getPassword());\n    bj.close();\n  }\n\n  @Test\n  public void checkDisconnectOnQuit() {\n    jedis.disconnect();\n    assertFalse(jedis.isConnected());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\", message = \"see https://redis.io/docs/latest/commands/client-setinfo/\")\n  public void clientSetInfoDefault() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder()\n        .clientSetInfoConfig(ClientSetInfoConfig.DEFAULT).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      String info = jedis.clientInfo();\n      assertTrue(info.contains(\"lib-name=\" + JedisMetaInfo.getArtifactId()));\n      assertTrue(info.contains(\"lib-ver=\" + JedisMetaInfo.getVersion()));\n    }\n  }\n\n  @Test\n  public void clientSetInfoDisabled() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder()\n        .clientSetInfoConfig(ClientSetInfoConfig.DISABLED).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      String info = jedis.clientInfo();\n      assertFalse(info.contains(\"lib-name=\" + JedisMetaInfo.getArtifactId()));\n      assertFalse(info.contains(\"lib-ver=\" + JedisMetaInfo.getVersion()));\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\", message = \"@see https://redis.io/docs/latest/commands/client-setinfo/\")\n  public void clientSetInfoLibNameSuffix() {\n    final String libNameSuffix = \"for-redis\";\n    ClientSetInfoConfig setInfoConfig = ClientSetInfoConfig.withLibNameSuffix(libNameSuffix);\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().clientSetInfoConfig(setInfoConfig).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      String info = jedis.clientInfo();\n      assertTrue(\n        info.contains(\"lib-name=\" + JedisMetaInfo.getArtifactId() + '(' + libNameSuffix + ')'));\n      assertTrue(info.contains(\"lib-ver=\" + JedisMetaInfo.getVersion()));\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\", message = \"@see https://redis.io/docs/latest/commands/client-setinfo/\")\n  public void clientSetInfoWithUpstreamDriver() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"3.2.0\")\n        .build();\n    ClientSetInfoConfig setInfoConfig = new ClientSetInfoConfig(driverInfo);\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().clientSetInfoConfig(setInfoConfig).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      String info = jedis.clientInfo();\n      assertTrue(\n        info.contains(\"lib-name=\" + JedisMetaInfo.getArtifactId() + \"(spring-data-redis_v3.2.0)\"));\n      assertTrue(info.contains(\"lib-ver=\" + JedisMetaInfo.getVersion()));\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\", message = \"@see https://redis.io/docs/latest/commands/client-setinfo/\")\n  public void clientSetInfoWithMultipleUpstreamDrivers() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"3.2.0\")\n        .addUpstreamDriver(\"lettuce-core\", \"6.4.1\").addUpstreamDriver(\"redisson\", \"3.25.0\").build();\n    ClientSetInfoConfig setInfoConfig = new ClientSetInfoConfig(driverInfo);\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().clientSetInfoConfig(setInfoConfig).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      String info = jedis.clientInfo();\n      assertTrue(info.contains(\"lib-name=\" + JedisMetaInfo.getArtifactId()\n          + \"(redisson_v3.25.0;lettuce-core_v6.4.1;spring-data-redis_v3.2.0)\"));\n      assertTrue(info.contains(\"lib-ver=\" + JedisMetaInfo.getVersion()));\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ManagedConnectionProviderTest.java",
    "content": "package redis.clients.jedis;\n\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.providers.ManagedConnectionProvider;\nimport redis.clients.jedis.util.IOUtils;\n\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\npublic class ManagedConnectionProviderTest {\n\n  private Connection connection;\n\n  @BeforeEach\n  public void setUp() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n    connection = new Connection(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build());\n  }\n\n  @AfterEach\n  public void tearDown() {\n    IOUtils.closeQuietly(connection);\n  }\n\n  @Test\n  public void test() {\n    ManagedConnectionProvider managed = new ManagedConnectionProvider();\n    try (UnifiedJedis jedis = new UnifiedJedis(managed)) {\n      try {\n        jedis.get(\"any\");\n        fail(\"Should get NPE.\");\n      } catch (NullPointerException npe) { }\n      managed.setConnection(connection);\n      assertNull(jedis.get(\"any\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/MigratePipeliningTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.both;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasToString;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.commands.jedis.JedisCommandsTestBase;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class MigratePipeliningTest extends JedisCommandsTestBase {\n\n  private static final byte[] bfoo = { 0x01, 0x02, 0x03 };\n  private static final byte[] bbar = { 0x04, 0x05, 0x06 };\n  private static final byte[] bfoo1 = { 0x07, 0x08, 0x01 };\n  private static final byte[] bbar1 = { 0x09, 0x00, 0x01 };\n  private static final byte[] bfoo2 = { 0x07, 0x08, 0x02 };\n  private static final byte[] bbar2 = { 0x09, 0x00, 0x02 };\n  private static final byte[] bfoo3 = { 0x07, 0x08, 0x03 };\n  private static final byte[] bbar3 = { 0x09, 0x00, 0x03 };\n\n  private static EndpointConfig destEndpoint;\n\n  private static EndpointConfig destEndpointWithAuth;\n\n  private static String host;\n  private static int port;\n  private static int portAuth;\n  private static final int db = 2;\n  private static final int dbAuth = 3;\n  private static final int timeout = Protocol.DEFAULT_TIMEOUT;\n\n  private Jedis dest;\n  private Jedis destAuth;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    destEndpoint = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n    destEndpointWithAuth = Endpoints.getRedisEndpoint(\"standalone1\");\n    host = destEndpoint.getHost();\n    port = destEndpoint.getPort();\n    portAuth = destEndpointWithAuth.getPort();\n  }\n\n  public MigratePipeliningTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n\n    dest = new Jedis(host, port, 500);\n    dest.flushAll();\n    dest.select(db);\n\n    destAuth = new Jedis(destEndpointWithAuth.getHostAndPort(),\n        destEndpointWithAuth.getClientConfigBuilder().build());\n    destAuth.flushAll();\n    destAuth.select(dbAuth);\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    dest.close();\n    destAuth.close();\n    super.tearDown();\n  }\n\n  @Test\n  public void noKey() {\n    Pipeline p = jedis.pipelined();\n\n    p.migrate(host, port, \"foo\", db, timeout);\n    p.migrate(host, port, bfoo, db, timeout);\n    p.migrate(host, port, db, timeout, new MigrateParams(), \"foo1\", \"foo2\", \"foo3\");\n    p.migrate(host, port, db, timeout, new MigrateParams(), bfoo1, bfoo2, bfoo3);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"NOKEY\", \"NOKEY\", \"NOKEY\", \"NOKEY\"));\n  }\n\n  @Test\n  public void migrate() {\n    assertNull(dest.get(\"foo\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.migrate(host, port, \"foo\", db, timeout);\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertEquals(\"bar\", dest.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateBinary() {\n    assertNull(dest.get(bfoo));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(bfoo, bbar);\n    p.migrate(host, port, bfoo, db, timeout);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertArrayEquals(bbar, dest.get(bfoo));\n  }\n\n  @Test\n  public void migrateEmptyParams() {\n    assertNull(dest.get(\"foo\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.migrate(host, port, db, timeout, new MigrateParams(), \"foo\");\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertEquals(\"bar\", dest.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateEmptyParamsBinary() {\n    assertNull(dest.get(bfoo));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(bfoo, bbar);\n    p.migrate(host, port, db, timeout, new MigrateParams(), bfoo);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertArrayEquals(bbar, dest.get(bfoo));\n  }\n\n  @Test\n  public void migrateCopy() {\n    assertNull(dest.get(\"foo\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.migrate(host, port, db, timeout, new MigrateParams().copy(), \"foo\");\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", \"bar\"));\n\n    assertEquals(\"bar\", dest.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateCopyBinary() {\n    assertNull(dest.get(bfoo));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(bfoo, bbar);\n    p.migrate(host, port, db, timeout, new MigrateParams().copy(), bfoo);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", bbar));\n\n    assertArrayEquals(bbar, dest.get(bfoo));\n  }\n\n  @Test\n  public void migrateReplace() {\n    dest.set(\"foo\", \"bar2\");\n\n    assertEquals(\"bar2\", dest.get(\"foo\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar1\");\n    p.migrate(host, port, db, timeout, new MigrateParams().replace(), \"foo\");\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertEquals(\"bar1\", dest.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateReplaceBinary() {\n    dest.set(bfoo, bbar2);\n\n    assertArrayEquals(bbar2, dest.get(bfoo));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(bfoo, bbar1);\n    p.migrate(host, port, db, timeout, new MigrateParams().replace(), bfoo);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertArrayEquals(bbar1, dest.get(bfoo));\n  }\n\n  @Test\n  public void migrateCopyReplace() {\n    dest.set(\"foo\", \"bar2\");\n\n    assertEquals(\"bar2\", dest.get(\"foo\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar1\");\n    p.migrate(host, port, db, timeout, new MigrateParams().copy().replace(), \"foo\");\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", \"bar1\"));\n\n    assertEquals(\"bar1\", dest.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateCopyReplaceBinary() {\n    dest.set(bfoo, bbar2);\n\n    assertArrayEquals(bbar2, dest.get(bfoo));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(bfoo, bbar1);\n    p.migrate(host, port, db, timeout, new MigrateParams().copy().replace(), bfoo);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", bbar1));\n\n    assertArrayEquals(bbar1, dest.get(bfoo));\n  }\n\n  @Test\n  public void migrateAuth() {\n    assertNull(dest.get(\"foo\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.migrate(host, portAuth, dbAuth, timeout,\n        new MigrateParams().auth(destEndpointWithAuth.getPassword()), \"foo\");\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertEquals(\"bar\", destAuth.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateAuthBinary() {\n    assertNull(dest.get(bfoo));\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(bfoo, bbar);\n    p.migrate(host, portAuth, dbAuth, timeout,\n        new MigrateParams().auth(destEndpointWithAuth.getPassword()), bfoo);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertArrayEquals(bbar, destAuth.get(bfoo));\n  }\n\n  @Test\n  public void migrateAuth2() {\n    assertNull(jedis.get(\"foo\"));\n\n    Pipeline p = destAuth.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.migrate(endpoint.getHost(), endpoint.getPort(), 0, timeout,\n        new MigrateParams().auth2(endpoint.getUsername(), endpoint.getPassword()),\n        \"foo\");\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n  }\n\n  @Test\n  public void migrateAuth2Binary() {\n    assertNull(jedis.get(bfoo));\n\n    Pipeline p = dest.pipelined();\n\n    p.set(bfoo, bbar);\n    p.migrate(endpoint.getHost(), endpoint.getPort(), 0, timeout,\n        new MigrateParams().auth2(endpoint.getUsername(), endpoint.getPassword()),\n        bfoo);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\", null));\n\n    assertArrayEquals(bbar, jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateMulti() {\n    assertNull(dest.get(\"foo1\"));\n    assertNull(dest.get(\"foo2\"));\n    assertNull(dest.get(\"foo3\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.mset(\"foo1\", \"bar1\", \"foo2\", \"bar2\", \"foo3\", \"bar3\");\n    p.migrate(host, port, db, timeout, new MigrateParams(), \"foo1\", \"foo2\", \"foo3\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\"));\n\n    assertEquals(\"bar1\", dest.get(\"foo1\"));\n    assertEquals(\"bar2\", dest.get(\"foo2\"));\n    assertEquals(\"bar3\", dest.get(\"foo3\"));\n  }\n\n  @Test\n  public void migrateMultiBinary() {\n    assertNull(dest.get(bfoo1));\n    assertNull(dest.get(bfoo2));\n    assertNull(dest.get(bfoo3));\n\n    Pipeline p = jedis.pipelined();\n\n    p.mset(bfoo1, bbar1, bfoo2, bbar2, bfoo3, bbar3);\n    p.migrate(host, port, db, timeout, new MigrateParams(), bfoo1, bfoo2, bfoo3);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\"OK\", \"OK\"));\n\n    assertArrayEquals(bbar1, dest.get(bfoo1));\n    assertArrayEquals(bbar2, dest.get(bfoo2));\n    assertArrayEquals(bbar3, dest.get(bfoo3));\n  }\n\n  @Test\n  public void migrateConflict() {\n    dest.set(\"foo2\", \"bar\");\n\n    assertNull(dest.get(\"foo1\"));\n    assertEquals(\"bar\", dest.get(\"foo2\"));\n    assertNull(dest.get(\"foo3\"));\n\n    Pipeline p = jedis.pipelined();\n\n    p.mset(\"foo1\", \"bar1\", \"foo2\", \"bar2\", \"foo3\", \"bar3\");\n    p.migrate(host, port, db, timeout, new MigrateParams(), \"foo1\", \"foo2\", \"foo3\");\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\n            equalTo(\"OK\"),\n            both(instanceOf(JedisDataException.class)).and(hasToString(containsString(\"BUSYKEY\")))\n        ));\n\n    assertEquals(\"bar1\", dest.get(\"foo1\"));\n    assertEquals(\"bar\", dest.get(\"foo2\"));\n    assertEquals(\"bar3\", dest.get(\"foo3\"));\n  }\n\n  @Test\n  public void migrateConflictBinary() {\n    dest.set(bfoo2, bbar);\n\n    assertNull(dest.get(bfoo1));\n    assertArrayEquals(bbar, dest.get(bfoo2));\n    assertNull(dest.get(bfoo3));\n\n    Pipeline p = jedis.pipelined();\n\n    p.mset(bfoo1, bbar1, bfoo2, bbar2, bfoo3, bbar3);\n    p.migrate(host, port, db, timeout, new MigrateParams(), bfoo1, bfoo2, bfoo3);\n\n    assertThat(p.syncAndReturnAll(),\n        contains(\n            equalTo(\"OK\"),\n            both(instanceOf(JedisDataException.class)).and(hasToString(containsString(\"BUSYKEY\")))\n        ));\n\n    assertArrayEquals(bbar1, dest.get(bfoo1));\n    assertArrayEquals(bbar, dest.get(bfoo2));\n    assertArrayEquals(bbar3, dest.get(bfoo3));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/MultiDbClientTest.java",
    "content": "package redis.clients.jedis;\n\nimport eu.rekawek.toxiproxy.Proxy;\nimport eu.rekawek.toxiproxy.ToxiproxyClient;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.exceptions.JedisValidationException;\nimport redis.clients.jedis.mcf.DatabaseSwitchEvent;\nimport redis.clients.jedis.mcf.SwitchReason;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Basic tests for MultiDbClient functionality.\n */\n@Tag(\"integration\")\npublic class MultiDbClientTest {\n\n  private MultiDbClient client;\n  private static EndpointConfig endpoint1;\n  private static EndpointConfig endpoint2;\n\n  private static final ToxiproxyClient tp = new ToxiproxyClient(\"localhost\", 8474);\n  private static Proxy redisProxy1;\n  private static Proxy redisProxy2;\n\n  @BeforeAll\n  public static void setupAdminClients() throws IOException {\n    endpoint1 = Endpoints.getRedisEndpoint(\"redis-failover-1\");\n    endpoint2 = Endpoints.getRedisEndpoint(\"redis-failover-2\");\n    if (tp.getProxyOrNull(\"redis-1\") != null) {\n      tp.getProxy(\"redis-1\").delete();\n    }\n    if (tp.getProxyOrNull(\"redis-2\") != null) {\n      tp.getProxy(\"redis-2\").delete();\n    }\n\n    redisProxy1 = tp.createProxy(\"redis-1\", \"0.0.0.0:29379\", \"redis-failover-1:9379\");\n    redisProxy2 = tp.createProxy(\"redis-2\", \"0.0.0.0:29380\", \"redis-failover-2:9380\");\n  }\n\n  @AfterAll\n  public static void cleanupAdminClients() throws IOException {\n    if (redisProxy1 != null) redisProxy1.delete();\n    if (redisProxy2 != null) redisProxy2.delete();\n  }\n\n  @BeforeEach\n  void setUp() {\n    // Create a simple resilient client with mock endpoints for testing\n    MultiDbConfig clientConfig = MultiDbConfig.builder()\n        .database(endpoint1.getHostAndPort(), 100.0f, endpoint1.getClientConfigBuilder().build())\n        .database(endpoint2.getHostAndPort(), 50.0f, endpoint2.getClientConfigBuilder().build())\n        .build();\n\n    client = MultiDbClient.builder().multiDbConfig(clientConfig).build();\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (client != null) {\n      client.close();\n    }\n  }\n\n  @Test\n  void testAddRemoveDatabaseWithEndpointInterface() {\n    Endpoint newEndpoint = new HostAndPort(\"unavailable\", 6381);\n\n    assertDoesNotThrow(\n      () -> client.addDatabase(newEndpoint, 25.0f, DefaultJedisClientConfig.builder().build()));\n\n    assertThat(client.getDatabaseEndpoints(), hasItems(newEndpoint));\n\n    assertDoesNotThrow(() -> client.removeDatabase(newEndpoint));\n\n    assertThat(client.getDatabaseEndpoints(), not(hasItems(newEndpoint)));\n  }\n\n  @Test\n  void testAddRemoveDatabaseWithDatabaseConfig() {\n    // todo : (@ggivo) Replace HostAndPort with Endpoint\n    HostAndPort newEndpoint = new HostAndPort(\"unavailable\", 6381);\n\n    DatabaseConfig newConfig = DatabaseConfig\n        .builder(newEndpoint, DefaultJedisClientConfig.builder().build()).weight(25.0f).build();\n\n    assertDoesNotThrow(() -> client.addDatabase(newConfig));\n\n    assertThat(client.getDatabaseEndpoints(), hasItems(newEndpoint));\n\n    assertDoesNotThrow(() -> client.removeDatabase(newEndpoint));\n\n    assertThat(client.getDatabaseEndpoints(), not(hasItems(newEndpoint)));\n  }\n\n  @Test\n  void testSetActiveDatabase() {\n    Endpoint endpoint = client.getActiveDatabaseEndpoint();\n\n    awaitIsHealthy(endpoint1.getHostAndPort());\n    awaitIsHealthy(endpoint2.getHostAndPort());\n    // Ensure we have a healthy endpoint to switch to\n    Endpoint newEndpoint = client.getDatabaseEndpoints().stream()\n        .filter(e -> e.equals(endpoint) && client.isHealthy(e)).findFirst().orElse(null);\n    assertNotNull(newEndpoint);\n\n    // Switch to the new endpoint\n    client.setActiveDatabase(newEndpoint);\n\n    assertEquals(newEndpoint, client.getActiveDatabaseEndpoint());\n  }\n\n  @Test\n  void testBuilderWithMultipleEndpointTypes() {\n    MultiDbConfig clientConfig = MultiDbConfig.builder()\n        .database(endpoint1.getHostAndPort(), 100.0f, DefaultJedisClientConfig.builder().build())\n        .database(DatabaseConfig\n            .builder(endpoint2.getHostAndPort(), DefaultJedisClientConfig.builder().build())\n            .weight(50.0f).build())\n        .build();\n\n    try (MultiDbClient testClient = MultiDbClient.builder().multiDbConfig(clientConfig).build()) {\n      assertThat(testClient.getDatabaseEndpoints().size(), equalTo(2));\n      assertThat(testClient.getDatabaseEndpoints(),\n        hasItems(endpoint1.getHostAndPort(), endpoint2.getHostAndPort()));\n    }\n  }\n\n  @Test\n  public void testForceActiveDatabase() {\n    Endpoint endpoint = client.getActiveDatabaseEndpoint();\n\n    // Ensure we have a healthy endpoint to switch to\n    awaitIsHealthy(endpoint1.getHostAndPort());\n    awaitIsHealthy(endpoint2.getHostAndPort());\n    Endpoint newEndpoint = client.getDatabaseEndpoints().stream()\n        .filter(e -> e.equals(endpoint) && client.isHealthy(e)).findFirst().orElse(null);\n    assertNotNull(newEndpoint);\n\n    // Force switch to the new endpoint for 10 seconds\n    client.forceActiveDatabase(newEndpoint, Duration.ofMillis(100).toMillis());\n\n    // Verify the active endpoint has changed\n    assertEquals(newEndpoint, client.getActiveDatabaseEndpoint());\n  }\n\n  @Test\n  public void testForceActiveDatabaseWithNonHealthyEndpoint() {\n    Endpoint newEndpoint = new HostAndPort(\"unavailable\", 6381);\n    client.addDatabase(newEndpoint, 25.0f, DefaultJedisClientConfig.builder().build());\n\n    assertThrows(JedisValidationException.class,\n      () -> client.forceActiveDatabase(newEndpoint, Duration.ofMillis(100).toMillis()));\n  }\n\n  @Test\n  public void testForceActiveDatabaseWithNonExistingEndpoint() {\n    Endpoint newEndpoint = new HostAndPort(\"unavailable\", 6381);\n    assertThrows(JedisValidationException.class,\n      () -> client.forceActiveDatabase(newEndpoint, Duration.ofMillis(100).toMillis()));\n  }\n\n  @Test\n  public void testWithDatabaseSwitchListener() {\n\n    MultiDbConfig endpointsConfig = MultiDbConfig.builder()\n        .database(DatabaseConfig\n            .builder(endpoint1.getHostAndPort(), endpoint1.getClientConfigBuilder().build())\n            .weight(100.0f).build())\n        .database(DatabaseConfig\n            .builder(endpoint2.getHostAndPort(), endpoint2.getClientConfigBuilder().build())\n            .weight(50.0f).build())\n        .build();\n\n    Consumer<DatabaseSwitchEvent> eventConsumer;\n    List<DatabaseSwitchEvent> events = new ArrayList<>();\n    eventConsumer = events::add;\n\n    try (MultiDbClient testClient = MultiDbClient.builder().databaseSwitchListener(eventConsumer)\n        .multiDbConfig(endpointsConfig).build()) {\n\n      assertThat(events.size(), equalTo(0));\n\n      awaitIsHealthy(endpoint2.getHostAndPort());\n      testClient.setActiveDatabase(endpoint2.getHostAndPort());\n\n      assertThat(events.size(), equalTo(1));\n      assertThat(events.get(0).getEndpoint(), equalTo(endpoint2.getHostAndPort()));\n      assertThat(events.get(0).getReason(), equalTo(SwitchReason.FORCED));\n    }\n  }\n\n  @Test\n  void testGetWeight() {\n    // Verify we can get the initial weight set during configuration\n    float weight1 = client.getWeight(endpoint1.getHostAndPort());\n    float weight2 = client.getWeight(endpoint2.getHostAndPort());\n\n    assertEquals(100.0f, weight1);\n    assertEquals(50.0f, weight2);\n  }\n\n  @Test\n  void testSetWeight() {\n    Endpoint endpoint = endpoint1.getHostAndPort();\n\n    // Verify initial weight\n    assertEquals(100.0f, client.getWeight(endpoint));\n\n    // Set a new weight\n    client.setWeight(endpoint, 75.0f);\n\n    // Verify the weight has changed\n    assertEquals(75.0f, client.getWeight(endpoint));\n  }\n\n  @Test\n  void testSetWeightToZero() {\n    Endpoint endpoint = endpoint2.getHostAndPort();\n    assertThrows(IllegalArgumentException.class, () -> client.setWeight(endpoint, 0.0f));\n  }\n\n  @Test\n  void testSetWeightMultipleTimes() {\n    Endpoint endpoint = endpoint1.getHostAndPort();\n\n    // Set weight multiple times\n    client.setWeight(endpoint, 25.0f);\n    assertEquals(25.0f, client.getWeight(endpoint));\n\n    client.setWeight(endpoint, 80.0f);\n    assertEquals(80.0f, client.getWeight(endpoint));\n\n    client.setWeight(endpoint, 1.0f);\n    assertEquals(1.0f, client.getWeight(endpoint));\n  }\n\n  private void awaitIsHealthy(HostAndPort hostAndPort) {\n    await().atMost(Duration.ofSeconds(1)).until(() -> client.isHealthy(hostAndPort));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/PipeliningTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.matchesPattern;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.awaitility.Awaitility;\n\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.commands.jedis.JedisCommandsTestBase;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class PipeliningTest extends JedisCommandsTestBase {\n\n  private static final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  private static final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x11, 0x12, 0x13, 0x14 };\n  private static final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  private static final byte[] bbaz = { 0x09, 0x0A, 0x0B, 0x0C };\n\n  public PipeliningTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void pipeline() {\n    Pipeline p = jedis.pipelined();\n    p.set(\"foo\", \"bar\");\n    p.get(\"foo\");\n    List<Object> results = p.syncAndReturnAll();\n\n    assertEquals(2, results.size());\n    assertEquals(\"OK\", results.get(0));\n    assertEquals(\"bar\", results.get(1));\n  }\n\n  @Test\n  public void pipelineResponse() {\n    jedis.set(\"string\", \"foo\");\n    jedis.lpush(\"list\", \"foo\");\n    jedis.hset(\"hash\", \"foo\", \"bar\");\n    jedis.zadd(\"zset\", 1, \"foo\");\n    jedis.sadd(\"set\", \"foo\");\n    jedis.setrange(\"setrange\", 0, \"0123456789\");\n    byte[] bytesForSetRange = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };\n    jedis.setrange(\"setrangebytes\".getBytes(), 0, bytesForSetRange);\n\n    Pipeline p = jedis.pipelined();\n    Response<String> string = p.get(\"string\");\n    Response<String> list = p.lpop(\"list\");\n    Response<String> hash = p.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = p.zrange(\"zset\", 0, -1);\n    Response<String> set = p.spop(\"set\");\n    Response<Boolean> blist = p.exists(\"list\");\n    Response<Double> zincrby = p.zincrby(\"zset\", 1, \"foo\");\n    Response<Long> zcard = p.zcard(\"zset\");\n    p.lpush(\"list\", \"bar\");\n    Response<List<String>> lrange = p.lrange(\"list\", 0, -1);\n    Response<Map<String, String>> hgetAll = p.hgetAll(\"hash\");\n    p.sadd(\"set\", \"foo\");\n    Response<Set<String>> smembers = p.smembers(\"set\");\n    Response<List<Tuple>> zrangeWithScores = p.zrangeWithScores(\"zset\", 0, -1);\n    Response<String> getrange = p.getrange(\"setrange\", 1, 3);\n    Response<byte[]> getrangeBytes = p.getrange(\"setrangebytes\".getBytes(), 6, 8);\n    p.sync();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n    assertEquals(false, blist.get());\n    assertEquals(Double.valueOf(2), zincrby.get());\n    assertEquals(Long.valueOf(1), zcard.get());\n    assertEquals(1, lrange.get().size());\n    assertNotNull(hgetAll.get().get(\"foo\"));\n    assertEquals(1, smembers.get().size());\n    assertEquals(1, zrangeWithScores.get().size());\n    assertEquals(\"123\", getrange.get());\n    byte[] expectedGetRangeBytes = { 6, 7, 8 };\n    assertArrayEquals(expectedGetRangeBytes, getrangeBytes.get());\n  }\n\n  @Test\n  public void intermediateSyncs() {\n    jedis.set(\"string\", \"foo\");\n    jedis.lpush(\"list\", \"foo\");\n    jedis.hset(\"hash\", \"foo\", \"bar\");\n    jedis.zadd(\"zset\", 1, \"foo\");\n    jedis.sadd(\"set\", \"foo\");\n    jedis.setrange(\"setrange\", 0, \"0123456789\");\n    byte[] bytesForSetRange = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };\n    jedis.setrange(\"setrangebytes\".getBytes(), 0, bytesForSetRange);\n\n    Pipeline p = jedis.pipelined();\n    Response<String> string = p.get(\"string\");\n    Response<String> list = p.lpop(\"list\");\n    Response<String> hash = p.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = p.zrange(\"zset\", 0, -1);\n    Response<String> set = p.spop(\"set\");\n    Response<Boolean> blist = p.exists(\"list\");\n    p.sync();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n    assertEquals(false, blist.get());\n\n    Response<Double> zincrby = p.zincrby(\"zset\", 1, \"foo\");\n    Response<Long> zcard = p.zcard(\"zset\");\n    p.lpush(\"list\", \"bar\");\n    Response<List<String>> lrange = p.lrange(\"list\", 0, -1);\n    Response<Map<String, String>> hgetAll = p.hgetAll(\"hash\");\n    p.sadd(\"set\", \"foo\");\n    p.sync();\n\n    assertEquals(Double.valueOf(2), zincrby.get());\n    assertEquals(Long.valueOf(1), zcard.get());\n    assertEquals(1, lrange.get().size());\n    assertNotNull(hgetAll.get().get(\"foo\"));\n\n    Response<Set<String>> smembers = p.smembers(\"set\");\n    Response<List<Tuple>> zrangeWithScores = p.zrangeWithScores(\"zset\", 0, -1);\n    Response<String> getrange = p.getrange(\"setrange\", 1, 3);\n    Response<byte[]> getrangeBytes = p.getrange(\"setrangebytes\".getBytes(), 6, 8);\n    p.sync();\n\n    assertEquals(1, smembers.get().size());\n    assertEquals(1, zrangeWithScores.get().size());\n    assertEquals(\"123\", getrange.get());\n    byte[] expectedGetRangeBytes = { 6, 7, 8 };\n    assertArrayEquals(expectedGetRangeBytes, getrangeBytes.get());\n  }\n\n  @Test\n  public void pipelineResponseWithData() {\n    jedis.zadd(\"zset\", 1, \"foo\");\n\n    Pipeline p = jedis.pipelined();\n    Response<Double> score = p.zscore(\"zset\", \"foo\");\n    p.sync();\n\n    assertNotNull(score.get());\n  }\n\n  @Test\n  public void pipelineBinarySafeHashCommands() {\n    jedis.hset(\"key\".getBytes(), \"f1\".getBytes(), \"v111\".getBytes());\n    jedis.hset(\"key\".getBytes(), \"f22\".getBytes(), \"v2222\".getBytes());\n\n    Pipeline p = jedis.pipelined();\n    Response<Map<byte[], byte[]>> fmap = p.hgetAll(\"key\".getBytes());\n    Response<Set<byte[]>> fkeys = p.hkeys(\"key\".getBytes());\n    Response<List<byte[]>> fordered = p.hmget(\"key\".getBytes(), \"f22\".getBytes(), \"f1\".getBytes());\n    Response<List<byte[]>> fvals = p.hvals(\"key\".getBytes());\n    p.sync();\n\n    assertNotNull(fmap.get());\n    // we have to do these strange contortions because byte[] is not a very\n    // good key\n    // for a java Map. It only works with equality (you need the exact key\n    // object to retrieve\n    // the value) I recommend we switch to using ByteBuffer or something\n    // similar:\n    // http://stackoverflow.com/questions/1058149/using-a-byte-array-as-hashmap-key-java\n    Map<byte[], byte[]> map = fmap.get();\n    Set<byte[]> mapKeys = map.keySet();\n    Iterator<byte[]> iterMap = mapKeys.iterator();\n    byte[] firstMapKey = iterMap.next();\n    byte[] secondMapKey = iterMap.next();\n    assertFalse(iterMap.hasNext());\n    verifyHasBothValues(firstMapKey, secondMapKey, \"f1\".getBytes(), \"f22\".getBytes());\n    byte[] firstMapValue = map.get(firstMapKey);\n    byte[] secondMapValue = map.get(secondMapKey);\n    verifyHasBothValues(firstMapValue, secondMapValue, \"v111\".getBytes(), \"v2222\".getBytes());\n\n    assertNotNull(fkeys.get());\n    Iterator<byte[]> iter = fkeys.get().iterator();\n    byte[] firstKey = iter.next();\n    byte[] secondKey = iter.next();\n    assertFalse(iter.hasNext());\n    verifyHasBothValues(firstKey, secondKey, \"f1\".getBytes(), \"f22\".getBytes());\n\n    assertNotNull(fordered.get());\n    assertArrayEquals(\"v2222\".getBytes(), fordered.get().get(0));\n    assertArrayEquals(\"v111\".getBytes(), fordered.get().get(1));\n\n    assertNotNull(fvals.get());\n    assertEquals(2, fvals.get().size());\n    byte[] firstValue = fvals.get().get(0);\n    byte[] secondValue = fvals.get().get(1);\n    verifyHasBothValues(firstValue, secondValue, \"v111\".getBytes(), \"v2222\".getBytes());\n  }\n\n  private void verifyHasBothValues(byte[] firstKey, byte[] secondKey, byte[] value1, byte[] value2) {\n    assertFalse(Arrays.equals(firstKey, secondKey));\n    assertTrue(Arrays.equals(firstKey, value1) || Arrays.equals(firstKey, value2));\n    assertTrue(Arrays.equals(secondKey, value1) || Arrays.equals(secondKey, value2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pipelineSelect() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.swapDB(0, 1);\n    Pipeline p = jedis.pipelined();\n    p.get(\"foo\");\n    p.select(1);\n    p.get(\"foo\");\n    assertEquals(Arrays.<Object>asList(null, \"OK\", \"bar\"), p.syncAndReturnAll());\n  }\n\n  @Test\n  public void pipelineResponseWithoutData() {\n    jedis.zadd(\"zset\", 1, \"foo\");\n\n    Pipeline p = jedis.pipelined();\n    Response<Double> score = p.zscore(\"zset\", \"bar\");\n    p.sync();\n\n    assertNull(score.get());\n  }\n\n  @Test\n  public void pipelineResponseWithinPipeline() {\n    jedis.set(\"string\", \"foo\");\n\n    Pipeline p = jedis.pipelined();\n    Response<String> string = p.get(\"string\");\n    assertThrows(IllegalStateException.class,string::get);\n    p.sync();\n  }\n\n  @Test\n  public void publishInPipeline() {\n    Pipeline pipelined = jedis.pipelined();\n    Response<Long> p1 = pipelined.publish(\"foo\", \"bar\");\n    Response<Long> p2 = pipelined.publish(\"foo\".getBytes(), \"bar\".getBytes());\n    pipelined.sync();\n    assertEquals(0, p1.get().longValue());\n    assertEquals(0, p2.get().longValue());\n  }\n\n  @Test\n  public void canRetrieveUnsetKey() {\n    Pipeline p = jedis.pipelined();\n    Response<String> shouldNotExist = p.get(UUID.randomUUID().toString());\n    p.sync();\n    assertNull(shouldNotExist.get());\n  }\n\n  @Test\n  public void piplineWithError() {\n    Pipeline p = jedis.pipelined();\n    p.set(\"foo\", \"bar\");\n    Response<Set<String>> error = p.smembers(\"foo\");\n    Response<String> r = p.get(\"foo\");\n    p.sync();\n    try {\n      error.get();\n      fail();\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(r.get(), \"bar\");\n  }\n\n  @Test\n  public void testJedisThrowExceptionWhenInPipeline() {\n    Pipeline pipeline = jedis.pipelined();\n    pipeline.set(\"foo\", \"3\");\n    assertThrows(IllegalStateException.class, () -> jedis.get(\"somekey\"));\n  }\n\n  @Test\n  public void testReuseJedisWhenPipelineIsEmpty() {\n    Pipeline pipeline = jedis.pipelined();\n    pipeline.set(\"foo\", \"3\");\n    pipeline.sync();\n    String result = jedis.get(\"foo\");\n    assertEquals(result, \"3\");\n  }\n\n  @Test\n  public void testResetStateWhenInPipeline() {\n    Pipeline pipeline = jedis.pipelined();\n    pipeline.set(\"foo\", \"3\");\n    jedis.resetState();\n    String result = jedis.get(\"foo\");\n    assertEquals(result, \"3\");\n  }\n\n  @Test\n  public void waitReplicas() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone4-replica-of-standalone1\");\n\n    Pipeline p = jedis.pipelined();\n    p.set(\"wait\", \"replicas\");\n    p.waitReplicas(1, 10);\n    p.sync();\n\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      j.auth(endpoint.getPassword());\n      assertEquals(\"replicas\", j.get(\"wait\"));\n    }\n  }\n\n  @Test\n  public void waitAof() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone4-replica-of-standalone1\");\n\n    Pipeline p = jedis.pipelined();\n    p.set(\"wait\", \"aof\");\n    p.waitAOF(1L, 0L, 0L);\n    p.sync();\n\n    try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n      j.auth(endpoint.getPassword());\n      Awaitility.await().atMost(5, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n          .untilAsserted(() -> assertEquals(\"aof\", j.get(\"wait\")));\n    }\n  }\n\n  @Test\n  public void setGet() {\n    Pipeline p = jedis.pipelined();\n    Response<String> _ok = p.set(\"hello\", \"world\");\n    Response<String> _world = p.setGet(\"hello\", \"jedis\", SetParams.setParams());\n    Response<String> _jedis = p.get(\"hello\");\n    Response<String> _null = p.setGet(\"key\", \"value\", SetParams.setParams());\n    p.sync();\n\n    assertEquals(\"OK\", _ok.get());\n    assertEquals(\"world\", _world.get());\n    assertEquals(\"jedis\", _jedis.get());\n    assertNull(_null.get());\n  }\n\n  @Test\n  public void setGetBinary() {\n    Pipeline p = jedis.pipelined();\n    Response<String> _ok = p.set(\"hello\".getBytes(), \"world\".getBytes());\n    Response<byte[]> _world = p.setGet(\"hello\".getBytes(), \"jedis\".getBytes(), SetParams.setParams());\n    Response<byte[]> _jedis = p.get(\"hello\".getBytes());\n    Response<byte[]> _null = p.setGet(\"key\".getBytes(), \"value\".getBytes(), SetParams.setParams());\n    p.sync();\n\n    assertEquals(\"OK\", _ok.get());\n    assertArrayEquals(\"world\".getBytes(), _world.get());\n    assertArrayEquals(\"jedis\".getBytes(), _jedis.get());\n    assertNull(_null.get());\n  }\n\n  @Test\n  public void testEval() {\n    String script = \"return 'success!'\";\n\n    Pipeline p = jedis.pipelined();\n    Response<Object> result = p.eval(script);\n    p.sync();\n\n    assertEquals(\"success!\", result.get());\n  }\n\n  @Test\n  public void testEvalWithBinary() {\n    String script = \"return 'success!'\";\n\n    Pipeline p = jedis.pipelined();\n    Response<Object> result = p.eval(SafeEncoder.encode(script));\n    p.sync();\n\n    assertArrayEquals(SafeEncoder.encode(\"success!\"), (byte[]) result.get());\n  }\n\n  @Test\n  public void testEvalKeyAndArg() {\n    String key = \"test\";\n    String arg = \"3\";\n    String script = \"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\";\n\n    Pipeline p = jedis.pipelined();\n    p.set(key, \"0\");\n    Response<Object> result0 = p.eval(script, Arrays.asList(key), Arrays.asList(arg));\n    p.incr(key);\n    Response<Object> result1 = p.eval(script, Arrays.asList(key), Arrays.asList(arg));\n    Response<String> result2 = p.get(key);\n    p.sync();\n\n    assertNull(result0.get());\n    assertNull(result1.get());\n    assertEquals(\"13\", result2.get());\n  }\n\n  @Test\n  public void testEvalKeyAndArgWithBinary() {\n    // binary\n    byte[] bKey = SafeEncoder.encode(\"test\");\n    byte[] bArg = SafeEncoder.encode(\"3\");\n    byte[] bScript = SafeEncoder\n        .encode(\"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\");\n\n    Pipeline bP = jedis.pipelined();\n    bP.set(bKey, SafeEncoder.encode(\"0\"));\n    Response<Object> bResult0 = bP.eval(bScript, Arrays.asList(bKey), Arrays.asList(bArg));\n    bP.incr(bKey);\n    Response<Object> bResult1 = bP.eval(bScript, Arrays.asList(bKey), Arrays.asList(bArg));\n    Response<byte[]> bResult2 = bP.get(bKey);\n    bP.sync();\n\n    assertNull(bResult0.get());\n    assertNull(bResult1.get());\n    assertArrayEquals(SafeEncoder.encode(\"13\"), bResult2.get());\n  }\n\n  @Test\n  public void testEvalNestedLists() {\n    String script = \"return { {KEYS[1]} , {2} }\";\n\n    Pipeline p = jedis.pipelined();\n    Response<Object> result = p.eval(script, 1, \"key1\");\n    p.sync();\n\n    List<?> results = (List<?>) result.get();\n    assertThat((List<String>) results.get(0), Matchers.hasItem(\"key1\"));\n    assertThat((List<Long>) results.get(1), Matchers.hasItem(2L));\n  }\n\n  @Test\n  public void testEvalNestedListsWithBinary() {\n    byte[] bScript = SafeEncoder.encode(\"return { {KEYS[1]} , {2} }\");\n    byte[] bKey = SafeEncoder.encode(\"key1\");\n\n    Pipeline p = jedis.pipelined();\n    Response<Object> result = p.eval(bScript, 1, bKey);\n    p.sync();\n\n    List<?> results = (List<?>) result.get();\n    assertThat((List<byte[]>) results.get(0), Matchers.hasItem(bKey));\n    assertThat((List<Long>) results.get(1), Matchers.hasItem(2L));\n  }\n\n  @Test\n  public void testEvalsha() {\n    String script = \"return 'success!'\";\n    String sha1 = jedis.scriptLoad(script);\n\n    assertTrue(jedis.scriptExists(sha1));\n\n    Pipeline p = jedis.pipelined();\n    Response<Object> result = p.evalsha(sha1);\n    p.sync();\n\n    assertEquals(\"success!\", result.get());\n  }\n\n  @Test\n  public void testEvalshaKeyAndArg() {\n    String key = \"test\";\n    String arg = \"3\";\n    String script = \"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\";\n    String sha1 = jedis.scriptLoad(script);\n\n    assertTrue(jedis.scriptExists(sha1));\n\n    Pipeline p = jedis.pipelined();\n    p.set(key, \"0\");\n    Response<Object> result0 = p.evalsha(sha1, Arrays.asList(key), Arrays.asList(arg));\n    p.incr(key);\n    Response<Object> result1 = p.evalsha(sha1, Arrays.asList(key), Arrays.asList(arg));\n    Response<String> result2 = p.get(key);\n    p.sync();\n\n    assertNull(result0.get());\n    assertNull(result1.get());\n    assertEquals(\"13\", result2.get());\n  }\n\n  @Test\n  public void testEvalshaKeyAndArgWithBinary() {\n    byte[] bKey = SafeEncoder.encode(\"test\");\n    byte[] bArg = SafeEncoder.encode(\"3\");\n    String script = \"redis.call('INCRBY', KEYS[1], ARGV[1]) redis.call('INCRBY', KEYS[1], ARGV[1])\";\n    byte[] bScript = SafeEncoder.encode(script);\n    byte[] bSha1 = jedis.scriptLoad(bScript);\n\n    assertTrue(jedis.scriptExists(bSha1));\n\n    Pipeline p = jedis.pipelined();\n    p.set(bKey, SafeEncoder.encode(\"0\"));\n    Response<Object> result0 = p.evalsha(bSha1, Arrays.asList(bKey), Arrays.asList(bArg));\n    p.incr(bKey);\n    Response<Object> result1 = p.evalsha(bSha1, Arrays.asList(bKey), Arrays.asList(bArg));\n    Response<byte[]> result2 = p.get(bKey);\n    p.sync();\n\n    assertNull(result0.get());\n    assertNull(result1.get());\n    assertArrayEquals(SafeEncoder.encode(\"13\"), result2.get());\n  }\n\n  @Test\n  public void testSyncWithNoCommandQueued() {\n    // we need to test with fresh instance of Jedis\n    Jedis jedis2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500);\n\n    Pipeline pipeline = jedis2.pipelined();\n    pipeline.sync();\n\n    jedis2.close();\n\n    jedis2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500);\n\n    pipeline = jedis2.pipelined();\n    List<Object> resp = pipeline.syncAndReturnAll();\n    assertTrue(resp.isEmpty());\n\n    jedis2.close();\n  }\n\n  @Test\n  public void testCloseable() throws IOException {\n    // we need to test with fresh instance of Jedis\n    Jedis jedis2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500);\n    jedis2.auth(endpoint.getPassword());\n\n    Pipeline pipeline = jedis2.pipelined();\n    Response<String> retFuture1 = pipeline.set(\"a\", \"1\");\n    Response<String> retFuture2 = pipeline.set(\"b\", \"2\");\n\n    pipeline.close();\n\n    // it shouldn't meet any exception\n    retFuture1.get();\n    retFuture2.get();\n    jedis2.close();\n  }\n\n  @Test\n  public void time() {\n    Pipeline p = jedis.pipelined();\n\n    p.time();\n\n    // we get back one result, with two components: the seconds, and the microseconds, but encoded as strings\n    Matcher timeResponseMatcher = hasItems(matchesPattern(\"\\\\d+\"), matchesPattern(\"\\\\d+\"));\n    assertThat(p.syncAndReturnAll(),\n        hasItems(timeResponseMatcher));\n  }\n\n  @Test\n  public void dbSize() {\n    Pipeline p = jedis.pipelined();\n\n    p.dbSize();\n    p.set(\"foo\", \"bar\");\n    p.dbSize();\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(0L, \"OK\", 1L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void move() {\n    Pipeline p = jedis.pipelined();\n\n    p.move(\"foo\", 1);\n    p.set(\"foo\", \"bar\");\n    p.move(\"foo\", 1);\n    p.get(\"foo\");\n    p.select(1);\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(0L, \"OK\", 1L, null, \"OK\", \"bar\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void moveBinary() {\n    Pipeline p = jedis.pipelined();\n\n    p.move(bfoo, 1);\n    p.set(bfoo, bbar);\n    p.move(bfoo, 1);\n    p.get(bfoo);\n    p.select(1);\n    p.get(bfoo);\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(0L, \"OK\", 1L, null, \"OK\", bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void swapDb() {\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.get(\"foo\");\n    p.select(1);\n    p.get(\"foo\");\n    p.swapDB(0, 1);\n    p.select(0);\n    p.get(\"foo\");\n    p.select(1);\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(\"OK\", \"bar\", \"OK\", null, \"OK\", \"OK\", null, \"OK\", \"bar\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void copyToAnotherDb() {\n    Pipeline p = jedis.pipelined();\n\n    p.copy(\"foo\", \"foo-copy\", 1, false);\n    p.set(\"foo\", \"bar\");\n    p.copy(\"foo\", \"foo-copy\", 1, false);\n    p.get(\"foo\");\n    p.select(1);\n    p.get(\"foo-copy\");\n    p.select(0);\n    p.set(\"foo\", \"baz\");\n    p.copy(\"foo\", \"foo-copy\", 1, false);\n    p.get(\"foo\");\n    p.select(1);\n    p.get(\"foo-copy\");\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(false, \"OK\", true, \"bar\", \"OK\", \"bar\", \"OK\", \"OK\", false, \"baz\", \"bar\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void copyToAnotherDbBinary() {\n    Pipeline p = jedis.pipelined();\n\n\n    p.copy(bfoo, bfoo1, 1, false);\n    p.set(bfoo, bbar);\n    p.copy(bfoo, bfoo1, 1, false);\n    p.get(bfoo);\n    p.select(1);\n    p.get(bfoo1);\n    p.select(0);\n    p.set(bfoo, bbaz);\n    p.copy(bfoo, bfoo1, 1, false);\n    p.get(bfoo);\n    p.select(1);\n    p.get(bfoo1);\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(false, \"OK\", true, bbar, \"OK\", bbar, \"OK\", \"OK\", false, bbaz, bbar));\n  }\n\n  enum Foo implements ProtocolCommand {\n    FOO;\n\n    @Override\n    public byte[] getRaw() {\n      return SafeEncoder.encode(name());\n    }\n  }\n\n  @Test\n  public void errorInTheMiddle() {\n    CommandObject<String> invalidCommand =\n        new CommandObject<>(new CommandObjects().commandArguments(Foo.FOO), BuilderFactory.STRING);\n\n    Pipeline p = jedis.pipelined();\n\n    p.set(\"foo\", \"bar\");\n    p.appendCommand(invalidCommand);\n    p.get(\"foo\");\n\n    assertThat(p.syncAndReturnAll(),\n        hasItems(\n            equalTo(\"OK\"),\n            instanceOf(JedisDataException.class),\n            equalTo(\"bar\")\n        ));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ProtocolTest.java",
    "content": "package redis.clients.jedis;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.util.FragmentedByteArrayInputStream;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\nimport redis.clients.jedis.exceptions.JedisBusyException;\nimport redis.clients.jedis.util.RedisInputStream;\nimport redis.clients.jedis.util.RedisOutputStream;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class ProtocolTest {\n  @Test\n  public void buildACommand() throws IOException {\n    PipedInputStream pis = new PipedInputStream();\n    BufferedInputStream bis = new BufferedInputStream(pis);\n    PipedOutputStream pos = new PipedOutputStream(pis);\n    RedisOutputStream ros = new RedisOutputStream(pos);\n\n//    Protocol.sendCommand(ros, Protocol.Command.GET, \"SOMEKEY\".getBytes(Protocol.CHARSET));\n    Protocol.sendCommand(ros, new CommandArguments(Protocol.Command.GET).add(\"SOMEKEY\"));\n    ros.flush();\n    pos.close();\n    String expectedCommand = \"*2\\r\\n$3\\r\\nGET\\r\\n$7\\r\\nSOMEKEY\\r\\n\";\n\n    int b;\n    StringBuilder sb = new StringBuilder();\n    while ((b = bis.read()) != -1) {\n      sb.append((char) b);\n    }\n\n    assertEquals(expectedCommand, sb.toString());\n  }\n\n  @Test\n  public void writeOverflow() throws IOException {\n    RedisOutputStream ros = new RedisOutputStream(new OutputStream() {\n\n      @Override\n      public void write(int b) throws IOException {\n        throw new IOException(\"thrown exception\");\n\n      }\n    });\n\n    ros.write(new byte[8191]);\n\n    try {\n      ros.write((byte) '*');\n    } catch (IOException ioe) {\n      //ignore\n    }\n    assertThrows(IOException.class, ()-> ros.write((byte) '*'));\n  }\n\n  @Test\n  public void bulkReply() {\n    InputStream is = new ByteArrayInputStream(\"$6\\r\\nfoobar\\r\\n\".getBytes());\n    byte[] response = (byte[]) Protocol.read(new RedisInputStream(is));\n    assertArrayEquals(SafeEncoder.encode(\"foobar\"), response);\n  }\n\n  @Test\n  public void fragmentedBulkReply() {\n    FragmentedByteArrayInputStream fis = new FragmentedByteArrayInputStream(\n        \"$30\\r\\n012345678901234567890123456789\\r\\n\".getBytes());\n    byte[] response = (byte[]) Protocol.read(new RedisInputStream(fis));\n    assertArrayEquals(SafeEncoder.encode(\"012345678901234567890123456789\"), response);\n  }\n\n  @Test\n  public void nullBulkReply() {\n    InputStream is = new ByteArrayInputStream(\"$-1\\r\\n\".getBytes());\n    String response = (String) Protocol.read(new RedisInputStream(is));\n    assertNull(response);\n  }\n\n  @Test\n  public void singleLineReply() {\n    InputStream is = new ByteArrayInputStream(\"+OK\\r\\n\".getBytes());\n    byte[] response = (byte[]) Protocol.read(new RedisInputStream(is));\n    assertArrayEquals(SafeEncoder.encode(\"OK\"), response);\n  }\n\n  @Test\n  public void integerReply() {\n    InputStream is = new ByteArrayInputStream(\":123\\r\\n\".getBytes());\n    long response = (Long) Protocol.read(new RedisInputStream(is));\n    assertEquals(123, response);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  public void multiBulkReply() {\n    InputStream is = new ByteArrayInputStream(\n        \"*4\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n$5\\r\\nHello\\r\\n$5\\r\\nWorld\\r\\n\".getBytes());\n    List<byte[]> response = (List<byte[]>) Protocol.read(new RedisInputStream(is));\n    List<byte[]> expected = new ArrayList<byte[]>();\n    expected.add(SafeEncoder.encode(\"foo\"));\n    expected.add(SafeEncoder.encode(\"bar\"));\n    expected.add(SafeEncoder.encode(\"Hello\"));\n    expected.add(SafeEncoder.encode(\"World\"));\n    assertByteArrayListEquals(expected, response);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  public void nullMultiBulkReply() {\n    InputStream is = new ByteArrayInputStream(\"*-1\\r\\n\".getBytes());\n    List<String> response = (List<String>) Protocol.read(new RedisInputStream(is));\n    assertNull(response);\n  }\n\n  @Test\n  public void busyReply() {\n    final String busyMessage = \"BUSY Redis is busy running a script.\";\n    final InputStream is = new ByteArrayInputStream(('-' + busyMessage + \"\\r\\n\").getBytes());\n    try {\n      Protocol.read(new RedisInputStream(is));\n    } catch (final JedisBusyException e) {\n      assertEquals(busyMessage, e.getMessage());\n      return;\n    }\n    fail(\"Expected a JedisBusyException to be thrown.\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/RedisClientTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anything;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.net.URI;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\npublic class RedisClientTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  private static EndpointConfig endpointStandalone7;\n  private static EndpointConfig endpointStandalone1; // password protected\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    endpointStandalone7 = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n    endpointStandalone1 = Endpoints.getRedisEndpoint(\"standalone1\");\n  }\n\n  @Test\n  public void checkCloseableConnections() {\n    RedisClient pool = RedisClient.builder()\n        .hostAndPort(endpointStandalone7.getHost(), endpointStandalone7.getPort())\n        .clientConfig(endpointStandalone7.getClientConfigBuilder().timeoutMillis(2000).build()).build();\n    pool.set(\"foo\", \"bar\");\n    assertEquals(\"bar\", pool.get(\"foo\"));\n    pool.close();\n    assertTrue(pool.getPool().isClosed());\n  }\n\n  @Test\n  public void checkResourceWithConfig() {\n    try (RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone7.getHostAndPort())\n        .clientConfig(endpointStandalone7.getClientConfigBuilder().socketTimeoutMillis(5000).build())\n        .build()) {\n\n      try (Connection jedis = pool.getPool().getResource()) {\n        assertTrue(jedis.ping());\n        assertEquals(5000, jedis.getSoTimeout());\n      }\n    }\n  }\n\n  @Test\n  public void checkPoolOverflow() {\n    GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n    try (\n        RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone7.getHostAndPort())\n            .poolConfig(config).build();\n        Connection jedis = pool.getPool().getResource()) {\n      assertThrows(JedisException.class, () -> pool.getPool().getResource());\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  @SuppressWarnings(\"deprecation\")\n  public void startWithUrlString() {\n    try (Jedis j = new Jedis(endpointStandalone1.getHostAndPort())) {\n      j.auth(endpointStandalone1.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (RedisClient pool = RedisClient.builder()\n        .fromURI(endpointStandalone1.getURIBuilder()\n            .credentials(\"\", endpointStandalone1.getPassword()).path(\"/2\").build().toString())\n        .build()) {\n      assertEquals(\"bar\", pool.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  @SuppressWarnings(\"deprecation\")\n  public void startWithUrl() {\n    try (Jedis j = new Jedis(endpointStandalone1.getHostAndPort())) {\n      j.auth(endpointStandalone1.getPassword());\n      j.select(2);\n      j.set(\"foo\", \"bar\");\n    }\n\n    try (RedisClient pool = RedisClient.builder().fromURI(endpointStandalone1.getURIBuilder()\n        .credentials(\"\", endpointStandalone1.getPassword()).path(\"/2\").build()).build()) {\n      assertEquals(\"bar\", pool.get(\"foo\"));\n    }\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  public void shouldThrowExceptionForInvalidURI() {\n    assertThrows(Exception.class,\n      () -> RedisClient.builder().fromURI(new URI(\"localhost:6380\")).build());\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  public void allowUrlWithNoDBAndNoPassword() {\n    RedisClient.builder().fromURI(endpointStandalone1.getURI().toString()).build().close();\n    RedisClient.builder().fromURI(endpointStandalone1.getURI()).build().close();\n  }\n\n  @Test\n  public void customClientName() {\n    try (\n        RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone7.getHostAndPort())\n            .clientConfig(\n                endpointStandalone7.getClientConfigBuilder().clientName(\"my_shiny_client_name\").build())\n            .build();\n        Connection jedis = pool.getPool().getResource()) {\n      assertEquals(\"my_shiny_client_name\", new Jedis(jedis).clientGetname());\n    }\n  }\n\n  @Test\n  public void invalidClientName() {\n    try (\n        RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone7.getHostAndPort())\n            .clientConfig(\n              DefaultJedisClientConfig.builder().clientName(\"invalid client name\").build())\n            .build();\n        Connection jedis = pool.getPool().getResource()) {\n    } catch (Exception e) {\n      if (!e.getMessage().startsWith(\"client info cannot contain space\")) {\n        fail(\"invalid client name test fail\");\n      }\n    }\n  }\n\n  @Test\n  public void getNumActiveWhenPoolIsClosed() {\n    RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone7.getHostAndPort())\n        .clientConfig(endpointStandalone7.getClientConfigBuilder().timeoutMillis(2000).build())\n        .build();\n\n    try (Connection j = pool.getPool().getResource()) {\n      j.ping();\n    }\n\n    pool.close();\n    assertEquals(0, pool.getPool().getNumActive());\n  }\n\n  @Test\n  public void getNumActiveReturnsTheCorrectNumber() {\n    try (RedisClient pool = RedisClient.builder()\n        .hostAndPort(endpointStandalone7.getHost(), endpointStandalone7.getPort())\n        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(2000).build())\n        .poolConfig(new ConnectionPoolConfig()).build()) {\n\n      Connection jedis = pool.getPool().getResource();\n      assertEquals(1, pool.getPool().getNumActive());\n\n      Connection jedis2 = pool.getPool().getResource();\n      assertEquals(2, pool.getPool().getNumActive());\n\n      jedis.close();\n      assertEquals(1, pool.getPool().getNumActive());\n\n      jedis2.close();\n      assertEquals(0, pool.getPool().getNumActive());\n    }\n  }\n\n  @Test\n  public void closeResourceTwice() {\n    try (RedisClient pool = RedisClient.builder()\n        .hostAndPort(endpointStandalone7.getHost(), endpointStandalone7.getPort())\n        .clientConfig(endpointStandalone7.getClientConfigBuilder().timeoutMillis(2000).build())\n        .poolConfig(new ConnectionPoolConfig()).build()) {\n      Connection j = pool.getPool().getResource();\n      j.ping();\n      j.close();\n      j.close();\n    }\n  }\n\n  @Test\n  public void closeBrokenResourceTwice() {\n    try (RedisClient pool = RedisClient.builder()\n        .hostAndPort(endpointStandalone7.getHost(), endpointStandalone7.getPort())\n        .clientConfig(endpointStandalone7.getClientConfigBuilder().timeoutMillis(2000).build())\n        .poolConfig(new ConnectionPoolConfig()).build()) {\n      Connection j = pool.getPool().getResource();\n      try {\n        // make connection broken\n        j.getOne();\n        fail();\n      } catch (Exception e) {\n        assertInstanceOf(JedisConnectionException.class, e);\n      }\n      assertTrue(j.isBroken());\n      j.close();\n      j.close();\n    }\n  }\n\n  @Test\n  public void testResetValidCredentials() {\n    DefaultRedisCredentialsProvider credentialsProvider =\n        new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, \"bad password\"));\n\n    try (RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone1.getHostAndPort())\n        .clientConfig(\n          DefaultJedisClientConfig.builder().credentialsProvider(credentialsProvider).build())\n        .build()) {\n      try {\n        pool.get(\"foo\");\n        fail(\"Should not get resource from pool\");\n      } catch (JedisException e) { }\n      assertEquals(0, pool.getPool().getNumActive());\n\n      credentialsProvider.setCredentials(new DefaultRedisCredentials(null, endpointStandalone1.getPassword()));\n      assertThat(pool.get(\"foo\"), anything());\n    }\n  }\n\n  @Test\n  public void testCredentialsProvider() {\n    final AtomicInteger prepareCount = new AtomicInteger();\n    final AtomicInteger cleanupCount = new AtomicInteger();\n    final AtomicBoolean validPassword = new AtomicBoolean(false);\n\n    RedisCredentialsProvider credentialsProvider = new RedisCredentialsProvider() {\n\n      @Override\n      public void prepare() {\n        prepareCount.incrementAndGet();\n      }\n\n      @Override\n      public RedisCredentials get() {\n        if (!validPassword.get()) {\n          return new RedisCredentials() {\n            @Override\n            public char[] getPassword() {\n              return \"invalidPass\".toCharArray();\n            }\n          };\n        }\n\n        return new RedisCredentials() {\n          @Override\n          public String getUser() {\n            return null;\n          }\n\n          @Override\n          public char[] getPassword() {\n            return endpointStandalone1.getPassword().toCharArray();\n          }\n        };\n      }\n\n      @Override\n      public void cleanUp() {\n        cleanupCount.incrementAndGet();\n      }\n    };\n\n    // TODO: do it without the help of pool config; from Connection constructor? (configurable) force ping?\n    GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();\n    poolConfig.setMaxTotal(1);\n    poolConfig.setTestOnBorrow(true);\n    try (RedisClient pool = RedisClient.builder().hostAndPort(endpointStandalone1.getHostAndPort())\n        .clientConfig(\n          DefaultJedisClientConfig.builder().credentialsProvider(credentialsProvider).build())\n        .poolConfig(poolConfig).build()) {\n      try {\n        pool.get(\"foo\");\n        fail(\"Should not get resource from pool\");\n      } catch (JedisException e) {\n        //ignore\n      }\n      assertEquals(0, pool.getPool().getNumActive() + pool.getPool().getNumIdle() + pool.getPool().getNumWaiters());\n      assertThat(prepareCount.getAndSet(0), greaterThanOrEqualTo(1));\n      assertThat(cleanupCount.getAndSet(0), greaterThanOrEqualTo(1));\n\n      validPassword.set(true);\n      assertThat(pool.get(\"foo\"), anything());\n      assertThat(prepareCount.get(), equalTo(1));\n      assertThat(cleanupCount.get(), equalTo(1));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/ReliableTransactionTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.Protocol.Command.INCR;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.SET;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class ReliableTransactionTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n\n  final byte[] bmykey = { 0x42, 0x02, 0x03, 0x04 };\n\n  private static EndpointConfig endpoint;\n\n  private Connection conn;\n  private Jedis nj;\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    conn = new Connection(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(500).build());\n\n    nj = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(500).build());\n    nj.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    nj.close();\n    conn.close();\n  }\n\n  @Test\n  public void multi() {\n    ReliableTransaction trans = new ReliableTransaction(conn);\n\n    trans.sadd(\"foo\", \"a\");\n    trans.sadd(\"foo\", \"b\");\n    trans.scard(\"foo\");\n\n    List<Object> response = trans.exec();\n\n    List<Object> expected = new ArrayList<Object>();\n    expected.add(1L);\n    expected.add(1L);\n    expected.add(2L);\n    assertEquals(expected, response);\n\n    // Binary\n    trans = new ReliableTransaction(conn);\n\n    trans.sadd(bfoo, ba);\n    trans.sadd(bfoo, bb);\n    trans.scard(bfoo);\n\n    response = trans.exec();\n\n    expected = new ArrayList<Object>();\n    expected.add(1L);\n    expected.add(1L);\n    expected.add(2L);\n    assertEquals(expected, response);\n\n  }\n\n  @Test\n  public void watch() {\n    ReliableTransaction t = new ReliableTransaction(conn, false);\n    assertEquals(\"OK\", t.watch(\"mykey\", \"somekey\"));\n    t.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", \"foo\");\n    List<Object> resp = t.exec();\n    assertNull(resp);\n    assertEquals(\"bar\", nj.get(\"mykey\"));\n\n    // Binary\n    assertEquals(\"OK\", t.watch(bmykey, \"foobar\".getBytes()));\n    t.multi();\n\n    nj.set(bmykey, bbar);\n\n    t.set(bmykey, bfoo);\n    resp = t.exec();\n    assertNull(resp);\n    assertArrayEquals(bbar, nj.get(bmykey));\n  }\n\n  @Test\n  public void unwatch() {\n    ReliableTransaction t = new ReliableTransaction(conn, false);\n    assertEquals(\"OK\", t.watch(\"mykey\"));\n    String val = \"foo\";\n    assertEquals(\"OK\", t.unwatch());\n    t.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", val);\n    List<Object> resp = t.exec();\n    assertEquals(1, resp.size());\n    assertEquals(\"OK\", resp.get(0));\n\n    // Binary\n    t.watch(bmykey);\n    byte[] bval = bfoo;\n    assertEquals(\"OK\", t.unwatch());\n    t.multi();\n\n    nj.set(bmykey, bbar);\n\n    t.set(bmykey, bval);\n    resp = t.exec();\n    assertEquals(1, resp.size());\n    assertEquals(\"OK\", resp.get(0));\n  }\n\n  @Test\n  public void discard() {\n    ReliableTransaction t = new ReliableTransaction(conn);\n    String status = t.discard();\n    assertEquals(\"OK\", status);\n  }\n\n  @Test\n  public void transactionResponse() {\n    nj.set(\"string\", \"foo\");\n    nj.lpush(\"list\", \"foo\");\n    nj.hset(\"hash\", \"foo\", \"bar\");\n    nj.zadd(\"zset\", 1, \"foo\");\n    nj.sadd(\"set\", \"foo\");\n\n    ReliableTransaction t = new ReliableTransaction(conn);\n    Response<String> string = t.get(\"string\");\n    Response<String> list = t.lpop(\"list\");\n    Response<String> hash = t.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = t.zrange(\"zset\", 0, -1);\n    Response<String> set = t.spop(\"set\");\n    t.exec();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n  }\n\n  @Test\n  public void transactionResponseBinary() {\n    nj.set(\"string\", \"foo\");\n    nj.lpush(\"list\", \"foo\");\n    nj.hset(\"hash\", \"foo\", \"bar\");\n    nj.zadd(\"zset\", 1, \"foo\");\n    nj.sadd(\"set\", \"foo\");\n\n    ReliableTransaction t = new ReliableTransaction(conn);\n    Response<byte[]> string = t.get(\"string\".getBytes());\n    Response<byte[]> list = t.lpop(\"list\".getBytes());\n    Response<byte[]> hash = t.hget(\"hash\".getBytes(), \"foo\".getBytes());\n    Response<List<byte[]>> zset = t.zrange(\"zset\".getBytes(), 0, -1);\n    Response<byte[]> set = t.spop(\"set\".getBytes());\n    t.exec();\n\n    assertArrayEquals(\"foo\".getBytes(), string.get());\n    assertArrayEquals(\"foo\".getBytes(), list.get());\n    assertArrayEquals(\"bar\".getBytes(), hash.get());\n    assertArrayEquals(\"foo\".getBytes(), zset.get().iterator().next());\n    assertArrayEquals(\"foo\".getBytes(), set.get());\n  }\n\n  @Test\n  public void transactionResponseWithinPipeline() {\n    nj.set(\"string\", \"foo\");\n\n    ReliableTransaction t = new ReliableTransaction(conn);\n    Response<String> string = t.get(\"string\");\n    assertThrows(IllegalStateException.class, string::get);\n    t.exec();\n  }\n\n  @Test\n  public void transactionResponseWithError() {\n    ReliableTransaction t = new ReliableTransaction(conn);\n    t.set(\"foo\", \"bar\");\n    Response<Set<String>> error = t.smembers(\"foo\");\n    Response<String> r = t.get(\"foo\");\n    List<Object> l = t.exec();\n    assertSame(JedisDataException.class, l.get(1).getClass());\n    try {\n      error.get();\n      fail(\"We expect exception here!\");\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(\"bar\", r.get());\n  }\n\n  @Test\n  public void testCloseable() {\n    ReliableTransaction transaction = new ReliableTransaction(conn);\n    transaction.set(\"a\", \"1\");\n    transaction.set(\"b\", \"2\");\n\n    transaction.close();\n\n    try {\n      transaction.exec();\n      fail(\"close should discard transaction\");\n    } catch (IllegalStateException e) {\n      assertTrue(e.getMessage().contains(\"EXEC without MULTI\"));\n      // pass\n    }\n  }\n\n  @Test\n  public void testTransactionWithGeneralCommand() {\n    ReliableTransaction t = new ReliableTransaction(conn);\n    t.set(\"string\", \"foo\");\n    t.lpush(\"list\", \"foo\");\n    t.hset(\"hash\", \"foo\", \"bar\");\n    t.zadd(\"zset\", 1, \"foo\");\n    t.sendCommand(SET, \"x\", \"1\");\n    t.sadd(\"set\", \"foo\");\n    t.sendCommand(INCR, \"x\");\n    Response<String> string = t.get(\"string\");\n    Response<String> list = t.lpop(\"list\");\n    Response<String> hash = t.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = t.zrange(\"zset\", 0, -1);\n    Response<String> set = t.spop(\"set\");\n    Response<Object> x = t.sendCommand(GET, \"x\");\n    t.exec();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n    assertEquals(\"2\", SafeEncoder.encode((byte[]) x.get()));\n  }\n\n  @Test\n  public void transactionResponseWithErrorWithGeneralCommand() {\n    ReliableTransaction t = new ReliableTransaction(conn);\n    t.set(\"foo\", \"bar\");\n    t.sendCommand(SET, \"x\", \"1\");\n    Response<Set<String>> error = t.smembers(\"foo\");\n    Response<String> r = t.get(\"foo\");\n    Response<Object> x = t.sendCommand(GET, \"x\");\n    t.sendCommand(INCR, \"x\");\n    List<Object> l = t.exec();\n    assertSame(JedisDataException.class, l.get(2).getClass());\n    try {\n      error.get();\n      fail(\"We expect exception here!\");\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(\"bar\", r.get());\n    assertEquals(\"1\", SafeEncoder.encode((byte[]) x.get()));\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/SentineledConnectionProviderTest.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.providers.SentineledConnectionProvider;\nimport redis.clients.jedis.util.ReflectionTestUtil;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasKey;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n/**\n * @see JedisSentinelPoolTest\n */\n@Tag(\"integration\")\npublic class SentineledConnectionProviderTest {\n\n  private static final String MASTER_NAME = \"mymaster\";\n\n  protected static HostAndPort sentinel1;\n  protected static HostAndPort sentinel2;\n\n  private static EndpointConfig primary;\n\n  protected Set<HostAndPort> sentinels = new HashSet<>();\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    sentinel1 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort();\n    sentinel2 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-3\").getHostAndPort();\n    primary = Endpoints.getRedisEndpoint(\"standalone2-primary\");\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    sentinels.clear();\n\n    sentinels.add(sentinel1);\n    sentinels.add(sentinel2);\n  }\n\n  @Test\n  public void repeatedSentinelPoolInitialization() {\n    for (int i = 0; i < 20; ++i) {\n\n      try (SentineledConnectionProvider provider = new SentineledConnectionProvider(MASTER_NAME,\n          DefaultJedisClientConfig.builder().timeoutMillis(1000).password(\"foobared\").database(2).build(),\n          sentinels, DefaultJedisClientConfig.builder().build())) {\n\n        provider.getConnection().close();\n      }\n    }\n  }\n\n  /**\n   * Ensure that getConnectionMap() does not cause connection leak. (#4323)\n   */\n  @Test\n  @Timeout( value = 1)\n  public void getConnectionMapDoesNotCauseConnectionLeak() {\n\n    ConnectionPoolConfig config = new ConnectionPoolConfig();\n    config.setMaxTotal(1);\n\n    try (SentineledConnectionProvider sut = new SentineledConnectionProvider(MASTER_NAME,\n            primary.getClientConfigBuilder().build(), config, sentinels,\n            DefaultJedisClientConfig.builder().build())) {\n\n      HostAndPort resolvedPrimary = sut.getCurrentMaster();\n      ConnectionPool pool = ReflectionTestUtil.getField(sut,\"pool\");\n      assertThat(pool.getNumActive(), equalTo(0));\n\n      Map<?, ?> cm = sut.getConnectionMap();\n\n      // exactly one entry for current primary\n      // and no active connections\n      assertThat(cm.size(), equalTo(1));\n      assertThat(cm, hasKey(resolvedPrimary));\n      assertThat(pool.getNumActive(), equalTo(0));\n      // primary did not change\n      assertThat(ReflectionTestUtil.getField(sut,\"pool\"), sameInstance(pool));\n    }\n  }\n\n  /**\n   * Ensure that getPrimaryNodesConnectionMap() does not cause connection leak. (#4323)\n   */\n  @Test\n  @Timeout( value = 1)\n  public void getPrimaryNodesConnectionMapDoesNotCauseConnectionLeak() {\n\n    ConnectionPoolConfig config = new ConnectionPoolConfig();\n    config.setMaxTotal(1);\n\n    try (SentineledConnectionProvider sut = new SentineledConnectionProvider(MASTER_NAME,\n            primary.getClientConfigBuilder().build(), config, sentinels,\n            DefaultJedisClientConfig.builder().build())) {\n\n      HostAndPort resolvedPrimary = sut.getCurrentMaster();\n      ConnectionPool pool = ReflectionTestUtil.getField(sut,\"pool\");\n      assertThat(pool.getNumActive(), equalTo(0));\n\n\n      Map<?, ?> cm = sut.getPrimaryNodesConnectionMap();\n\n      // exactly one entry for current primary\n      // and no active connections\n      assertThat(cm.size(), equalTo(1));\n      assertThat(cm, hasKey(resolvedPrimary));\n      assertThat(pool.getNumActive(), equalTo(0));\n      // primary did not change\n      assertThat(ReflectionTestUtil.getField(sut,\"pool\"), sameInstance(pool));\n    }\n\n  }\n\n  @Test\n  public void initializeWithNotAvailableSentinelsShouldThrowException() {\n    Set<HostAndPort> wrongSentinels = new HashSet<>();\n    wrongSentinels.add(new HostAndPort(\"localhost\", 65432));\n    wrongSentinels.add(new HostAndPort(\"localhost\", 65431));\n    assertThrows(JedisConnectionException.class, () -> {\n      try (SentineledConnectionProvider provider = new SentineledConnectionProvider(MASTER_NAME,\n          DefaultJedisClientConfig.builder().build(), wrongSentinels, DefaultJedisClientConfig.builder().build())) {\n      }\n    });\n  }\n\n  @Test\n  public void initializeWithNotMonitoredMasterNameShouldThrowException() {\n    final String wrongMasterName = \"wrongMasterName\";\n    assertThrows(JedisException.class, () -> {\n      try (SentineledConnectionProvider provider = new SentineledConnectionProvider(wrongMasterName,\n          DefaultJedisClientConfig.builder().build(), sentinels, DefaultJedisClientConfig.builder().build())) {\n      }\n    });\n  }\n\n  @Test\n  public void checkCloseableConnections() throws Exception {\n    GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();\n\n    try (RedisSentinelClient jedis = RedisSentinelClient\n        .builder().masterName(MASTER_NAME).clientConfig(DefaultJedisClientConfig.builder()\n            .timeoutMillis(1000).password(\"foobared\").database(2).build())\n        .poolConfig(config).sentinels(sentinels).build()) {\n      assertSame(SentineledConnectionProvider.class, jedis.provider.getClass());\n      jedis.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n    }\n  }\n\n  @Test\n  public void checkResourceIsCloseable() {\n    GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    config.setBlockWhenExhausted(false);\n\n    try (RedisSentinelClient jedis = RedisSentinelClient\n        .builder().masterName(MASTER_NAME).clientConfig(DefaultJedisClientConfig.builder()\n            .timeoutMillis(1000).password(\"foobared\").database(2).build())\n        .poolConfig(config).sentinels(sentinels).build()) {\n\n      Connection conn = jedis.provider.getConnection();\n      try {\n        conn.ping();\n      } finally {\n        conn.close();\n      }\n\n      Connection conn2 = jedis.provider.getConnection();\n      try {\n        assertEquals(conn, conn2);\n      } finally {\n        conn2.close();\n      }\n    }\n  }\n\n  @Test\n  public void testResetInvalidPassword() {\n    DefaultRedisCredentialsProvider credentialsProvider\n        = new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, \"foobared\"));\n\n    try (RedisSentinelClient jedis = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(2000)\n            .credentialsProvider(credentialsProvider).database(2).clientName(\"my_shiny_client_name\")\n            .build())\n        .sentinels(sentinels).build()) {\n\n      jedis.set(\"foo\", \"bar\");\n\n      Connection conn1_ref;\n      try (Connection conn1_1 = jedis.provider.getConnection()) {\n        conn1_ref = conn1_1;\n        assertEquals(\"bar\", new Jedis(conn1_1).get(\"foo\"));\n      }\n\n      credentialsProvider.setCredentials(new DefaultRedisCredentials(null, \"wrong password\"));\n\n      try (Connection conn1_2 = jedis.provider.getConnection()) {\n        assertSame(conn1_ref, conn1_2);\n\n        try (Connection conn2 = jedis.provider.getConnection()) {\n          fail(\"Should not get resource from pool\");\n        } catch (JedisException e) { }\n      }\n    }\n  }\n\n  @Test\n  public void testResetValidPassword() {\n    DefaultRedisCredentialsProvider credentialsProvider\n        = new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, \"wrong password\"));\n\n    try (RedisSentinelClient jedis = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(2000)\n            .credentialsProvider(credentialsProvider).database(2).clientName(\"my_shiny_client_name\")\n            .build())\n        .sentinels(sentinels).build()) {\n\n      try (Connection conn1 = jedis.provider.getConnection()) {\n        fail(\"Should not get resource from pool\");\n      } catch (JedisException e) { }\n\n      credentialsProvider.setCredentials(new DefaultRedisCredentials(null, \"foobared\"));\n\n      try (Connection conn2 = jedis.provider.getConnection()) {\n        new Jedis(conn2).set(\"foo\", \"bar\");\n        assertEquals(\"bar\", jedis.get(\"foo\"));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/StaticCommandFlagsRegistryTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.EnumSet;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.CommandFlagsRegistry.CommandFlag;\nimport redis.clients.jedis.CommandFlagsRegistry.RequestPolicy;\nimport redis.clients.jedis.CommandFlagsRegistry.ResponsePolicy;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.SafeEncoder;\n\n/**\n * Unit tests for StaticCommandFlagsRegistry. Tests the retrieval of command flags for various Redis\n * commands, including commands with subcommands.\n */\npublic class StaticCommandFlagsRegistryTest {\n\n  private StaticCommandFlagsRegistry registry;\n\n  @BeforeEach\n  public void setUp() {\n    registry = StaticCommandFlagsRegistry.registry();\n  }\n\n  /**\n   * Test that FUNCTION LOAD command returns the correct flags. FUNCTION LOAD should have: DENYOOM,\n   * NOSCRIPT, WRITE flags.\n   */\n  @Test\n  public void testFunctionLoadCommandFlags() {\n    // Create a CommandArguments for \"FUNCTION LOAD\"\n    CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add(\"LOAD\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(functionLoadArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"FUNCTION LOAD should have flags\");\n    assertEquals(3, flags.size(), \"FUNCTION LOAD should have exactly 3 flags\");\n    assertTrue(flags.contains(CommandFlag.DENYOOM), \"FUNCTION LOAD should have DENYOOM flag\");\n    assertTrue(flags.contains(CommandFlag.NOSCRIPT), \"FUNCTION LOAD should have NOSCRIPT flag\");\n    assertTrue(flags.contains(CommandFlag.WRITE), \"FUNCTION LOAD should have WRITE flag\");\n  }\n\n  /**\n   * Test that FUNCTION DELETE command returns the correct flags. FUNCTION DELETE should have:\n   * NOSCRIPT, WRITE flags.\n   */\n  @Test\n  public void testFunctionDeleteCommandFlags() {\n    CommandArguments functionDeleteArgs = new CommandArguments(Protocol.Command.FUNCTION)\n        .add(\"DELETE\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(functionDeleteArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"FUNCTION DELETE should have flags\");\n    assertEquals(2, flags.size(), \"FUNCTION DELETE should have exactly 2 flags\");\n    assertTrue(flags.contains(CommandFlag.NOSCRIPT), \"FUNCTION DELETE should have NOSCRIPT flag\");\n    assertTrue(flags.contains(CommandFlag.WRITE), \"FUNCTION DELETE should have WRITE flag\");\n  }\n\n  /**\n   * Test other subcommand examples: ACL SETUSER\n   */\n  @Test\n  public void testAclSetUserCommandFlags() {\n    CommandArguments aclSetUserArgs = new CommandArguments(Protocol.Command.ACL).add(\"SETUSER\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(aclSetUserArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"ACL SETUSER should have flags\");\n    assertTrue(flags.contains(CommandFlag.ADMIN), \"ACL SETUSER should have ADMIN flag\");\n    assertTrue(flags.contains(CommandFlag.NOSCRIPT), \"ACL SETUSER should have NOSCRIPT flag\");\n  }\n\n  /**\n   * Test other subcommand examples: CONFIG GET\n   */\n  @Test\n  public void testConfigGetCommandFlags() {\n    CommandArguments configGetArgs = new CommandArguments(Protocol.Command.CONFIG).add(\"GET\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(configGetArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"CONFIG GET should have flags\");\n    assertTrue(flags.contains(CommandFlag.ADMIN), \"CONFIG GET should have ADMIN flag\");\n    assertTrue(flags.contains(CommandFlag.LOADING), \"CONFIG GET should have LOADING flag\");\n    assertTrue(flags.contains(CommandFlag.STALE), \"CONFIG GET should have STALE flag\");\n  }\n\n  /**\n   * Test simple command without subcommands: GET\n   */\n  @Test\n  public void testGetCommandFlags() {\n    CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add(\"key\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(getArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"GET should have flags\");\n    assertTrue(flags.contains(CommandFlag.READONLY), \"GET should have READONLY flag\");\n    assertTrue(flags.contains(CommandFlag.FAST), \"GET should have FAST flag\");\n  }\n\n  /**\n   * Test simple command without subcommands: SET\n   */\n  @Test\n  public void testSetCommandFlags() {\n    CommandArguments setArgs = new CommandArguments(Protocol.Command.SET).add(\"key\").add(\"value\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(setArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"SET should have flags\");\n    assertTrue(flags.contains(CommandFlag.WRITE), \"SET should have WRITE flag\");\n    assertTrue(flags.contains(CommandFlag.DENYOOM), \"SET should have DENYOOM flag\");\n  }\n\n  /**\n   * Test that unknown commands return empty flags\n   */\n  @Test\n  public void testUnknownCommandReturnsEmptyFlags() {\n    ProtocolCommand unknownCommand = () -> SafeEncoder.encode(\"UNKNOWN_COMMAND_XYZ\");\n    CommandArguments unknownArgs = new CommandArguments(unknownCommand);\n\n    EnumSet<CommandFlag> flags = registry.getFlags(unknownArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertTrue(flags.isEmpty(), \"Unknown command should return empty flags\");\n  }\n\n  /**\n   * Test case insensitivity - command names should be normalized to uppercase\n   */\n  @Test\n  public void testCaseInsensitivity() {\n    ProtocolCommand functionCommand = () -> SafeEncoder.encode(\"function\");\n    CommandArguments functionLoadArgs = new CommandArguments(functionCommand).add(\"load\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(functionLoadArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertFalse(flags.isEmpty(), \"function load (lowercase) should have flags\");\n    assertEquals(3, flags.size(), \"function load should have exactly 3 flags\");\n    assertTrue(flags.contains(CommandFlag.DENYOOM), \"function load should have DENYOOM flag\");\n    assertTrue(flags.contains(CommandFlag.NOSCRIPT), \"function load should have NOSCRIPT flag\");\n    assertTrue(flags.contains(CommandFlag.WRITE), \"function load should have WRITE flag\");\n  }\n\n  /**\n   * Test that unknown subcommands of parent commands fall back to parent command flags. If the\n   * parent command also doesn't exist, it should return empty flags.\n   */\n  @Test\n  public void testUnknownSubcommandFallback() {\n    // Create a CommandArguments for \"FUNCTION UNKNOWN_SUBCOMMAND\"\n    // This subcommand doesn't exist, so it should fall back to \"FUNCTION\" parent flags\n    // Since \"FUNCTION\" parent has empty flags, it should return empty flags\n    CommandArguments unknownSubcommandArgs = new CommandArguments(Protocol.Command.FUNCTION)\n        .add(\"UNKNOWN_SUBCOMMAND\");\n\n    EnumSet<CommandFlag> flags = registry.getFlags(unknownSubcommandArgs);\n\n    assertNotNull(flags, \"Flags should not be null\");\n    assertTrue(flags.isEmpty(),\n      \"Unknown FUNCTION subcommand should return empty flags (parent flags)\");\n  }\n\n  // ==================== Request Policy Tests ====================\n\n  /**\n   * Test that getRequestPolicy returns DEFAULT for commands without a specific policy. Since the\n   * current registry doesn't populate request policies, all commands should return DEFAULT.\n   */\n  @Test\n  public void testGetRequestPolicyReturnsDefault() {\n    CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add(\"key\");\n\n    RequestPolicy policy = registry.getRequestPolicy(getArgs);\n\n    assertNotNull(policy, \"Request policy should not be null\");\n    assertEquals(RequestPolicy.DEFAULT, policy, \"GET should have DEFAULT request policy\");\n  }\n\n  /**\n   * Test that getRequestPolicy returns DEFAULT for unknown commands.\n   */\n  @Test\n  public void testGetRequestPolicyForUnknownCommand() {\n    ProtocolCommand unknownCommand = () -> SafeEncoder.encode(\"UNKNOWN_COMMAND_XYZ\");\n    CommandArguments unknownArgs = new CommandArguments(unknownCommand);\n\n    RequestPolicy policy = registry.getRequestPolicy(unknownArgs);\n\n    assertNotNull(policy, \"Request policy should not be null\");\n    assertEquals(RequestPolicy.DEFAULT, policy,\n      \"Unknown command should have DEFAULT request policy\");\n  }\n\n  /**\n   * Test that getRequestPolicy works for commands with subcommands.\n   */\n  @Test\n  public void testGetRequestPolicyForSubcommand() {\n    CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add(\"LOAD\");\n\n    RequestPolicy policy = registry.getRequestPolicy(functionLoadArgs);\n\n    assertNotNull(policy, \"Request policy should not be null\");\n    // Currently all commands return DEFAULT since policies aren't populated\n    assertEquals(RequestPolicy.ALL_SHARDS, policy,\n      \"FUNCTION LOAD should have DEFAULT request policy\");\n  }\n\n  // ==================== Response Policy Tests ====================\n\n  /**\n   * Test that getResponsePolicy returns DEFAULT for commands without a specific policy. Since the\n   * current registry doesn't populate response policies, all commands should return DEFAULT.\n   */\n  @Test\n  public void testGetResponsePolicyReturnsDefault() {\n    CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add(\"key\");\n\n    ResponsePolicy policy = registry.getResponsePolicy(getArgs);\n\n    assertNotNull(policy, \"Response policy should not be null\");\n    assertEquals(ResponsePolicy.DEFAULT, policy, \"GET should have DEFAULT response policy\");\n  }\n\n  /**\n   * Test that getResponsePolicy returns DEFAULT for unknown commands.\n   */\n  @Test\n  public void testGetResponsePolicyForUnknownCommand() {\n    ProtocolCommand unknownCommand = () -> SafeEncoder.encode(\"UNKNOWN_COMMAND_XYZ\");\n    CommandArguments unknownArgs = new CommandArguments(unknownCommand);\n\n    ResponsePolicy policy = registry.getResponsePolicy(unknownArgs);\n\n    assertNotNull(policy, \"Response policy should not be null\");\n    assertEquals(ResponsePolicy.DEFAULT, policy,\n      \"Unknown command should have DEFAULT response policy\");\n  }\n\n  /**\n   * Test that getResponsePolicy works for commands with subcommands.\n   */\n  @Test\n  public void testGetResponsePolicyForSubcommand() {\n    CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add(\"LOAD\");\n\n    ResponsePolicy policy = registry.getResponsePolicy(functionLoadArgs);\n\n    assertNotNull(policy, \"Response policy should not be null\");\n    // Currently all commands return DEFAULT since policies aren't populated\n    assertEquals(ResponsePolicy.ALL_SUCCEEDED, policy,\n      \"FUNCTION LOAD should have DEFAULT response policy\");\n  }\n\n  /**\n   * Test that RequestPolicy enum contains all expected values.\n   */\n  @Test\n  public void testRequestPolicyEnumValues() {\n    RequestPolicy[] values = RequestPolicy.values();\n    assertEquals(5, values.length, \"RequestPolicy should have 5 values\");\n\n    // Verify all expected values exist\n    assertNotNull(RequestPolicy.valueOf(\"DEFAULT\"));\n    assertNotNull(RequestPolicy.valueOf(\"ALL_NODES\"));\n    assertNotNull(RequestPolicy.valueOf(\"ALL_SHARDS\"));\n    assertNotNull(RequestPolicy.valueOf(\"MULTI_SHARD\"));\n    assertNotNull(RequestPolicy.valueOf(\"SPECIAL\"));\n  }\n\n  /**\n   * Test that ResponsePolicy enum contains all expected values.\n   */\n  @Test\n  public void testResponsePolicyEnumValues() {\n    ResponsePolicy[] values = ResponsePolicy.values();\n    assertEquals(9, values.length, \"ResponsePolicy should have 9 values\");\n\n    // Verify all expected values exist\n    assertNotNull(ResponsePolicy.valueOf(\"DEFAULT\"));\n    assertNotNull(ResponsePolicy.valueOf(\"ONE_SUCCEEDED\"));\n    assertNotNull(ResponsePolicy.valueOf(\"ALL_SUCCEEDED\"));\n    assertNotNull(ResponsePolicy.valueOf(\"AGG_LOGICAL_AND\"));\n    assertNotNull(ResponsePolicy.valueOf(\"AGG_LOGICAL_OR\"));\n    assertNotNull(ResponsePolicy.valueOf(\"AGG_MIN\"));\n    assertNotNull(ResponsePolicy.valueOf(\"AGG_MAX\"));\n    assertNotNull(ResponsePolicy.valueOf(\"AGG_SUM\"));\n    assertNotNull(ResponsePolicy.valueOf(\"SPECIAL\"));\n  }\n\n  /**\n   * Test that correct flags are stored for commands with subcommands.\n   */\n  @Test\n  public void testFlagsForCommandWithSubcommands() {\n    // verify flags for COMMAND (top level)\n    CommandArguments commandArgs = new CommandArguments(Protocol.Command.COMMAND);\n    EnumSet<CommandFlag> commandFlags = registry.getFlags(commandArgs);\n    EnumSet<CommandFlag> expectedCommandFlags = EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE);\n    assertEquals(expectedCommandFlags, commandFlags, \"COMMAND should have expected flags\");\n\n    // verify flags for COMMAND INFO (subcommand)\n    CommandArguments commandInfoArgs = new CommandArguments(Protocol.Command.COMMAND).add(\"INFO\");\n    EnumSet<CommandFlag> commandInfoflags = registry.getFlags(commandInfoArgs);\n    EnumSet<CommandFlag> expectedCommandInfoFlags = EnumSet.of(CommandFlag.LOADING,\n      CommandFlag.STALE);\n    assertEquals(expectedCommandInfoFlags, commandInfoflags,\n      \"COMMAND INFO should have expected flags\");\n  }\n\n  /**\n   * Test that HOTKEYS subcommands (START, STOP, GET, RESET) return the correct flags. All HOTKEYS\n   * subcommands should have ADMIN and NOSCRIPT flags.\n   */\n  @Test\n  public void testHotkeysSubcommandFlags() {\n    // Expected flags for all HOTKEYS subcommands: ADMIN, NOSCRIPT\n    EnumSet<CommandFlag> expectedFlags = EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT);\n\n    // Test HOTKEYS START\n    CommandArguments hotkeysStartArgs = new CommandArguments(Protocol.Command.HOTKEYS).add(\"START\");\n    EnumSet<CommandFlag> startFlags = registry.getFlags(hotkeysStartArgs);\n    assertNotNull(startFlags, \"HOTKEYS START flags should not be null\");\n    assertFalse(startFlags.isEmpty(), \"HOTKEYS START should have flags\");\n    assertEquals(expectedFlags, startFlags, \"HOTKEYS START should have ADMIN and NOSCRIPT flags\");\n\n    // Test HOTKEYS STOP\n    CommandArguments hotkeysStopArgs = new CommandArguments(Protocol.Command.HOTKEYS).add(\"STOP\");\n    EnumSet<CommandFlag> stopFlags = registry.getFlags(hotkeysStopArgs);\n    assertNotNull(stopFlags, \"HOTKEYS STOP flags should not be null\");\n    assertFalse(stopFlags.isEmpty(), \"HOTKEYS STOP should have flags\");\n    assertEquals(expectedFlags, stopFlags, \"HOTKEYS STOP should have ADMIN and NOSCRIPT flags\");\n\n    // Test HOTKEYS GET\n    CommandArguments hotkeysGetArgs = new CommandArguments(Protocol.Command.HOTKEYS).add(\"GET\");\n    EnumSet<CommandFlag> getFlags = registry.getFlags(hotkeysGetArgs);\n    assertNotNull(getFlags, \"HOTKEYS GET flags should not be null\");\n    assertFalse(getFlags.isEmpty(), \"HOTKEYS GET should have flags\");\n    assertEquals(expectedFlags, getFlags, \"HOTKEYS GET should have ADMIN and NOSCRIPT flags\");\n\n    // Test HOTKEYS RESET\n    CommandArguments hotkeysResetArgs = new CommandArguments(Protocol.Command.HOTKEYS).add(\"RESET\");\n    EnumSet<CommandFlag> resetFlags = registry.getFlags(hotkeysResetArgs);\n    assertNotNull(resetFlags, \"HOTKEYS RESET flags should not be null\");\n    assertFalse(resetFlags.isEmpty(), \"HOTKEYS RESET should have flags\");\n    assertEquals(expectedFlags, resetFlags, \"HOTKEYS RESET should have ADMIN and NOSCRIPT flags\");\n\n    // Verify request policy for HOTKEYS GET (has SPECIAL request and response policy)\n    RequestPolicy getRequestPolicy = registry.getRequestPolicy(hotkeysGetArgs);\n    assertEquals(RequestPolicy.SPECIAL, getRequestPolicy,\n      \"HOTKEYS GET should have SPECIAL request policy\");\n\n    ResponsePolicy getResponsePolicy = registry.getResponsePolicy(hotkeysGetArgs);\n    assertEquals(ResponsePolicy.SPECIAL, getResponsePolicy,\n      \"HOTKEYS GET should have SPECIAL response policy\");\n\n    // Verify request policy for HOTKEYS START (has SPECIAL request policy)\n    RequestPolicy startRequestPolicy = registry.getRequestPolicy(hotkeysStartArgs);\n    assertEquals(RequestPolicy.SPECIAL, startRequestPolicy,\n      \"HOTKEYS START should have SPECIAL request policy\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/TransactionV2Test.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.Protocol.Command.INCR;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.SET;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\npublic class TransactionV2Test {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n\n  final byte[] bmykey = { 0x42, 0x02, 0x03, 0x04 };\n\n  private static EndpointConfig endpoint;\n\n  @BeforeAll\n  public static void prepare() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n\n  private Connection conn;\n  private Jedis nj;\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    conn = new Connection(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(500).build());\n\n    nj = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(500).build());\n\n    nj.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    nj.close();\n    conn.close();\n  }\n\n  @Test\n  public void multi() {\n    Transaction trans = new Transaction(conn);\n\n    trans.sadd(\"foo\", \"a\");\n    trans.sadd(\"foo\", \"b\");\n    trans.scard(\"foo\");\n\n    List<Object> response = trans.exec();\n\n    List<Object> expected = new ArrayList<Object>();\n    expected.add(1L);\n    expected.add(1L);\n    expected.add(2L);\n    assertEquals(expected, response);\n\n    // Binary\n    trans = new Transaction(conn);\n\n    trans.sadd(bfoo, ba);\n    trans.sadd(bfoo, bb);\n    trans.scard(bfoo);\n\n    response = trans.exec();\n\n    expected = new ArrayList<Object>();\n    expected.add(1L);\n    expected.add(1L);\n    expected.add(2L);\n    assertEquals(expected, response);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void watch() {\n    Transaction t = new Transaction(conn, false);\n    assertEquals(\"OK\", t.watch(\"mykey\", \"somekey\"));\n    t.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", \"foo\");\n    List<Object> resp = t.exec();\n    assertNull(resp);\n    assertEquals(\"bar\", nj.get(\"mykey\"));\n\n    // Binary\n    assertEquals(\"OK\", t.watch(bmykey, \"foobar\".getBytes()));\n    t.multi();\n\n    nj.set(bmykey, bbar);\n\n    t.set(bmykey, bfoo);\n    resp = t.exec();\n    assertNull(resp);\n    assertArrayEquals(bbar, nj.get(bmykey));\n  }\n\n  @Test\n  public void unwatch() {\n    Transaction t = new Transaction(conn, false);\n    assertEquals(\"OK\", t.watch(\"mykey\"));\n    String val = \"foo\";\n    assertEquals(\"OK\", t.unwatch());\n    t.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", val);\n    List<Object> resp = t.exec();\n    assertEquals(1, resp.size());\n    assertEquals(\"OK\", resp.get(0));\n\n    // Binary\n    t.watch(bmykey);\n    byte[] bval = bfoo;\n    assertEquals(\"OK\", t.unwatch());\n    t.multi();\n\n    nj.set(bmykey, bbar);\n\n    t.set(bmykey, bval);\n    resp = t.exec();\n    assertEquals(1, resp.size());\n    assertEquals(\"OK\", resp.get(0));\n  }\n\n  @Test\n  public void discard() {\n    Transaction t = new Transaction(conn);\n    String status = t.discard();\n    assertEquals(\"OK\", status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transactionResponse() {\n    nj.set(\"string\", \"foo\");\n    nj.lpush(\"list\", \"foo\");\n    nj.hset(\"hash\", \"foo\", \"bar\");\n    nj.zadd(\"zset\", 1, \"foo\");\n    nj.sadd(\"set\", \"foo\");\n\n    Transaction t = new Transaction(conn);\n    Response<String> string = t.get(\"string\");\n    Response<String> list = t.lpop(\"list\");\n    Response<String> hash = t.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = t.zrange(\"zset\", 0, -1);\n    Response<String> set = t.spop(\"set\");\n    t.exec();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transactionResponseBinary() {\n    nj.set(\"string\", \"foo\");\n    nj.lpush(\"list\", \"foo\");\n    nj.hset(\"hash\", \"foo\", \"bar\");\n    nj.zadd(\"zset\", 1, \"foo\");\n    nj.sadd(\"set\", \"foo\");\n\n    Transaction t = new Transaction(conn);\n    Response<byte[]> string = t.get(\"string\".getBytes());\n    Response<byte[]> list = t.lpop(\"list\".getBytes());\n    Response<byte[]> hash = t.hget(\"hash\".getBytes(), \"foo\".getBytes());\n    Response<List<byte[]>> zset = t.zrange(\"zset\".getBytes(), 0, -1);\n    Response<byte[]> set = t.spop(\"set\".getBytes());\n    t.exec();\n\n    assertArrayEquals(\"foo\".getBytes(), string.get());\n    assertArrayEquals(\"foo\".getBytes(), list.get());\n    assertArrayEquals(\"bar\".getBytes(), hash.get());\n    assertArrayEquals(\"foo\".getBytes(), zset.get().iterator().next());\n    assertArrayEquals(\"foo\".getBytes(), set.get());\n  }\n\n  @Test\n  public void transactionResponseWithinPipeline() {\n    nj.set(\"string\", \"foo\");\n\n    Transaction t = new Transaction(conn);\n    Response<String> string = t.get(\"string\");\n    assertThrows(IllegalStateException.class, string::get);\n    t.exec();\n  }\n\n  @Test\n  public void transactionResponseWithError() {\n    Transaction t = new Transaction(conn);\n    t.set(\"foo\", \"bar\");\n    Response<Set<String>> error = t.smembers(\"foo\");\n    Response<String> r = t.get(\"foo\");\n    List<Object> l = t.exec();\n    assertSame(JedisDataException.class, l.get(1).getClass());\n    try {\n      error.get();\n      fail(\"We expect exception here!\");\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(\"bar\", r.get());\n  }\n\n  @Test\n  public void transactionPropagatesErrorsBeforeExec() {\n    // A command may fail to be queued, so there may be an error before EXEC is called.\n    // For instance the command may be syntactically wrong (wrong number of arguments, wrong command name, ...)\n    CommandObject<String> invalidCommand =\n        new CommandObject<>(new CommandObjects().commandArguments(SET), BuilderFactory.STRING);\n\n    Transaction t = new Transaction(conn);\n    t.appendCommand(invalidCommand);\n    t.set(\"foo\",\"bar\");\n    JedisDataException exception = assertThrows(JedisDataException.class, t::exec);\n    Throwable[] suppressed = exception.getSuppressed();\n    assertNotNull(suppressed, \"Suppressed exceptions should not be null\");\n    assertTrue(suppressed.length > 0, \"There should be at least one suppressed exception\");\n    MatcherAssert.assertThat(suppressed[0].getMessage(), containsString(\"ERR wrong number of arguments\"));\n  }\n\n  @Test\n  public void testCloseable() {\n    Transaction transaction = new Transaction(conn);\n    transaction.set(\"a\", \"1\");\n    transaction.set(\"b\", \"2\");\n\n    transaction.close();\n\n    try {\n      transaction.exec();\n      fail(\"close should discard transaction\");\n    } catch (IllegalStateException e) {\n      assertTrue(e.getMessage().contains(\"EXEC without MULTI\"));\n      // pass\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testTransactionWithGeneralCommand() {\n    Transaction t = new Transaction(conn);\n    t.set(\"string\", \"foo\");\n    t.lpush(\"list\", \"foo\");\n    t.hset(\"hash\", \"foo\", \"bar\");\n    t.zadd(\"zset\", 1, \"foo\");\n    t.sendCommand(SET, \"x\", \"1\");\n    t.sadd(\"set\", \"foo\");\n    t.sendCommand(INCR, \"x\");\n    Response<String> string = t.get(\"string\");\n    Response<String> list = t.lpop(\"list\");\n    Response<String> hash = t.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = t.zrange(\"zset\", 0, -1);\n    Response<String> set = t.spop(\"set\");\n    Response<Object> x = t.sendCommand(GET, \"x\");\n    t.exec();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n    assertEquals(\"2\", SafeEncoder.encode((byte[]) x.get()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transactionResponseWithErrorWithGeneralCommand() {\n    Transaction t = new Transaction(conn);\n    t.set(\"foo\", \"bar\");\n    t.sendCommand(SET, \"x\", \"1\");\n    Response<Set<String>> error = t.smembers(\"foo\");\n    Response<String> r = t.get(\"foo\");\n    Response<Object> x = t.sendCommand(GET, \"x\");\n    t.sendCommand(INCR, \"x\");\n    List<Object> l = t.exec();\n    assertSame(JedisDataException.class, l.get(2).getClass());\n    try {\n      error.get();\n      fail(\"We expect exception here!\");\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(\"bar\", r.get());\n    assertEquals(\"1\", SafeEncoder.encode((byte[]) x.get()));\n  }\n\n  @Test\n  public void zrevrangebyscore() {\n    nj.zadd(\"foo\", 1.0d, \"a\");\n    nj.zadd(\"foo\", 2.0d, \"b\");\n    nj.zadd(\"foo\", 3.0d, \"c\");\n    nj.zadd(\"foo\", 4.0d, \"d\");\n    nj.zadd(\"foo\", 5.0d, \"e\");\n\n    Transaction t = new Transaction(conn);\n    Response<List<String>> range = t.zrevrangeByScore(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    t.exec();\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n\n    assertEquals(expected, range.get());\n\n    t = new Transaction(conn);\n    range = t.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    t.exec();\n    expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"b\");\n\n    assertEquals(expected, range.get());\n\n    t = new Transaction(conn);\n    range = t.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    t.exec();\n    expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, range.get());\n\n    t = new Transaction(conn);\n    range = t.zrevrangeByScore(\"foo\", 4d, 2d);\n    t.exec();\n    expected = new ArrayList<String>();\n    expected.add(\"d\");\n    expected.add(\"c\");\n    expected.add(\"b\");\n\n    assertEquals(expected, range.get());\n\n    t = new Transaction(conn);\n    range = t.zrevrangeByScore(\"foo\", \"+inf\", \"(4\");\n    t.exec();\n    expected = new ArrayList<String>();\n    expected.add(\"e\");\n\n    assertEquals(expected, range.get());\n\n    // Binary\n    nj.zadd(bfoo, 1d, ba);\n    nj.zadd(bfoo, 10d, bb);\n    nj.zadd(bfoo, 0.1d, bc);\n    nj.zadd(bfoo, 2d, ba);\n\n    t = new Transaction(conn);\n    Response<List<byte[]>> brange = t.zrevrangeByScore(bfoo, 2d, 0d);\n    t.exec();\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange.get());\n\n    t = new Transaction(conn);\n    brange = t.zrevrangeByScore(bfoo, 2d, 0d, 0, 1);\n    t.exec();\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange.get());\n\n    t = new Transaction(conn);\n    Response<List<byte[]>> brange2 = t.zrevrangeByScore(bfoo, SafeEncoder.encode(\"+inf\"),\n        SafeEncoder.encode(\"(2\"));\n    t.exec();\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, brange2.get());\n\n    t = new Transaction(conn);\n    brange = t.zrevrangeByScore(bfoo, 2d, 0d, 1, 1);\n    t.exec();\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange.get());\n  }\n\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/TupleSortedSetTest.java",
    "content": "package redis.clients.jedis;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.commands.jedis.JedisCommandsTestBase;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class TupleSortedSetTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bd = { 0x0D };\n  final byte[] be = { 0x0E };\n  final byte[] bf = { 0x0F };\n\n  public TupleSortedSetTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testBinary() {\n    List<Tuple> array = new ArrayList<Tuple>();\n\n    jedis.zadd(bfoo, 0d, ba);\n    array.add(new Tuple(ba, 0d));\n\n    jedis.zadd(bfoo, 1d, bb);\n    array.add(new Tuple(bb, 1d));\n\n    List<Tuple> zrange = jedis.zrangeWithScores(bfoo, 0, -1);\n    assertEquals(zrange, sorted(array));\n\n    jedis.zadd(bfoo, -0.3, bc);\n    array.add(new Tuple(bc, -0.3));\n\n    jedis.zadd(bfoo, 0.3, bf);\n    array.add(new Tuple(bf, 0.3));\n\n    jedis.zadd(bfoo, 0.3, be);\n    array.add(new Tuple(be, 0.3));\n\n    jedis.zadd(bfoo, 0.3, bd);\n    array.add(new Tuple(bd, 0.3));\n\n    zrange = jedis.zrangeWithScores(bfoo, 0, -1);\n    assertEquals(zrange, sorted(array));\n  }\n\n  @Test\n  public void testString() {\n    List<Tuple> array = new ArrayList<Tuple>();\n\n    jedis.zadd(\"foo\", 0d, \"a\");\n    array.add(new Tuple(\"a\", 0d));\n\n    jedis.zadd(\"foo\", 1d, \"b\");\n    array.add(new Tuple(\"b\", 1d));\n\n    List<Tuple> range = jedis.zrangeWithScores(\"foo\", 0, -1);\n    assertEquals(range, sorted(array));\n\n    jedis.zadd(\"foo\", -0.3, \"c\");\n    array.add(new Tuple(\"c\", -0.3));\n\n    jedis.zadd(\"foo\", 0.3, \"f\");\n    array.add(new Tuple(\"f\", 0.3));\n\n    jedis.zadd(\"foo\", 0.3, \"e\");\n    array.add(new Tuple(\"e\", 0.3));\n\n    jedis.zadd(\"foo\", 0.3, \"d\");\n    array.add(new Tuple(\"d\", 0.3));\n\n    range = jedis.zrangeWithScores(\"foo\", 0, -1);\n    assertEquals(range, sorted(array));\n  }\n\n  private List<Tuple> sorted(List<Tuple> list) {\n    List<Tuple> sort = new ArrayList<>(list);\n    Collections.sort(sort);\n    return sort;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/UdsTest.java",
    "content": "package redis.clients.jedis;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.Socket;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.newsclub.net.unix.AFUNIXSocket;\nimport org.newsclub.net.unix.AFUNIXSocketAddress;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = true)\npublic class UdsTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @Test\n  public void jedisConnectsToUds() {\n    try (Jedis jedis = new Jedis(new UdsJedisSocketFactory())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void jedisConnectsToUdsResp3() {\n    try (Jedis jedis = new Jedis(new UdsJedisSocketFactory(),\n        DefaultJedisClientConfig.builder().resp3().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void unifiedJedisConnectsToUds() {\n    try (UnifiedJedis jedis = new UnifiedJedis(new UdsJedisSocketFactory())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void unifiedJedisConnectsToUdsResp3() {\n    try (UnifiedJedis jedis = new UnifiedJedis(new UdsJedisSocketFactory(),\n        DefaultJedisClientConfig.builder().resp3().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  private static class UdsJedisSocketFactory implements JedisSocketFactory {\n\n    private static final File UDS_SOCKET = new File(\"/tmp/redis_uds.sock\");\n\n    @Override\n    public Socket createSocket() throws JedisConnectionException {\n      try {\n        Socket socket = AFUNIXSocket.newStrictInstance();\n        socket.connect(new AFUNIXSocketAddress(UDS_SOCKET), Protocol.DEFAULT_TIMEOUT);\n        return socket;\n      } catch (IOException ioe) {\n        throw new JedisConnectionException(\"Failed to create UDS connection.\", ioe);\n      }\n    }\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/UnavailableConnectionTest.java",
    "content": "package redis.clients.jedis;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = true)\npublic class UnavailableConnectionTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  private static final HostAndPort unavailableNode = new HostAndPort(\"localhost\", 6400);\n\n  @BeforeAll\n  public static void setup() {\n    setupAvoidQuitInDestroyObject();\n\n    try (Jedis j = new Jedis(unavailableNode)) {\n      j.shutdown();\n    }\n  }\n\n  public static void cleanup() {\n    cleanupAvoidQuitInDestroyObject();\n  }\n\n  private static JedisPool poolForBrokenJedis1;\n  private static Thread threadForBrokenJedis1;\n  private static Jedis brokenJedis1;\n\n  public static void setupAvoidQuitInDestroyObject() {\n    GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(1);\n    poolForBrokenJedis1 = new JedisPool(config, unavailableNode.getHost(),\n        unavailableNode.getPort());\n    brokenJedis1 = poolForBrokenJedis1.getResource();\n    threadForBrokenJedis1 = new Thread(new Runnable() {\n      @Override\n      public void run() {\n        brokenJedis1.blpop(0, \"broken-key-1\");\n      }\n    });\n    threadForBrokenJedis1.start();\n  }\n\n  @Test\n  @Timeout(5)\n  public void testAvoidQuitInDestroyObjectForBrokenConnection() throws InterruptedException {\n    threadForBrokenJedis1.join();\n    assertFalse(threadForBrokenJedis1.isAlive());\n    assertTrue(brokenJedis1.isBroken());\n    brokenJedis1.close(); // we need capture/mock to test this properly\n\n    try {\n      poolForBrokenJedis1.getResource();\n      fail(\"Should not get connection from pool\");\n    } catch (Exception ex) {\n      assertSame(JedisConnectionException.class, ex.getClass());\n    }\n  }\n\n  public static void cleanupAvoidQuitInDestroyObject() {\n    poolForBrokenJedis1.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/UnboundRedisClusterClientTest.java",
    "content": "package redis.clients.jedis;\n\n\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.Protocol.CLUSTER_HASHSLOTS;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.args.ClusterResetType;\nimport redis.clients.jedis.exceptions.*;\nimport redis.clients.jedis.util.ClientKillerUtil;\nimport redis.clients.jedis.util.JedisClusterTestUtil;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport redis.clients.jedis.util.Pool;\n\n@Tag(\"integration\")\npublic class UnboundRedisClusterClientTest extends UnboundRedisClusterClientTestBase {\n\n  private static final int DEFAULT_TIMEOUT = 2000; //sec\n  private static final int DEFAULT_REDIRECTIONS = 5;\n  private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig();\n  private static final DefaultJedisClientConfig DEFAULT_CLIENT_CONFIG\n      = endpoint.getClientConfigBuilder().build();\n\n  @Test\n  public void testThrowMovedException() {\n    assertThrows(JedisMovedDataException.class, ()->node1.set(\"foo\", \"bar\"));\n  }\n\n  @Test\n  public void testMovedExceptionParameters() {\n    try {\n      node1.set(\"foo\", \"bar\");\n    } catch (JedisMovedDataException jme) {\n      assertEquals(12182, jme.getSlot());\n      assertEquals(nodeInfo3, jme.getTargetNode());\n      return;\n    }\n    fail();\n  }\n\n  @Test\n  public void testThrowAskException() {\n    int keySlot = JedisClusterCRC16.getSlot(\"test\");\n    String node3Id = JedisClusterTestUtil.getNodeId(node3.clusterNodes());\n    node2.clusterSetSlotMigrating(keySlot, node3Id);\n    assertThrows(JedisAskDataException.class, ()->node2.get(\"test\"));\n  }\n\n  @Test\n  public void testDiscoverNodesAutomatically() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      assertEquals(3, jc.getClusterNodes().size());\n    }\n\n    try (RedisClusterClient jc2 = RedisClusterClient.builder()\n        .nodes(Collections.singleton(nodeInfo1))\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      assertEquals(3, jc2.getClusterNodes().size());\n    }\n  }\n\n  @Test\n  public void testDiscoverNodesAutomaticallyWithSocketConfig() {\n    HostAndPort hp = nodeInfo1;\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DEFAULT_CLIENT_CONFIG)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      assertEquals(3, jc.getClusterNodes().size());\n    }\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DEFAULT_CLIENT_CONFIG)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      assertEquals(3, jc.getClusterNodes().size());\n    }\n  }\n\n  @Test\n  public void testSetClientName() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    String clientName = \"myAppName\";\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .clientName(clientName)\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      for (Pool<Connection> pool : jc.getClusterNodes().values()) {\n        try (Jedis jedis = new Jedis(pool.getResource())) {\n          assertEquals(clientName, jedis.clientGetname());\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testSetClientNameWithConfig() {\n    HostAndPort hp = nodeInfo1;\n    String clientName = \"config-pattern-app\";\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DefaultJedisClientConfig.builder().password(endpoint.getPassword()).clientName(clientName).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      jc.getClusterNodes().values().forEach(pool -> {\n        try (Jedis jedis = new Jedis(pool.getResource())) {\n          assertEquals(clientName, jedis.clientGetname());\n        }\n      });\n    }\n  }\n\n  @Test\n  public void testCalculateConnectionPerSlot() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      jc.set(\"foo\", \"bar\");\n      jc.set(\"test\", \"test\");\n      assertEquals(\"bar\", node3.get(\"foo\"));\n      assertEquals(\"test\", node2.get(\"test\"));\n    }\n\n    try (RedisClusterClient jc2 = RedisClusterClient.builder()\n        .nodes(Collections.singleton(nodeInfo1))\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      jc2.set(\"foo\", \"bar\");\n      jc2.set(\"test\", \"test\");\n      assertEquals(\"bar\", node3.get(\"foo\"));\n      assertEquals(\"test\", node2.get(\"test\"));\n    }\n  }\n\n  @Test\n  public void testReadonlyAndReadwrite() throws Exception {\n    node1.clusterMeet(LOCAL_IP, nodeInfoSlave2.getPort());\n    JedisClusterTestUtil.waitForClusterReady(node1, node2, node3, nodeSlave2);\n\n    for (String nodeInfo : node2.clusterNodes().split(\"\\n\")) {\n      if (nodeInfo.contains(\"myself\")) {\n        nodeSlave2.clusterReplicate(nodeInfo.split(\" \")[0]);\n        break;\n      }\n    }\n\n    try {\n      nodeSlave2.get(\"test\");\n      fail();\n    } catch (JedisMovedDataException e) {\n    }\n    nodeSlave2.readonly();\n    nodeSlave2.get(\"test\");\n\n    nodeSlave2.readwrite();\n    try {\n      nodeSlave2.get(\"test\");\n      fail();\n    } catch (JedisMovedDataException e) {\n    }\n\n    nodeSlave2.clusterReset(ClusterResetType.SOFT);\n    nodeSlave2.flushDB();\n  }\n\n  @Test\n  public void testReadFromReplicas() throws Exception {\n    node1.clusterMeet(LOCAL_IP, nodeInfoSlave2.getPort());\n    JedisClusterTestUtil.waitForClusterReady(node1, node2, node3, nodeSlave2);\n\n    for (String nodeInfo : node2.clusterNodes().split(\"\\n\")) {\n      if (nodeInfo.contains(\"myself\")) {\n        nodeSlave2.clusterReplicate(nodeInfo.split(\" \")[0]);\n        break;\n      }\n    }\n\n    DefaultJedisClientConfig READ_REPLICAS_CLIENT_CONFIG = DefaultJedisClientConfig.builder()\n        .password(endpoint.getPassword()).readOnlyForRedisClusterReplicas().build();\n    ClusterCommandObjects commandObjects = new ClusterCommandObjects();\n    try (RedisClusterClient jedisCluster = RedisClusterClient.builder()\n        .nodes(Collections.singleton(nodeInfo1))\n        .clientConfig(READ_REPLICAS_CLIENT_CONFIG)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      assertEquals(\"OK\", jedisCluster.set(\"test\", \"read-from-replicas\"));\n\n      assertEquals(\"read-from-replicas\", jedisCluster.executeCommandToReplica(commandObjects.get(\"test\")));\n      // TODO: ensure data being served from replica node(s)\n    }\n\n    nodeSlave2.clusterReset(ClusterResetType.SOFT);\n    nodeSlave2.flushDB();\n  }\n\n  /**\n   * slot->nodes 15363 node3 e\n   */\n  @Test\n  public void testMigrate() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      String node3Id = JedisClusterTestUtil.getNodeId(node3.clusterNodes());\n      String node2Id = JedisClusterTestUtil.getNodeId(node2.clusterNodes());\n      node3.clusterSetSlotMigrating(15363, node2Id);\n      node2.clusterSetSlotImporting(15363, node3Id);\n      try {\n        node2.set(\"e\", \"e\");\n      } catch (JedisMovedDataException jme) {\n        assertEquals(15363, jme.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo3.getPort()), jme.getTargetNode());\n      }\n\n      try {\n        node3.set(\"e\", \"e\");\n      } catch (JedisAskDataException jae) {\n        assertEquals(15363, jae.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo2.getPort()), jae.getTargetNode());\n      }\n\n      jc.set(\"e\", \"e\");\n\n      try {\n        node2.get(\"e\");\n      } catch (JedisMovedDataException jme) {\n        assertEquals(15363, jme.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo3.getPort()), jme.getTargetNode());\n      }\n      try {\n        node3.get(\"e\");\n      } catch (JedisAskDataException jae) {\n        assertEquals(15363, jae.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo2.getPort()), jae.getTargetNode());\n      }\n\n      assertEquals(\"e\", jc.get(\"e\"));\n\n      node2.clusterSetSlotNode(15363, node2Id);\n      node3.clusterSetSlotNode(15363, node2Id);\n      // assertEquals(\"e\", jc.get(\"e\"));\n      assertEquals(\"e\", node2.get(\"e\"));\n\n      // assertEquals(\"e\", node3.get(\"e\"));\n    }\n  }\n\n  @Test\n  public void testMigrateToNewNode() throws InterruptedException {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      node3.clusterMeet(LOCAL_IP, nodeInfo4.getPort());\n\n      String node3Id = JedisClusterTestUtil.getNodeId(node3.clusterNodes());\n      String node4Id = JedisClusterTestUtil.getNodeId(node4.clusterNodes());\n      JedisClusterTestUtil.waitForClusterReady(node4);\n      node3.clusterSetSlotMigrating(15363, node4Id);\n      node4.clusterSetSlotImporting(15363, node3Id);\n      try {\n        node4.set(\"e\", \"e\");\n      } catch (JedisMovedDataException jme) {\n        assertEquals(15363, jme.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo3.getPort()), jme.getTargetNode());\n      }\n\n      try {\n        node3.set(\"e\", \"e\");\n      } catch (JedisAskDataException jae) {\n        assertEquals(15363, jae.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo4.getPort()), jae.getTargetNode());\n      }\n\n      try {\n        node3.set(\"e\", \"e\");\n      } catch (JedisAskDataException jae) {\n        assertEquals(15363, jae.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo4.getPort()), jae.getTargetNode());\n      }\n\n      jc.set(\"e\", \"e\");\n\n      try {\n        node4.get(\"e\");\n      } catch (JedisMovedDataException jme) {\n        assertEquals(15363, jme.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo3.getPort()), jme.getTargetNode());\n      }\n      try {\n        node3.get(\"e\");\n      } catch (JedisAskDataException jae) {\n        assertEquals(15363, jae.getSlot());\n        assertEquals(new HostAndPort(LOCAL_IP, nodeInfo4.getPort()), jae.getTargetNode());\n      }\n\n      assertEquals(\"e\", jc.get(\"e\"));\n\n      node4.clusterSetSlotNode(15363, node4Id);\n      node3.clusterSetSlotNode(15363, node4Id);\n      // assertEquals(\"e\", jc.get(\"e\"));\n      assertEquals(\"e\", node4.get(\"e\"));\n\n      // assertEquals(\"e\", node3.get(\"e\"));\n    }\n  }\n\n  @Test\n  public void testRecalculateSlotsWhenMoved() throws InterruptedException {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      int slot51 = JedisClusterCRC16.getSlot(\"51\");\n      node2.clusterDelSlots(slot51);\n      node3.clusterDelSlots(slot51);\n      node3.clusterAddSlots(slot51);\n\n      JedisClusterTestUtil.waitForClusterReady(node1, node2, node3);\n      jc.set(\"51\", \"foo\");\n      assertEquals(\"foo\", jc.get(\"51\"));\n    }\n  }\n\n  @Test\n  public void testAskResponse() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      int slot51 = JedisClusterCRC16.getSlot(\"51\");\n      node3.clusterSetSlotImporting(slot51, JedisClusterTestUtil.getNodeId(node2.clusterNodes()));\n      node2.clusterSetSlotMigrating(slot51, JedisClusterTestUtil.getNodeId(node3.clusterNodes()));\n      jc.set(\"51\", \"foo\");\n      assertEquals(\"foo\", jc.get(\"51\"));\n    }\n  }\n\n  @Test\n  public void testAskResponseWithConfig() {\n    HostAndPort hp = nodeInfo1;\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DEFAULT_CLIENT_CONFIG)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      int slot51 = JedisClusterCRC16.getSlot(\"51\");\n      node3.clusterSetSlotImporting(slot51, JedisClusterTestUtil.getNodeId(node2.clusterNodes()));\n      node2.clusterSetSlotMigrating(slot51, JedisClusterTestUtil.getNodeId(node3.clusterNodes()));\n      jc.set(\"51\", \"foo\");\n      assertEquals(\"foo\", jc.get(\"51\"));\n    }\n  }\n\n//  @Test(expected = JedisClusterMaxAttemptsException.class)\n  @Test\n  public void testRedisClusterMaxRedirections() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    assertThrows(JedisClusterOperationException.class,()-> {\n      try (RedisClusterClient jc = RedisClusterClient.builder()\n          .nodes(jedisClusterNode)\n          .clientConfig(DefaultJedisClientConfig.builder()\n              .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n              .socketTimeoutMillis(DEFAULT_TIMEOUT)\n              .password(endpoint.getPassword())\n              .build())\n          .maxAttempts(DEFAULT_REDIRECTIONS)\n          .poolConfig(DEFAULT_POOL_CONFIG)\n          .build()) {\n        int slot51 = JedisClusterCRC16.getSlot(\"51\");\n        // This will cause an infinite redirection loop\n        node2.clusterSetSlotMigrating(slot51, JedisClusterTestUtil.getNodeId(node3.clusterNodes()));\n        jc.set(\"51\", \"foo\");\n      }\n    });\n  }\n\n//  @Test(expected = JedisClusterMaxAttemptsException.class)\n  @Test\n  public void testRedisClusterMaxRedirectionsWithConfig() {\n    HostAndPort hp = nodeInfo1;\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DEFAULT_CLIENT_CONFIG)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      int slot51 = JedisClusterCRC16.getSlot(\"51\");\n      // This will cause an infinite redirection loop\n      node2.clusterSetSlotMigrating(slot51, JedisClusterTestUtil.getNodeId(node3.clusterNodes()));\n      assertThrows(JedisClusterOperationException.class, ()->jc.set(\"51\", \"foo\"));\n    }\n  }\n\n  @Test\n  public void testClusterForgetNode() {\n    // at first, join node4 to cluster\n    node1.clusterMeet(nodeInfo4.getHost(), nodeInfo4.getPort());\n    node2.clusterMeet(nodeInfo4.getHost(), nodeInfo4.getPort());\n    node3.clusterMeet(nodeInfo4.getHost(), nodeInfo4.getPort());\n\n    String node4Id = JedisClusterTestUtil.getNodeId(node4.clusterNodes());\n\n    JedisClusterTestUtil.assertNodeIsKnown(node1, node4Id, 1000);\n    JedisClusterTestUtil.assertNodeIsKnown(node2, node4Id, 1000);\n    JedisClusterTestUtil.assertNodeIsKnown(node3, node4Id, 1000);\n\n    assertNodeHandshakeEnded(node1, 1000);\n    assertNodeHandshakeEnded(node2, 1000);\n    assertNodeHandshakeEnded(node3, 1000);\n\n    assertEquals(4, node1.clusterNodes().split(\"\\n\").length);\n    assertEquals(4, node2.clusterNodes().split(\"\\n\").length);\n    assertEquals(4, node3.clusterNodes().split(\"\\n\").length);\n\n    // do cluster forget\n    node1.clusterForget(node4Id);\n    node2.clusterForget(node4Id);\n    node3.clusterForget(node4Id);\n\n    JedisClusterTestUtil.assertNodeIsUnknown(node1, node4Id, 1000);\n    JedisClusterTestUtil.assertNodeIsUnknown(node2, node4Id, 1000);\n    JedisClusterTestUtil.assertNodeIsUnknown(node3, node4Id, 1000);\n\n    assertEquals(3, node1.clusterNodes().split(\"\\n\").length);\n    assertEquals(3, node2.clusterNodes().split(\"\\n\").length);\n    assertEquals(3, node3.clusterNodes().split(\"\\n\").length);\n  }\n\n  @Test\n  public void testClusterFlushSlots() {\n    String slotRange = getNodeServingSlotRange(node1.clusterNodes());\n    assertNotNull(slotRange);\n\n    try {\n      node1.clusterFlushSlots();\n      assertNull(getNodeServingSlotRange(node1.clusterNodes()));\n    } finally {\n      // rollback\n      String[] rangeInfo = slotRange.split(\"-\");\n      int lower = Integer.parseInt(rangeInfo[0]);\n      int upper = Integer.parseInt(rangeInfo[1]);\n\n      int[] node1Slots = new int[upper - lower + 1];\n      for (int i = 0; lower <= upper;) {\n        node1Slots[i++] = lower++;\n      }\n      node1.clusterAddSlots(node1Slots);\n    }\n  }\n\n  @Test\n  public void testClusterCountKeysInSlot() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(new HostAndPort(nodeInfo1.getHost(), nodeInfo1.getPort()));\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n\n      int count = 5;\n      for (int index = 0; index < count; index++) {\n        jc.set(\"foo{bar}\" + index, \"hello\");\n      }\n\n      int slot = JedisClusterCRC16.getSlot(\"foo{bar}\");\n      assertEquals(count, node1.clusterCountKeysInSlot(slot));\n    }\n  }\n\n  @Test\n  public void testStableSlotWhenMigratingNodeOrImportingNodeIsNotSpecified()\n      throws InterruptedException {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(new HostAndPort(nodeInfo1.getHost(), nodeInfo1.getPort()));\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n\n      int slot51 = JedisClusterCRC16.getSlot(\"51\");\n      jc.set(\"51\", \"foo\");\n      // node2 is responsible of taking care of slot51 (7186)\n\n      node3.clusterSetSlotImporting(slot51, JedisClusterTestUtil.getNodeId(node2.clusterNodes()));\n      assertEquals(\"foo\", jc.get(\"51\"));\n      node3.clusterSetSlotStable(slot51);\n      assertEquals(\"foo\", jc.get(\"51\"));\n\n      node2.clusterSetSlotMigrating(slot51, JedisClusterTestUtil.getNodeId(node3.clusterNodes()));\n      // assertEquals(\"foo\", jc.get(\"51\")); // it leads Max Redirections\n      node2.clusterSetSlotStable(slot51);\n      assertEquals(\"foo\", jc.get(\"51\"));\n    }\n  }\n\n  @Test\n  public void testIfPoolConfigAppliesToClusterPools() {\n    GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();\n    config.setMaxTotal(0);\n    config.setMaxWait(Duration.ofMillis(DEFAULT_TIMEOUT));\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(config)\n        .build()) {\n      assertThrows(JedisException.class, ()->jc.set(\"52\", \"poolTestValue\"));\n    }\n  }\n\n  @Test\n  public void testCloseable() throws IOException {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(new HostAndPort(nodeInfo1.getHost(), nodeInfo1.getPort()));\n\n    RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build();\n    jc.set(\"51\", \"foo\");\n    jc.close();\n\n    assertEquals(0, jc.getClusterNodes().size());\n  }\n\n  @Test\n  public void testCloseableWithConfig() {\n    HostAndPort hp = nodeInfo1;\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DEFAULT_CLIENT_CONFIG)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      jc.set(\"51\", \"foo\");\n      jc.close();\n\n      assertEquals(0, jc.getClusterNodes().size());\n    }\n  }\n\n  @Test\n  public void testJedisClusterTimeout() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(new HostAndPort(nodeInfo1.getHost(), nodeInfo1.getPort()));\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(4000)\n            .socketTimeoutMillis(4000)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n\n      for (Pool<Connection> pool : jc.getClusterNodes().values()) {\n        try (Connection conn = pool.getResource()) {\n          assertEquals(4000, conn.getSoTimeout());\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testJedisClusterTimeoutWithConfig() {\n    HostAndPort hp = nodeInfo1;\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(hp))\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(4000).socketTimeoutMillis(4000).password(endpoint.getPassword()).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n\n      jc.getClusterNodes().values().forEach(pool -> {\n        try (Connection conn = pool.getResource()) {\n          assertEquals(4000, conn.getSoTimeout());\n        }\n      });\n    }\n  }\n\n  @Test\n  public void testJedisClusterRunsWithMultithreaded() throws InterruptedException,\n      ExecutionException, IOException {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    final RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build();\n    jc.set(\"foo\", \"bar\");\n\n    ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 100, 0, TimeUnit.SECONDS,\n        new ArrayBlockingQueue<Runnable>(10));\n    List<Future<String>> futures = new ArrayList<Future<String>>();\n    for (int i = 0; i < 50; i++) {\n      executor.submit(new Callable<String>() {\n        @Override\n        public String call() throws Exception {\n          // FIXME : invalidate slot cache from JedisCluster to test\n          // random connection also does work\n          return jc.get(\"foo\");\n        }\n      });\n    }\n\n    for (Future<String> future : futures) {\n      String value = future.get();\n      assertEquals(\"bar\", value);\n    }\n\n    jc.close();\n  }\n\n  @Test\n  @Timeout(value = DEFAULT_TIMEOUT * 2, unit = TimeUnit.MILLISECONDS)\n  public void testReturnConnectionOnJedisConnectionException() throws InterruptedException {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    ConnectionPoolConfig config = new ConnectionPoolConfig();\n    config.setMaxTotal(1);\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(config)\n        .build()) {\n\n      try (Connection c = jc.getClusterNodes().get(nodeInfo2.toString()).getResource()) {\n        Jedis j = new Jedis(c);\n        ClientKillerUtil.tagClient(j, \"DEAD\");\n        ClientKillerUtil.killClient(j, \"DEAD\");\n      }\n\n      jc.get(\"test\");\n    }\n  }\n\n  @Test\n  @Timeout(value = DEFAULT_TIMEOUT, unit = TimeUnit.MILLISECONDS)\n  public void testReturnConnectionOnRedirection() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    ConnectionPoolConfig config = new ConnectionPoolConfig();\n    config.setMaxTotal(1);\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(config)\n        .build()) {\n\n      // This will cause an infinite redirection between node 2 and 3\n      node3.clusterSetSlotMigrating(15363, JedisClusterTestUtil.getNodeId(node2.clusterNodes()));\n      assertThrows(JedisClusterOperationException.class, ()->jc.get(\"e\"));\n    }\n  }\n\n  @Test\n  public void testLocalhostNodeNotAddedWhen127Present() {\n    HostAndPort localhost = new HostAndPort(\"localhost\", nodeInfo1.getPort());\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    // cluster node is defined as 127.0.0.1; adding localhost should work,\n    // but shouldn't show up.\n    jedisClusterNode.add(localhost);\n    ConnectionPoolConfig config = new ConnectionPoolConfig();\n    config.setMaxTotal(1);\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(config)\n        .build()) {\n      Map<String, ?> clusterNodes = jc.getClusterNodes();\n      assertEquals(3, clusterNodes.size());\n      assertFalse(clusterNodes.containsKey(JedisClusterInfoCache.getNodeKey(localhost)));\n    }\n  }\n\n  @Test\n  public void testInvalidStartNodeNotAdded() {\n    HostAndPort invalidHost = new HostAndPort(\"not-a-real-host\", nodeInfo1.getPort());\n    Set<HostAndPort> jedisClusterNode = new LinkedHashSet<HostAndPort>();\n    jedisClusterNode.add(invalidHost);\n    jedisClusterNode.add(nodeInfo1);\n    ConnectionPoolConfig config = new ConnectionPoolConfig();\n    config.setMaxTotal(1);\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(config)\n        .build()) {\n      Map<String, ?> clusterNodes = jc.getClusterNodes();\n      assertEquals(3, clusterNodes.size());\n      assertFalse(clusterNodes.containsKey(JedisClusterInfoCache.getNodeKey(invalidHost)));\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void clusterLinks2() {\n    Set<String> mapKeys = new HashSet<>(Arrays.asList(\"direction\", \"node\", \"create-time\", \"events\",\n        \"send-buffer-allocated\", \"send-buffer-used\"));\n\n    List<Map<String, Object>> links = node1.clusterLinks();\n    assertNotNull(links);\n    assertTrue(links.size() >= 3);\n    for (Map<String, Object> link : links) {\n      assertEquals(6, link.size());\n      assertEquals(mapKeys, link.keySet());\n    }\n  }\n\n  @Test\n  public void clusterRefreshNodes() throws Exception {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    jedisClusterNode.add(nodeInfo2);\n    jedisClusterNode.add(nodeInfo3);\n\n    try (RedisClusterClient cluster = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .connectionTimeoutMillis(DEFAULT_TIMEOUT)\n            .socketTimeoutMillis(DEFAULT_TIMEOUT)\n            .password(endpoint.getPassword())\n            .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .build()) {\n      assertEquals(3, cluster.getClusterNodes().size());\n      cleanUp(); // cleanup and add node4\n\n      // at first, join node4 to cluster\n      node1.clusterMeet(LOCAL_IP, nodeInfo2.getPort());\n      node1.clusterMeet(LOCAL_IP, nodeInfo3.getPort());\n      node1.clusterMeet(LOCAL_IP, nodeInfo4.getPort());\n      // split available slots across the three nodes\n      int slotsPerNode = CLUSTER_HASHSLOTS / 4;\n      int[] node1Slots = new int[slotsPerNode];\n      int[] node2Slots = new int[slotsPerNode];\n      int[] node3Slots = new int[slotsPerNode];\n      int[] node4Slots = new int[slotsPerNode];\n      for (int i = 0, slot1 = 0, slot2 = 0, slot3 = 0, slot4 = 0; i < CLUSTER_HASHSLOTS; i++) {\n        if (i < slotsPerNode) {\n          node1Slots[slot1++] = i;\n        } else if (i >= slotsPerNode && i < slotsPerNode*2) {\n          node2Slots[slot2++] = i;\n        } else if (i >= slotsPerNode*2 && i < slotsPerNode*3) {\n          node3Slots[slot3++] = i;\n        } else {\n          node4Slots[slot4++] = i;\n        }\n      }\n\n      node1.clusterAddSlots(node1Slots);\n      node2.clusterAddSlots(node2Slots);\n      node3.clusterAddSlots(node3Slots);\n      node4.clusterAddSlots(node4Slots);\n      JedisClusterTestUtil.waitForClusterReady(node1, node2, node3, node4);\n\n      // cluster.set(\"key\", \"value\"); will get JedisMovedDataException and renewSlotCache\n      cluster.set(\"key\", \"value\");\n\n      assertEquals(4, cluster.getClusterNodes().size());\n      String nodeKey4 = LOCAL_IP + \":\" + nodeInfo4.getPort();\n      assertTrue(cluster.getClusterNodes().keySet().contains(nodeKey4));\n\n      // make 4 nodes to 3 nodes\n      cleanUp();\n      setUp();\n      // cluster.set(\"bar\", \"foo\") will get JedisMovedDataException and renewSlotCache\n      cluster.set(\"bar\", \"foo\");\n      assertEquals(3, cluster.getClusterNodes().size());\n    }\n  }\n\n  @Test\n  @Timeout(30)\n  public void clusterPeriodTopologyRefreshTest() throws Exception {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(nodeInfo1);\n    jedisClusterNode.add(nodeInfo2);\n    jedisClusterNode.add(nodeInfo3);\n\n    // we set topologyRefreshPeriod is 1s\n    Duration topologyRefreshPeriod = Duration.ofSeconds(1);\n    try (RedisClusterClient cluster = RedisClusterClient.builder()\n        .nodes(jedisClusterNode)\n        .clientConfig(DEFAULT_CLIENT_CONFIG)\n        .poolConfig(DEFAULT_POOL_CONFIG)\n        .topologyRefreshPeriod(topologyRefreshPeriod)\n        .maxAttempts(DEFAULT_REDIRECTIONS)\n        .maxTotalRetriesDuration(Duration.ofSeconds(10))\n        .build()) {\n      assertEquals(3, cluster.getClusterNodes().size());\n      cleanUp(); // cleanup and add node4\n\n      // at first, join node4 to cluster\n      node1.clusterMeet(LOCAL_IP, nodeInfo2.getPort());\n      node1.clusterMeet(LOCAL_IP, nodeInfo3.getPort());\n      node1.clusterMeet(LOCAL_IP, nodeInfo4.getPort());\n      // split available slots across the three nodes\n      int slotsPerNode = CLUSTER_HASHSLOTS / 4;\n      int[] node1Slots = new int[slotsPerNode];\n      int[] node2Slots = new int[slotsPerNode];\n      int[] node3Slots = new int[slotsPerNode];\n      int[] node4Slots = new int[slotsPerNode];\n      for (int i = 0, slot1 = 0, slot2 = 0, slot3 = 0, slot4 = 0; i < CLUSTER_HASHSLOTS; i++) {\n        if (i < slotsPerNode) {\n          node1Slots[slot1++] = i;\n        } else if (i >= slotsPerNode && i < slotsPerNode*2) {\n          node2Slots[slot2++] = i;\n        } else if (i >= slotsPerNode*2 && i < slotsPerNode*3) {\n          node3Slots[slot3++] = i;\n        } else {\n          node4Slots[slot4++] = i;\n        }\n      }\n\n      node1.clusterAddSlots(node1Slots);\n      node2.clusterAddSlots(node2Slots);\n      node3.clusterAddSlots(node3Slots);\n      node4.clusterAddSlots(node4Slots);\n      JedisClusterTestUtil.waitForClusterReady(node1, node2, node3, node4);\n\n      // Now we just wait topologyRefreshPeriod * 3 (executor will delay) for cluster topology refresh (3 -> 4)\n      Thread.sleep(topologyRefreshPeriod.toMillis() * 3);\n\n      assertEquals(4, cluster.getClusterNodes().size());\n      String nodeKey4 = LOCAL_IP + \":\" + nodeInfo4.getPort();\n      assertTrue(cluster.getClusterNodes().keySet().contains(nodeKey4));\n\n      // make 4 nodes to 3 nodes\n      cleanUp();\n      setUp();\n\n      // Now we just wait topologyRefreshPeriod * 3 (executor will delay) for cluster topology refresh (4 -> 3)\n      Thread.sleep(topologyRefreshPeriod.toMillis() * 3);\n      assertEquals(3, cluster.getClusterNodes().size());\n    }\n  }\n\n  private static String getNodeServingSlotRange(String infoOutput) {\n    // f4f3dc4befda352a4e0beccf29f5e8828438705d 127.0.0.1:7380 master - 0\n    // 1394372400827 0 connected 5461-10922\n    for (String infoLine : infoOutput.split(\"\\n\")) {\n      if (infoLine.contains(\"myself\")) {\n        try {\n          return infoLine.split(\" \")[8];\n        } catch (ArrayIndexOutOfBoundsException e) {\n          return null;\n        }\n      }\n    }\n    return null;\n  }\n\n  private void assertNodeHandshakeEnded(Jedis node, int timeoutMs) {\n    int sleepInterval = 100;\n    for (int sleepTime = 0; sleepTime <= timeoutMs; sleepTime += sleepInterval) {\n      boolean isHandshaking = isAnyNodeHandshaking(node);\n      if (!isHandshaking) return;\n\n      try {\n        Thread.sleep(sleepInterval);\n      } catch (InterruptedException e) {\n      }\n    }\n\n    throw new JedisException(\"Node handshaking is not ended\");\n  }\n\n  private boolean isAnyNodeHandshaking(Jedis node) {\n    String infoOutput = node.clusterNodes();\n    for (String infoLine : infoOutput.split(\"\\n\")) {\n      if (infoLine.contains(\"handshake\")) {\n        return true;\n      }\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/UnboundRedisClusterClientTestBase.java",
    "content": "package redis.clients.jedis;\n\nimport static redis.clients.jedis.Protocol.CLUSTER_HASHSLOTS;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.args.ClusterResetType;\nimport redis.clients.jedis.util.JedisClusterTestUtil;\n\n@Tag(\"integration\")\npublic abstract class UnboundRedisClusterClientTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  protected static Jedis node1;\n  protected static Jedis node2;\n  protected static Jedis node3;\n  protected static Jedis node4;\n  protected static Jedis nodeSlave2;\n\n  protected static HostAndPort nodeInfo1;\n  protected static HostAndPort nodeInfo2;\n  protected static HostAndPort nodeInfo3;\n  protected static HostAndPort nodeInfo4;\n  protected static HostAndPort nodeInfoSlave2;\n\n  protected static final String LOCAL_IP = \"127.0.0.1\";\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"cluster-unbound\"));\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      () -> Endpoints.getRedisEndpoint(\"cluster-unbound\"));\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-unbound\");\n    nodeInfo1 = endpoint.getHostsAndPorts().get(0);\n    nodeInfo2 = endpoint.getHostsAndPorts().get(1);\n    nodeInfo3 = endpoint.getHostsAndPorts().get(2);\n    nodeInfo4 = endpoint.getHostsAndPorts().get(3);\n    nodeInfoSlave2 = endpoint.getHostsAndPorts().get(4);\n  }\n\n  @BeforeEach\n  public void setUp() throws InterruptedException {\n    node1 = new Jedis(nodeInfo1);\n    node1.auth(endpoint.getPassword());\n    node1.flushAll();\n\n    node2 = new Jedis(nodeInfo2);\n    node2.auth(endpoint.getPassword());\n    node2.flushAll();\n\n    node3 = new Jedis(nodeInfo3);\n    node3.auth(endpoint.getPassword());\n    node3.flushAll();\n\n    node4 = new Jedis(nodeInfo4);\n    node4.auth(endpoint.getPassword());\n    node4.flushAll();\n\n    nodeSlave2 = new Jedis(nodeInfoSlave2);\n    nodeSlave2.auth(endpoint.getPassword());\n    nodeSlave2.flushAll();\n    // ---- configure cluster\n\n    // add nodes to cluster\n    node1.clusterMeet(LOCAL_IP, nodeInfo2.getPort());\n    node1.clusterMeet(LOCAL_IP, nodeInfo3.getPort());\n\n    // split available slots across the three nodes\n    int slotsPerNode = CLUSTER_HASHSLOTS / 3;\n    int[] node1Slots = new int[slotsPerNode];\n    int[] node2Slots = new int[slotsPerNode + 1];\n    int[] node3Slots = new int[slotsPerNode];\n    for (int i = 0, slot1 = 0, slot2 = 0, slot3 = 0; i < CLUSTER_HASHSLOTS; i++) {\n      if (i < slotsPerNode) {\n        node1Slots[slot1++] = i;\n      } else if (i > slotsPerNode * 2) {\n        node3Slots[slot3++] = i;\n      } else {\n        node2Slots[slot2++] = i;\n      }\n    }\n\n    node1.clusterAddSlots(node1Slots);\n    node2.clusterAddSlots(node2Slots);\n    node3.clusterAddSlots(node3Slots);\n\n    JedisClusterTestUtil.waitForClusterReady(node1, node2, node3);\n  }\n\n  protected void cleanUp() {\n    node1.flushDB();\n    node2.flushDB();\n    node3.flushDB();\n    node4.flushDB();\n    node1.clusterReset(ClusterResetType.HARD);\n    node2.clusterReset(ClusterResetType.HARD);\n    node3.clusterReset(ClusterResetType.HARD);\n    node4.clusterReset(ClusterResetType.HARD);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    cleanUp();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/UnifiedJedisCustomCommandsTest.java",
    "content": "package redis.clients.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.mocked.unified.UnifiedJedisMockedTestBase;\n\n/**\n * These tests are part of the mocked tests for {@link UnifiedJedis}, but, due to {@code protected}\n * visibility of some methods, they must reside in the same package as the tested class.\n */\npublic class UnifiedJedisCustomCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testSendCommandWithProtocolCommand() {\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    CommandArguments commandArguments = mock(CommandArguments.class);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendCommand(cmd);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArguments));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendCommandWithProtocolCommandAndByteArrayArgs() {\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    byte[][] args = { \"arg1\".getBytes(), \"arg2\".getBytes() };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendCommand(cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsWithArgs));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendBlockingCommandWithProtocolCommandAndByteArrayArgs() {\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    byte[][] args = { \"arg1\".getBytes(), \"arg2\".getBytes() };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n\n    CommandArguments commandArgumentsBlocking = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    when(commandArgumentsWithArgs.blocking()).thenReturn(commandArgumentsBlocking);\n\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendBlockingCommand(cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsBlocking));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendCommandWithProtocolCommandAndStringArgs() {\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    String[] args = { \"arg1\", \"arg2\" };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendCommand(cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsWithArgs));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendBlockingCommandWithProtocolCommandAndStringArgs() {\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    String[] args = { \"arg1\", \"arg2\" };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n\n    CommandArguments commandArgumentsBlocking = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    when(commandArgumentsWithArgs.blocking()).thenReturn(commandArgumentsBlocking);\n\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendBlockingCommand(cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsBlocking));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendCommandWithSampleKeyProtocolCommandAndByteArrayArgs() {\n    byte[] sampleKey = \"key\".getBytes();\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    byte[][] args = { \"arg1\".getBytes(), \"arg2\".getBytes() };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithKey = mock(CommandArguments.class);\n\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n    when(commandArgumentsWithArgs.addHashSlotKey(sampleKey)).thenReturn(commandArgumentsWithKey);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendCommand(sampleKey, cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsWithKey));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendBlockingCommandWithSampleKeyProtocolCommandAndByteArrayArgs() {\n    byte[] sampleKey = \"key\".getBytes();\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    byte[][] args = { \"arg1\".getBytes(), \"arg2\".getBytes() };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    CommandArguments commandArgumentsBlocking = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithKey = mock(CommandArguments.class);\n\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n    when(commandArgumentsWithArgs.blocking()).thenReturn(commandArgumentsBlocking);\n    when(commandArgumentsBlocking.addHashSlotKey(sampleKey)).thenReturn(commandArgumentsWithKey);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendBlockingCommand(sampleKey, cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsWithKey));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendCommandWithStringSampleKeyProtocolCommandAndStringArgs() {\n    String sampleKey = \"key\";\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    String[] args = { \"arg1\", \"arg2\" };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithKey = mock(CommandArguments.class);\n\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n    when(commandArgumentsWithArgs.addHashSlotKey(sampleKey)).thenReturn(commandArgumentsWithKey);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendCommand(sampleKey, cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsWithKey));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n  @Test\n  public void testSendBlockingCommandWithStringSampleKeyProtocolCommandAndStringArgs() {\n    String sampleKey = \"key\";\n    ProtocolCommand cmd = mock(ProtocolCommand.class);\n    String[] args = { \"arg1\", \"arg2\" };\n    CommandArguments commandArguments = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithArgs = mock(CommandArguments.class);\n    CommandArguments commandArgumentsBlocking = mock(CommandArguments.class);\n    CommandArguments commandArgumentsWithKey = mock(CommandArguments.class);\n\n    when(commandArguments.addObjects((Object[]) args)).thenReturn(commandArgumentsWithArgs);\n    when(commandArgumentsWithArgs.blocking()).thenReturn(commandArgumentsBlocking);\n    when(commandArgumentsBlocking.addHashSlotKey(sampleKey)).thenReturn(commandArgumentsWithKey);\n\n    when(commandObjects.commandArguments(cmd)).thenReturn(commandArguments);\n    when(commandExecutor.executeCommand(any())).thenReturn(\"OK\");\n\n    Object result = jedis.sendBlockingCommand(sampleKey, cmd, args);\n\n    ArgumentCaptor<CommandObject<Object>> argumentCaptor = ArgumentCaptor.forClass(CommandObject.class);\n    verify(commandExecutor).executeCommand(argumentCaptor.capture());\n\n    CommandObject<Object> commandObject = argumentCaptor.getValue();\n    assertThat(commandObject.getArguments(), sameInstance(commandArgumentsWithKey));\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandObjects).commandArguments(cmd);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/EntraIDTestContext.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport java.io.ByteArrayInputStream;\nimport java.security.KeyFactory;\nimport java.security.PrivateKey;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.HashSet;\nimport java.util.Set;\n\npublic class EntraIDTestContext {\n  private static final String AZURE_CLIENT_ID = \"AZURE_CLIENT_ID\";\n  private static final String AZURE_AUTHORITY = \"AZURE_AUTHORITY\";\n  private static final String AZURE_CLIENT_SECRET = \"AZURE_CLIENT_SECRET\";\n  private static final String AZURE_PRIVATE_KEY = \"AZURE_PRIVATE_KEY\";\n  private static final String AZURE_CERT = \"AZURE_CERT\";\n  private static final String AZURE_REDIS_SCOPES = \"AZURE_REDIS_SCOPES\";\n  private static final String AZURE_USER_ASSIGNED_MANAGED_ID = \"AZURE_USER_ASSIGNED_MANAGED_ID\";\n\n  private String clientId;\n  private String authority;\n  private String clientSecret;\n  private PrivateKey privateKey;\n  private X509Certificate cert;\n  private Set<String> redisScopes;\n  private String userAssignedManagedIdentity;\n\n  public static final EntraIDTestContext DEFAULT = new EntraIDTestContext();\n\n  private EntraIDTestContext() {\n    clientId = System.getenv(AZURE_CLIENT_ID);\n    authority = System.getenv(AZURE_AUTHORITY);\n    clientSecret = System.getenv(AZURE_CLIENT_SECRET);\n    userAssignedManagedIdentity = System.getenv(AZURE_USER_ASSIGNED_MANAGED_ID);\n  }\n\n  public EntraIDTestContext(String clientId, String authority, String clientSecret,\n      PrivateKey privateKey, X509Certificate cert, Set<String> redisScopes,\n      String userAssignedManagedIdentity) {\n    this.clientId = clientId;\n    this.authority = authority;\n    this.clientSecret = clientSecret;\n    this.privateKey = privateKey;\n    this.cert = cert;\n    this.redisScopes = redisScopes;\n    this.userAssignedManagedIdentity = userAssignedManagedIdentity;\n  }\n\n  public String getClientId() {\n    return clientId;\n  }\n\n  public String getAuthority() {\n    return authority;\n  }\n\n  public String getClientSecret() {\n    return clientSecret;\n  }\n\n  public PrivateKey getPrivateKey() {\n    if (privateKey == null) {\n      this.privateKey = getPrivateKey(System.getenv(AZURE_PRIVATE_KEY));\n    }\n    return privateKey;\n  }\n\n  public X509Certificate getCert() {\n    if (cert == null) {\n      this.cert = getCert(System.getenv(AZURE_CERT));\n    }\n    return cert;\n  }\n\n  public Set<String> getRedisScopes() {\n    if (redisScopes == null) {\n      String redisScopesEnv = System.getenv(AZURE_REDIS_SCOPES);\n      this.redisScopes = new HashSet<>(Arrays.asList(redisScopesEnv.split(\";\")));\n    }\n    return redisScopes;\n  }\n\n  public String getUserAssignedManagedIdentity() {\n    return userAssignedManagedIdentity;\n  }\n\n  private PrivateKey getPrivateKey(String privateKey) {\n    try {\n      // Decode the base64 encoded key into a byte array\n      byte[] decodedKey = Base64.getDecoder().decode(privateKey);\n\n      // Generate the private key from the decoded byte array using PKCS8EncodedKeySpec\n      PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);\n      KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\"); // Use the correct algorithm (e.g., \"RSA\", \"EC\", \"DSA\")\n      PrivateKey key = keyFactory.generatePrivate(keySpec);\n      return key;\n    } catch (Exception e) {\n      e.printStackTrace();\n      throw new RuntimeException(e);\n    }\n  }\n\n  private X509Certificate getCert(String cert) {\n    try {\n      // Convert the Base64 encoded string into a byte array\n      byte[] encoded = java.util.Base64.getDecoder().decode(cert);\n\n      // Create a CertificateFactory for X.509 certificates\n      CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n\n      // Generate the certificate from the byte array\n      X509Certificate certificate = (X509Certificate) certificateFactory\n          .generateCertificate(new ByteArrayInputStream(encoded));\n      return certificate;\n    } catch (Exception e) {\n      e.printStackTrace();\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/RedisEntraIDClusterIntegrationTests.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Durations.*;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPoolConfig;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.RedisClusterClient;\n\npublic class RedisEntraIDClusterIntegrationTests {\n    private static final Logger log = LoggerFactory\n            .getLogger(RedisEntraIDClusterIntegrationTests.class);\n\n    private static EntraIDTestContext testCtx;\n    private static EndpointConfig endpointConfig;\n    private static HostAndPort hnp;\n\n    @BeforeAll\n    public static void before() {\n        try {\n            testCtx = EntraIDTestContext.DEFAULT;\n            endpointConfig = Endpoints.getRedisEndpoint(\"cluster-entraid-acl\");\n            hnp = endpointConfig.getHostAndPort();\n        } catch (IllegalArgumentException e) {\n            log.warn(\"Skipping test because no Redis endpoint is configured\");\n            assumeTrue(false, \"No Redis endpoint 'standalone-entraid-acl' is configured!\");\n        }\n    }\n\n    @Test\n    public void testClusterInitWithAuthXManager() {\n        TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n                .lowerRefreshBoundMillis(1000).clientId(testCtx.getClientId())\n                .secret(testCtx.getClientSecret()).authority(testCtx.getAuthority())\n                .scopes(testCtx.getRedisScopes()).build();\n\n        int defaultDirections = 5;\n        JedisClientConfig config = DefaultJedisClientConfig.builder()\n                .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n        ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig();\n\n        try (RedisClusterClient jc = RedisClusterClient.builder()\n                .nodes(Collections.singleton(hnp))\n                .clientConfig(config)\n                .maxAttempts(defaultDirections)\n                .poolConfig(DEFAULT_POOL_CONFIG)\n                .build()) {\n\n            assertEquals(\"OK\", jc.set(\"foo\", \"bar\"));\n            assertEquals(\"bar\", jc.get(\"foo\"));\n            assertEquals(1, jc.del(\"foo\"));\n        }\n    }\n\n    @Test\n    public void testClusterWithReAuth() throws InterruptedException, ExecutionException {\n        TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n                // 0.00002F is to make it fit into 2 seconds, we need at least 2 attempt in 2 seconds\n                // to trigger re-authentication.\n                // For expiration time between 30 minutes to 12 hours \n                // token renew will happen in from 36ms up to 864ms\n                // If the received token has more than 12 hours to expire, this test will probably fail, and need to be adjusted.\n                .expirationRefreshRatio(0.00002F).clientId(testCtx.getClientId())\n                .secret(testCtx.getClientSecret()).authority(testCtx.getAuthority())\n                .scopes(testCtx.getRedisScopes()).build();\n\n        AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n\n        authXManager = spy(authXManager);\n\n        List<Connection> connections = new CopyOnWriteArrayList<>();\n        doAnswer(invocation -> {\n            Connection connection = spy((Connection) invocation.getArgument(0));\n            invocation.getArguments()[0] = connection;\n            connections.add(connection);\n            return invocation.callRealMethod();\n        }).when(authXManager).addConnection(any(Connection.class));\n\n        JedisClientConfig config = DefaultJedisClientConfig.builder().authXManager(authXManager)\n                .build();\n\n        ExecutorService executorService = Executors.newFixedThreadPool(2);\n        CountDownLatch latch = new CountDownLatch(1);\n        try (RedisClusterClient jc = RedisClusterClient.builder()\n                .nodes(Collections.singleton(hnp))\n                .clientConfig(config)\n                .build()) {\n            Runnable task = () -> {\n                while (latch.getCount() > 0) {\n                    assertEquals(\"OK\", jc.set(\"foo\", \"bar\"));\n                }\n            };\n            Future<?> task1 = executorService.submit(task);\n            Future<?> task2 = executorService.submit(task);\n\n            await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(TWO_SECONDS)\n                    .until(connections::size, greaterThanOrEqualTo(2));\n\n            connections.forEach(conn -> {\n                await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(TWO_SECONDS)\n                        .untilAsserted(() -> verify(conn, atLeast(2)).reAuthenticate());\n            });\n            latch.countDown();\n            task1.get();\n            task2.get();\n        } finally {\n            latch.countDown();\n            executorService.shutdown();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/RedisEntraIDIntegrationTests.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Durations.TWO_SECONDS;\nimport static org.awaitility.Durations.FIVE_SECONDS;\nimport static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockConstruction;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.hamcrest.Matchers.in;\nimport static org.hamcrest.Matchers.is;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\nimport org.awaitility.Awaitility;\nimport org.awaitility.Durations;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.MethodOrderer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestMethodOrder;\nimport org.mockito.MockedConstruction;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.azure.identity.DefaultAzureCredential;\nimport com.azure.identity.DefaultAzureCredentialBuilder;\n\nimport redis.clients.authentication.core.IdentityProvider;\nimport redis.clients.authentication.core.IdentityProviderConfig;\nimport redis.clients.authentication.core.SimpleToken;\nimport redis.clients.authentication.core.Token;\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.authentication.entraid.AzureTokenAuthConfigBuilder;\nimport redis.clients.authentication.entraid.EntraIDIdentityProvider;\nimport redis.clients.authentication.entraid.EntraIDIdentityProviderConfig;\nimport redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;\nimport redis.clients.authentication.entraid.ServicePrincipalInfo;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.exceptions.JedisAccessControlException;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.scenario.FaultInjectionClient;\nimport redis.clients.jedis.scenario.FaultInjectionClient.TriggerActionResponse;\n\n@TestMethodOrder(MethodOrderer.MethodName.class)\npublic class RedisEntraIDIntegrationTests {\n  private static final Logger log = LoggerFactory.getLogger(RedisEntraIDIntegrationTests.class);\n\n  private static EntraIDTestContext testCtx;\n  private static EndpointConfig endpointConfig;\n  private static HostAndPort hnp;\n\n  private final FaultInjectionClient faultClient = new FaultInjectionClient();\n\n  @BeforeAll\n  public static void before() {\n    try {\n      testCtx = EntraIDTestContext.DEFAULT;\n      endpointConfig = Endpoints.getRedisEndpoint(\"standalone-entraid-acl\");\n      hnp = endpointConfig.getHostAndPort();\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false, \"No Redis endpoint 'standalone-entraid-acl' is configured!\");\n    }\n  }\n\n  @Test\n  public void testJedisConfig() {\n    AtomicInteger counter = new AtomicInteger(0);\n    try (MockedConstruction<EntraIDIdentityProvider> mockedConstructor = mockConstruction(\n      EntraIDIdentityProvider.class, (mock, context) -> {\n        ServicePrincipalInfo info = (ServicePrincipalInfo) context.arguments().get(0);\n\n        assertEquals(testCtx.getClientId(), info.getClientId());\n        assertEquals(testCtx.getAuthority(), info.getAuthority());\n        assertEquals(testCtx.getClientSecret(), info.getSecret());\n        assertEquals(testCtx.getRedisScopes(), context.arguments().get(1));\n        assertNotNull(mock);\n        doAnswer(invocation -> {\n          counter.incrementAndGet();\n          return new SimpleToken(\"default\", \"token1\", System.currentTimeMillis() + 5 * 60 * 1000,\n              System.currentTimeMillis(), null);\n        }).when(mock).requestToken();\n      })) {\n\n      TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n          .authority(testCtx.getAuthority()).clientId(testCtx.getClientId())\n          .secret(testCtx.getClientSecret()).scopes(testCtx.getRedisScopes()).build();\n\n      DefaultJedisClientConfig jedisConfig = DefaultJedisClientConfig.builder()\n          .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n      RedisClient jedis = RedisClient.builder()\n          .hostAndPort(new HostAndPort(\"localhost\", 6379))\n          .clientConfig(jedisConfig)\n          .build();\n      assertNotNull(jedis);\n      assertEquals(1, counter.get());\n\n    }\n  }\n\n  // T.1.1\n  // Verify authentication using Azure AD with service principals\n  @Test\n  public void withSecret_azureServicePrincipalIntegrationTest() {\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .clientId(testCtx.getClientId()).secret(testCtx.getClientSecret())\n        .authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes()).build();\n\n    DefaultJedisClientConfig jedisConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisConfig)\n        .build()) {\n      String key = UUID.randomUUID().toString();\n      jedis.set(key, \"value\");\n      assertEquals(\"value\", jedis.get(key));\n      jedis.del(key);\n    }\n  }\n\n  // T.1.1        \n  // Verify authentication using Azure AD with service principals\n  @Test\n  public void withCertificate_azureServicePrincipalIntegrationTest() {\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .clientId(testCtx.getClientId()).secret(testCtx.getClientSecret())\n        .authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes()).build();\n\n    DefaultJedisClientConfig jedisConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisConfig)\n        .build()) {\n      String key = UUID.randomUUID().toString();\n      jedis.set(key, \"value\");\n      assertEquals(\"value\", jedis.get(key));\n      jedis.del(key);\n    }\n  }\n\n  // T.2.2\n  // Test that the Redis client is not blocked/interrupted during token renewal.\n  @Test\n  public void renewalDuringOperationsTest() throws InterruptedException, ExecutionException {\n    // set the stage with consecutive get/set operations with unique keys which keeps running with a jedispooled instace, \n    // configure token manager to renew token approximately approximately every 10ms\n    // wait till token was renewed at least 10 times after initial token acquisition \n    // Additional note: Assumptions made on the time taken for token renewal and operations are based on the current implementation and may vary in future\n    // Assumptions:\n    //    - TTL of token is 2 hour\n    //    - expirationRefreshRatio is 0.000001F\n    //    - renewal delay is 7 ms each time a token is acquired\n    //    - each auth command takes 40 ms in total to complete(considering the cloud test environments)\n    //    - each auth command would need to wait for an ongoing customer operation(GET/SET/DEL) to complete, which would take another 40 ms\n    //    - each renewal happens in 40+40+7 = 87 ms\n    //    - total number of renewals would take 87 * 10 = 870 ms\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .clientId(testCtx.getClientId()).secret(testCtx.getClientSecret())\n        .authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes())\n        .expirationRefreshRatio(0.000001F).build();\n\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n    Consumer<Token> hook = mock(Consumer.class);\n    authXManager.addPostAuthenticationHook(hook);\n\n    DefaultJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(authXManager).build();\n\n    ExecutorService jedisExecutors = Executors.newFixedThreadPool(5);\n    AtomicBoolean completed = new AtomicBoolean(false);\n\n    ExecutorService runner = Executors.newSingleThreadExecutor();\n    runner.submit(() -> {\n\n      try (RedisClient jedis = RedisClient.builder()\n          .hostAndPort(hnp)\n          .clientConfig(jedisClientConfig)\n          .build()) {\n        List<Future<?>> futures = new ArrayList<>();\n        for (int i = 0; i < 5; i++) {\n          Future<?> future = jedisExecutors.submit(() -> {\n            while (!completed.get()) {\n              String key = UUID.randomUUID().toString();\n              jedis.set(key, \"value\");\n              assertEquals(\"value\", jedis.get(key));\n              jedis.del(key);\n            }\n          });\n          futures.add(future);\n        }\n        for (Future<?> task : futures) {\n          try {\n            task.get();\n          } catch (InterruptedException | ExecutionException e) {\n            e.printStackTrace();\n          }\n        }\n      }\n    });\n\n    await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(FIVE_SECONDS).untilAsserted(() -> {\n      verify(hook, atLeast(10)).accept(any());\n    });\n\n    completed.set(true);\n    runner.shutdown();\n    jedisExecutors.shutdown();\n  }\n\n  // T.3.2\n  // Verify that all existing connections can be re-authenticated when a new token is received.\n  @Test\n  public void allConnectionsReauthTest() throws InterruptedException, ExecutionException {\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .clientId(testCtx.getClientId()).secret(testCtx.getClientSecret())\n        .authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes())\n        .expirationRefreshRatio(0.000001F).build();\n\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n    authXManager = spy(authXManager);\n\n    List<Connection> connections = new ArrayList<>();\n\n    doAnswer(invocation -> {\n      Connection connection = spy((Connection) invocation.getArgument(0));\n      invocation.getArguments()[0] = connection;\n      connections.add(connection);\n      Object result = invocation.callRealMethod();\n      return result;\n    }).when(authXManager).addConnection(any(Connection.class));\n\n    DefaultJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(authXManager).build();\n\n    long startTime = System.currentTimeMillis();\n    List<Future<?>> futures = new ArrayList<>();\n    ExecutorService executor = Executors.newFixedThreadPool(5);\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisClientConfig)\n        .build()) {\n      for (int i = 0; i < 5; i++) {\n        Future<?> future = executor.submit(() -> {\n          for (; System.currentTimeMillis() - startTime < 2000;) {\n            String key = UUID.randomUUID().toString();\n            jedis.set(key, \"value\");\n            assertEquals(\"value\", jedis.get(key));\n            jedis.del(key);\n          }\n        });\n        futures.add(future);\n      }\n      for (Future<?> task : futures) {\n        task.get();\n      }\n\n      connections.forEach(conn -> {\n        verify(conn, atLeast(1)).reAuthenticate();\n      });\n      executor.shutdown();\n    }\n  }\n\n  // T.3.3\n  // Verify behavior when attempting to authenticate a single connection with an expired token.\n  @Test\n  public void connectionAuthWithExpiredTokenTest() {\n    IdentityProvider idp = new EntraIDIdentityProviderConfig(\n        new ServicePrincipalInfo(testCtx.getClientId(), testCtx.getClientSecret(),\n            testCtx.getAuthority()),\n        testCtx.getRedisScopes(), 1000).getProvider();\n\n    IdentityProvider mockIdentityProvider = mock(IdentityProvider.class);\n    AtomicReference<Token> token = new AtomicReference<>();\n    doAnswer(invocation -> {\n      if (token.get() == null) {\n        token.set(idp.requestToken());\n      }\n      return token.get();\n    }).when(mockIdentityProvider).requestToken();\n    IdentityProviderConfig idpConfig = mock(IdentityProviderConfig.class);\n    when(idpConfig.getProvider()).thenReturn(mockIdentityProvider);\n\n    TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder().tokenRequestExecTimeoutInMs(4000)\n        .identityProviderConfig(idpConfig).expirationRefreshRatio(0.000001F).build();\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n    DefaultJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(authXManager).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisClientConfig)\n        .build()) {\n      for (int i = 0; i < 50; i++) {\n        String key = UUID.randomUUID().toString();\n        jedis.set(key, \"value\");\n        assertEquals(\"value\", jedis.get(key));\n        jedis.del(key);\n      }\n\n      token.set(new SimpleToken(idp.requestToken().getUser(), \"token1\",\n          System.currentTimeMillis() - 1, System.currentTimeMillis(), null));\n\n      JedisAccessControlException aclException = assertThrows(JedisAccessControlException.class,\n        () -> {\n          for (int i = 0; i < 50; i++) {\n            String key = UUID.randomUUID().toString();\n            jedis.set(key, \"value\");\n            assertEquals(\"value\", jedis.get(key));\n            jedis.del(key);\n          }\n        });\n      String expectedError = \"WRONGPASS invalid username-password pair\";\n      assertTrue(aclException.getMessage().startsWith(expectedError),\n        \"Expected '\" + aclException.getMessage() + \"' to start with '\" + expectedError + \"'\");\n    }\n  }\n\n  // T.3.4\n  // Verify handling of reconnection and re-authentication after a network partition. (use cached token)\n  @Test\n  public void networkPartitionEvictionTest() {\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .clientId(testCtx.getClientId()).secret(testCtx.getClientSecret())\n        .authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes())\n        .expirationRefreshRatio(0.5F).build();\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n    DefaultJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(authXManager).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisClientConfig)\n        .build()) {\n      for (int i = 0; i < 5; i++) {\n        String key = UUID.randomUUID().toString();\n        jedis.set(key, \"value\");\n        assertEquals(\"value\", jedis.get(key));\n        jedis.del(key);\n      }\n\n      TriggerActionResponse actionResponse = triggerNetworkFailure();\n\n      JedisConnectionException aclException = assertThrows(JedisConnectionException.class, () -> {\n        while (!actionResponse.isCompleted(ONE_HUNDRED_MILLISECONDS, TWO_SECONDS, FIVE_SECONDS)) {\n          for (int i = 0; i < 50; i++) {\n            String key = UUID.randomUUID().toString();\n            jedis.set(key, \"value\");\n            assertEquals(\"value\", jedis.get(key));\n            jedis.del(key);\n          }\n        }\n      });\n\n      String[] expectedMessages = new String[] { \"Unexpected end of stream.\",\n          \"java.net.SocketException: Connection reset\" };\n      MatcherAssert.assertThat(aclException.getMessage(), is(in(expectedMessages)));\n      Awaitility.await().pollDelay(Durations.ONE_HUNDRED_MILLISECONDS).atMost(Durations.TWO_SECONDS)\n          .until(() -> {\n            try {\n              String key = UUID.randomUUID().toString();\n              jedis.set(key, \"value\");\n              assertEquals(\"value\", jedis.get(key));\n              jedis.del(key);\n              return true;\n            } catch (Exception e) {\n              log.debug(\"attempt to reconnect after network failure, connection has not been re-established yet:\"\n                  + e.getMessage());\n              return false;\n            }\n          });\n    }\n  }\n\n  private TriggerActionResponse triggerNetworkFailure() {\n    HashMap<String, Object> params = new HashMap<>();\n    params.put(\"bdb_id\", endpointConfig.getBdbId());\n\n    TriggerActionResponse actionResponse = null;\n    String action = \"network_failure\";\n    try {\n      log.info(\"Triggering {}\", action);\n      actionResponse = faultClient.triggerAction(action, params);\n    } catch (IOException e) {\n      fail(\"Fault Injection Server error:\" + e.getMessage());\n    }\n    log.info(\"Action id: {}\", actionResponse.getActionId());\n    return actionResponse;\n  }\n\n  @Test\n  public void withDefaultCredentials_azureCredentialsIntegrationTest() {\n    DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();\n    TokenAuthConfig tokenAuthConfig = AzureTokenAuthConfigBuilder.builder()\n        .defaultAzureCredential(credential).tokenRequestExecTimeoutInMs(2000)\n        .build();\n\n    DefaultJedisClientConfig jedisConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisConfig)\n        .build()) {\n      String key = UUID.randomUUID().toString();\n      jedis.set(key, \"value\");\n      assertEquals(\"value\", jedis.get(key));\n      jedis.del(key);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/RedisEntraIDManagedIdentityIntegrationTests.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.UUID;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;\nimport redis.clients.authentication.entraid.ManagedIdentityInfo.UserManagedIdentityType;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisClient;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\npublic class RedisEntraIDManagedIdentityIntegrationTests {\n  private static final Logger log = LoggerFactory.getLogger(RedisEntraIDIntegrationTests.class);\n\n  private static EntraIDTestContext testCtx;\n  private static EndpointConfig endpointConfig;\n  private static HostAndPort hnp;\n  private static Set<String> managedIdentityAudience = Collections\n      .singleton(\"https://redis.azure.com\");\n\n  @BeforeAll\n  public static void before() {\n    try {\n      testCtx = EntraIDTestContext.DEFAULT;\n      endpointConfig = Endpoints.getRedisEndpoint(\"standalone-entraid-acl\");\n      hnp = endpointConfig.getHostAndPort();\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false,\"No Redis endpoint 'standalone-entraid-acl' is configured!\");\n    }\n  }\n\n  // T.1.1\n  // Verify authentication using Azure AD with managed identities\n  @Test\n  public void withUserAssignedId_azureManagedIdentityIntegrationTest() {\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .userAssignedManagedIdentity(UserManagedIdentityType.OBJECT_ID,\n          testCtx.getUserAssignedManagedIdentity())\n        .scopes(managedIdentityAudience).build();\n\n    DefaultJedisClientConfig jedisConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisConfig)\n        .build()) {\n      String key = UUID.randomUUID().toString();\n      jedis.set(key, \"value\");\n      assertEquals(\"value\", jedis.get(key));\n      jedis.del(key);\n    }\n  }\n\n  // T.1.1\n  // Verify authentication using Azure AD with managed identities\n  @Test\n  public void withSystemAssignedId_azureManagedIdentityIntegrationTest() {\n    TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()\n        .systemAssignedManagedIdentity().scopes(managedIdentityAudience).build();\n\n    DefaultJedisClientConfig jedisConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(jedisConfig)\n        .build()) {\n      String key = UUID.randomUUID().toString();\n      jedis.set(key, \"value\");\n      assertEquals(\"value\", jedis.get(key));\n      jedis.del(key);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/TokenBasedAuthenticationClusterIntegrationTests.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Durations.*;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.authentication.core.IdentityProvider;\nimport redis.clients.authentication.core.IdentityProviderConfig;\nimport redis.clients.authentication.core.SimpleToken;\nimport redis.clients.authentication.core.Token;\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPoolConfig;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.RedisClusterClient;\n\npublic class TokenBasedAuthenticationClusterIntegrationTests {\n    private static final Logger log = LoggerFactory\n            .getLogger(TokenBasedAuthenticationClusterIntegrationTests.class);\n\n    private static EndpointConfig endpointConfig;\n    private static HostAndPort hnp;\n\n    @BeforeAll\n    public static void before() {\n        try {\n            endpointConfig = Endpoints.getRedisEndpoint(\"cluster\");\n            hnp = endpointConfig.getHostAndPort();\n        } catch (IllegalArgumentException e) {\n            log.warn(\"Skipping test because no Redis endpoint is configured\");\n            assumeTrue(false, \"No Redis endpoint 'cluster' is configured!\");\n        }\n    }\n\n    @Test\n    public void testClusterInitWithAuthXManager() {\n        IdentityProviderConfig idpConfig = new IdentityProviderConfig() {\n            @Override\n            public IdentityProvider getProvider() {\n                return new IdentityProvider() {\n                    @Override\n                    public Token requestToken() {\n                        return new SimpleToken(endpointConfig.getUsername(),\n                                endpointConfig.getPassword() == null ? \"\"\n                                        : endpointConfig.getPassword(),\n                                System.currentTimeMillis() + 5 * 1000, System.currentTimeMillis(),\n                                null);\n                    }\n                };\n            }\n        };\n\n        AuthXManager manager = new AuthXManager(TokenAuthConfig.builder()\n                .lowerRefreshBoundMillis(1000).tokenRequestExecTimeoutInMs(3000)\n                .identityProviderConfig(idpConfig).build());\n\n        int defaultDirections = 5;\n        JedisClientConfig config = DefaultJedisClientConfig.builder().authXManager(manager).build();\n\n        ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig();\n\n        try (RedisClusterClient jc = RedisClusterClient.builder()\n                .nodes(Collections.singleton(hnp))\n                .clientConfig(config)\n                .maxAttempts(defaultDirections)\n                .poolConfig(DEFAULT_POOL_CONFIG)\n                .build()) {\n\n            assertEquals(\"OK\", jc.set(\"foo\", \"bar\"));\n            assertEquals(\"bar\", jc.get(\"foo\"));\n            assertEquals(1, jc.del(\"foo\"));\n        }\n    }\n\n    @Test\n    public void testClusterWithReAuth() throws InterruptedException, ExecutionException {\n        IdentityProviderConfig idpConfig = new IdentityProviderConfig() {\n            @Override\n            public IdentityProvider getProvider() {\n                return new IdentityProvider() {\n                    @Override\n                    public Token requestToken() {\n                        return new SimpleToken(endpointConfig.getUsername(),\n                                endpointConfig.getPassword() == null ? \"\"\n                                        : endpointConfig.getPassword(),\n                                System.currentTimeMillis() + 5 * 1000, System.currentTimeMillis(),\n                                null);\n                    }\n                };\n            }\n        };\n\n        AuthXManager authXManager = new AuthXManager(TokenAuthConfig.builder()\n                .lowerRefreshBoundMillis(4600).tokenRequestExecTimeoutInMs(3000)\n                .identityProviderConfig(idpConfig).build());\n\n        authXManager = spy(authXManager);\n\n        List<Connection> connections = new CopyOnWriteArrayList<>();\n        doAnswer(invocation -> {\n            Connection connection = spy((Connection) invocation.getArgument(0));\n            invocation.getArguments()[0] = connection;\n            connections.add(connection);\n            return invocation.callRealMethod();\n        }).when(authXManager).addConnection(any(Connection.class));\n\n        JedisClientConfig config = DefaultJedisClientConfig.builder().authXManager(authXManager)\n                .build();\n\n        ExecutorService executorService = Executors.newFixedThreadPool(2);\n        CountDownLatch latch = new CountDownLatch(1);\n        try (RedisClusterClient jc = RedisClusterClient.builder()\n                .nodes(Collections.singleton(hnp))\n                .clientConfig(config)\n                .build()) {\n            Runnable task = () -> {\n                while (latch.getCount() > 0) {\n                    assertEquals(\"OK\", jc.set(\"foo\", \"bar\"));\n                }\n            };\n            Future<?> task1 = executorService.submit(task);\n            Future<?> task2 = executorService.submit(task);\n\n            await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(TWO_SECONDS)\n                    .until(connections::size, greaterThanOrEqualTo(2));\n\n            connections.forEach(conn -> {\n                await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(TWO_SECONDS)\n                        .untilAsserted(() -> verify(conn, atLeast(2)).reAuthenticate());\n            });\n            latch.countDown();\n            task1.get();\n            task2.get();\n        } finally {\n            latch.countDown();\n            executorService.shutdown();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/TokenBasedAuthenticationIntegrationTests.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;\nimport static org.awaitility.Durations.ONE_SECOND;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.authentication.core.IdentityProvider;\nimport redis.clients.authentication.core.IdentityProviderConfig;\nimport redis.clients.authentication.core.SimpleToken;\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\n/*  */\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Protocol.Command;\nimport redis.clients.jedis.exceptions.JedisException;\n\npublic class TokenBasedAuthenticationIntegrationTests {\n  private static final Logger log = LoggerFactory\n      .getLogger(TokenBasedAuthenticationIntegrationTests.class);\n\n  private static EndpointConfig endpointConfig;\n\n  @BeforeAll\n  public static void before() {\n    try {\n      endpointConfig = Endpoints.getRedisEndpoint(\"standalone0\");\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false);\n    }\n  }\n\n  @Test\n  public void testClientForInitialAuth() {\n    String user = \"default\";\n    String password = endpointConfig.getPassword();\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken()).thenReturn(new SimpleToken(user, password,\n        System.currentTimeMillis() + 100000, System.currentTimeMillis(), null));\n\n    IdentityProviderConfig idProviderConfig = mock(IdentityProviderConfig.class);\n    when(idProviderConfig.getProvider()).thenReturn(idProvider);\n\n    TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder()\n        .identityProviderConfig(idProviderConfig).expirationRefreshRatio(0.8F)\n        .lowerRefreshBoundMillis(10000).tokenRequestExecTimeoutInMs(1000).build();\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpointConfig.getHostAndPort())\n        .clientConfig(clientConfig)\n        .build()) {\n      jedis.get(\"key1\");\n    }\n  }\n\n  @Test\n  public void testClientReauth() {\n    String user = \"default\";\n    String password = endpointConfig.getPassword();\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken()).thenAnswer(invocation -> new SimpleToken(user, password,\n        System.currentTimeMillis() + 5000, System.currentTimeMillis(), null));\n\n    IdentityProviderConfig idProviderConfig = mock(IdentityProviderConfig.class);\n    when(idProviderConfig.getProvider()).thenReturn(idProvider);\n\n    TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder()\n        .identityProviderConfig(idProviderConfig).expirationRefreshRatio(0.8F)\n        .lowerRefreshBoundMillis(4800).tokenRequestExecTimeoutInMs(1000).build();\n\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n    authXManager = spy(authXManager);\n    List<Connection> connections = new ArrayList<>();\n    doAnswer(invocation -> {\n      Connection connection = spy((Connection) invocation.getArgument(0));\n      invocation.getArguments()[0] = connection;\n      connections.add(connection);\n      Object result = invocation.callRealMethod();\n      return result;\n    }).when(authXManager).addConnection(any(Connection.class));\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().authXManager(authXManager)\n        .build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpointConfig.getHostAndPort())\n        .clientConfig(clientConfig)\n        .build()) {\n      AtomicBoolean stop = new AtomicBoolean(false);\n      ExecutorService executor = Executors.newSingleThreadExecutor();\n      executor.submit(() -> {\n        while (!stop.get()) {\n          jedis.get(\"key1\");\n        }\n      });\n\n      for (Connection connection : connections) {\n        await().pollDelay(ONE_HUNDRED_MILLISECONDS).atMost(ONE_SECOND).untilAsserted(() -> {\n          verify(connection, atLeast(3)).reAuthenticate();\n        });\n      }\n      stop.set(true);\n      executor.shutdown();\n    }\n  }\n\n  @Test\n  public void testPubSubForInitialAuth() throws InterruptedException {\n    String user = \"default\";\n    String password = endpointConfig.getPassword();\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken()).thenReturn(new SimpleToken(user, password,\n        System.currentTimeMillis() + 100000, System.currentTimeMillis(), null));\n\n    IdentityProviderConfig idProviderConfig = mock(IdentityProviderConfig.class);\n    when(idProviderConfig.getProvider()).thenReturn(idProvider);\n\n    TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder()\n        .identityProviderConfig(idProviderConfig).expirationRefreshRatio(0.8F)\n        .lowerRefreshBoundMillis(10000).tokenRequestExecTimeoutInMs(1000).build();\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).protocol(RedisProtocol.RESP3).build();\n\n    JedisPubSub pubSub = new JedisPubSub() {\n      public void onSubscribe(String channel, int subscribedChannels) {\n        this.unsubscribe();\n      }\n    };\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpointConfig.getHostAndPort())\n        .clientConfig(clientConfig)\n        .build()) {\n      jedis.subscribe(pubSub, \"channel1\");\n    }\n  }\n\n  @Test\n  public void testJedisPubSubReauth() {\n    String user = \"default\";\n    String password = endpointConfig.getPassword();\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken()).thenAnswer(invocation -> new SimpleToken(user, password,\n        System.currentTimeMillis() + 5000, System.currentTimeMillis(), null));\n\n    IdentityProviderConfig idProviderConfig = mock(IdentityProviderConfig.class);\n    when(idProviderConfig.getProvider()).thenReturn(idProvider);\n\n    TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder()\n        .identityProviderConfig(idProviderConfig).expirationRefreshRatio(0.8F)\n        .lowerRefreshBoundMillis(4800).tokenRequestExecTimeoutInMs(1000).build();\n\n    AuthXManager authXManager = new AuthXManager(tokenAuthConfig);\n    authXManager = spy(authXManager);\n    List<Connection> connections = new ArrayList<>();\n    doAnswer(invocation -> {\n      Connection connection = spy((Connection) invocation.getArgument(0));\n      invocation.getArguments()[0] = connection;\n      connections.add(connection);\n      Object result = invocation.callRealMethod();\n      return result;\n    }).when(authXManager).addConnection(any(Connection.class));\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().authXManager(authXManager)\n        .protocol(RedisProtocol.RESP3).build();\n\n    JedisPubSub pubSub = new JedisPubSub() {\n    };\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpointConfig.getHostAndPort())\n        .clientConfig(clientConfig)\n        .build()) {\n      ExecutorService executor = Executors.newSingleThreadExecutor();\n      executor.submit(() -> {\n        jedis.subscribe(pubSub, \"channel1\");\n      });\n\n      await().pollDelay(ONE_HUNDRED_MILLISECONDS).atMost(ONE_SECOND)\n          .until(pubSub::getSubscribedChannels, greaterThan(0));\n\n      assertEquals(1, connections.size());\n      for (Connection connection : connections) {\n        await().pollDelay(ONE_HUNDRED_MILLISECONDS).atMost(ONE_SECOND).untilAsserted(() -> {\n          ArgumentCaptor<CommandArguments> captor = ArgumentCaptor.forClass(CommandArguments.class);\n\n          verify(connection, atLeast(3)).sendCommand(captor.capture());\n          assertThat(captor.getAllValues().stream()\n              .filter((item) -> item.getCommand() == Command.AUTH).count(),\n            greaterThan(3L));\n\n        });\n      }\n      pubSub.unsubscribe();\n      executor.shutdown();\n    }\n  }\n\n  @Test\n  public void testJedisPubSubWithResp2() {\n    String user = \"default\";\n    String password = endpointConfig.getPassword();\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken()).thenReturn(new SimpleToken(user, password,\n        System.currentTimeMillis() + 100000, System.currentTimeMillis(), null));\n\n    IdentityProviderConfig idProviderConfig = mock(IdentityProviderConfig.class);\n    when(idProviderConfig.getProvider()).thenReturn(idProvider);\n\n    TokenAuthConfig tokenAuthConfig = TokenAuthConfig.builder()\n        .identityProviderConfig(idProviderConfig).expirationRefreshRatio(0.8F)\n        .lowerRefreshBoundMillis(10000).tokenRequestExecTimeoutInMs(1000).build();\n\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .authXManager(new AuthXManager(tokenAuthConfig)).build();\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpointConfig.getHostAndPort())\n        .clientConfig(clientConfig)\n        .build()) {\n      JedisPubSub pubSub = new JedisPubSub() {};\n      JedisException e = assertThrows(JedisException.class,\n          () -> jedis.subscribe(pubSub, \"channel1\"));\n      assertEquals(\n          \"Blocking pub/sub operations are not supported on token-based authentication enabled connections with RESP2 protocol!\",\n          e.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/authentication/TokenBasedAuthenticationUnitTests.java",
    "content": "package redis.clients.jedis.authentication;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.*;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Durations.*;\nimport static org.hamcrest.CoreMatchers.either;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\n\nimport java.util.Collections;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedConstruction;\n\nimport redis.clients.authentication.core.IdentityProvider;\nimport redis.clients.authentication.core.IdentityProviderConfig;\nimport redis.clients.authentication.core.SimpleToken;\nimport redis.clients.authentication.core.Token;\nimport redis.clients.authentication.core.TokenAuthConfig;\nimport redis.clients.authentication.core.TokenListener;\nimport redis.clients.authentication.core.TokenManager;\nimport redis.clients.authentication.core.TokenManagerConfig;\nimport redis.clients.authentication.core.TokenManagerConfig.RetryPolicy;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\n\npublic class TokenBasedAuthenticationUnitTests {\n\n  private HostAndPort hnp = new HostAndPort(\"localhost\", 6379);\n  private EndpointConfig endpoint = new EndpointConfig(hnp, null, null, false, null);\n\n  @Test\n  public void testJedisAuthXManagerInstance() {\n    TokenManagerConfig tokenManagerConfig = mock(TokenManagerConfig.class);\n    IdentityProviderConfig identityProviderConfig = mock(IdentityProviderConfig.class);\n    IdentityProvider identityProvider = mock(IdentityProvider.class);\n\n    when(identityProviderConfig.getProvider()).thenReturn(identityProvider);\n\n    try (MockedConstruction<TokenManager> mockedConstructor = mockConstruction(TokenManager.class,\n      (mock, context) -> {\n        assertEquals(identityProvider, context.arguments().get(0));\n        assertEquals(tokenManagerConfig, context.arguments().get(1));\n      })) {\n\n      new AuthXManager(new TokenAuthConfig(tokenManagerConfig, identityProviderConfig));\n    }\n  }\n\n  @Test\n  public void withExpirationRefreshRatio_testJedisAuthXManagerTriggersEvict() throws Exception {\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken())\n        .thenReturn(new SimpleToken(\"default\", \"password\", System.currentTimeMillis() + 1000,\n            System.currentTimeMillis(), Collections.singletonMap(\"oid\", \"default\")));\n\n    TokenManager tokenManager = new TokenManager(idProvider,\n        new TokenManagerConfig(0.4F, 100, 1000, new RetryPolicy(1, 1)));\n    AuthXManager jedisAuthXManager = new AuthXManager(tokenManager);\n\n    AtomicInteger numberOfEvictions = new AtomicInteger(0);\n\n    try (ConnectionPool pool = new ConnectionPool(hnp,\n        endpoint.getClientConfigBuilder().authXManager(jedisAuthXManager).build()) {\n      @Override\n      public void evict() throws Exception {\n        numberOfEvictions.incrementAndGet();\n        super.evict();\n      }\n    }) {\n      await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(FIVE_HUNDRED_MILLISECONDS)\n          .until(numberOfEvictions::get, Matchers.greaterThanOrEqualTo(1));\n    }\n  }\n\n  public void withLowerRefreshBounds_testJedisAuthXManagerTriggersEvict() throws Exception {\n\n    IdentityProvider idProvider = mock(IdentityProvider.class);\n    when(idProvider.requestToken())\n        .thenReturn(new SimpleToken(\"default\", \"password\", System.currentTimeMillis() + 1000,\n            System.currentTimeMillis(), Collections.singletonMap(\"oid\", \"default\")));\n\n    TokenManager tokenManager = new TokenManager(idProvider,\n        new TokenManagerConfig(0.9F, 600, 1000, new RetryPolicy(1, 1)));\n    AuthXManager jedisAuthXManager = new AuthXManager(tokenManager);\n\n    AtomicInteger numberOfEvictions = new AtomicInteger(0);\n\n    try (ConnectionPool pool = new ConnectionPool(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().authXManager(jedisAuthXManager).build()) {\n      @Override\n      public void evict() throws Exception {\n        numberOfEvictions.incrementAndGet();\n        super.evict();\n      }\n    }) {\n      await().pollInterval(ONE_HUNDRED_MILLISECONDS).atMost(FIVE_HUNDRED_MILLISECONDS)\n          .until(numberOfEvictions::get, Matchers.greaterThanOrEqualTo(1));\n    }\n  }\n\n  public static class TokenManagerConfigWrapper extends TokenManagerConfig {\n    int lower;\n    float ratio;\n\n    public TokenManagerConfigWrapper() {\n      super(0, 0, 0, null);\n    }\n\n    @Override\n    public int getLowerRefreshBoundMillis() {\n      return lower;\n    }\n\n    @Override\n    public float getExpirationRefreshRatio() {\n      return ratio;\n    }\n\n    @Override\n    public RetryPolicy getRetryPolicy() {\n      return new RetryPolicy(1, 1);\n    }\n  }\n\n  @Test\n  public void testCalculateRenewalDelay() {\n    long delay = 0;\n    long duration = 0;\n    long issueDate;\n    long expireDate;\n\n    TokenManagerConfigWrapper config = new TokenManagerConfigWrapper();\n    TokenManager manager = new TokenManager(() -> null, config);\n\n    duration = 5000;\n    config.lower = 2000;\n    config.ratio = 0.5F;\n    issueDate = System.currentTimeMillis();\n    expireDate = issueDate + duration;\n\n    delay = manager.calculateRenewalDelay(expireDate, issueDate);\n\n    assertThat(delay,\n      lessThanOrEqualTo(Math.min(duration - config.lower, (long) (duration * config.ratio))));\n\n    duration = 10000;\n    config.lower = 8000;\n    config.ratio = 0.2F;\n    issueDate = System.currentTimeMillis();\n    expireDate = issueDate + duration;\n\n    delay = manager.calculateRenewalDelay(expireDate, issueDate);\n\n    assertThat(delay,\n      lessThanOrEqualTo(Math.min(duration - config.lower, (long) (duration * config.ratio))));\n\n    duration = 10000;\n    config.lower = 10000;\n    config.ratio = 0.2F;\n    issueDate = System.currentTimeMillis();\n    expireDate = issueDate + duration;\n\n    delay = manager.calculateRenewalDelay(expireDate, issueDate);\n\n    assertEquals(0, delay);\n\n    duration = 0;\n    config.lower = 5000;\n    config.ratio = 0.2F;\n    issueDate = System.currentTimeMillis();\n    expireDate = issueDate + duration;\n\n    delay = manager.calculateRenewalDelay(expireDate, issueDate);\n\n    assertEquals(0, delay);\n\n    duration = 10000;\n    config.lower = 1000;\n    config.ratio = 0.00001F;\n    issueDate = System.currentTimeMillis();\n    expireDate = issueDate + duration;\n\n    delay = manager.calculateRenewalDelay(expireDate, issueDate);\n\n    assertEquals(0, delay);\n\n    duration = 10000;\n    config.lower = 1000;\n    config.ratio = 0.0001F;\n    issueDate = System.currentTimeMillis();\n    expireDate = issueDate + duration;\n\n    delay = manager.calculateRenewalDelay(expireDate, issueDate);\n\n    assertThat(delay, either(is(0L)).or(is(1L)));\n  }\n\n  @Test\n  public void testAuthXManagerReceivesNewToken()\n      throws InterruptedException, ExecutionException, TimeoutException {\n\n    IdentityProvider identityProvider = () -> new SimpleToken(\"user1\", \"tokenVal\",\n        System.currentTimeMillis() + 5 * 1000, System.currentTimeMillis(),\n        Collections.singletonMap(\"oid\", \"user1\"));\n\n    TokenManager tokenManager = new TokenManager(identityProvider,\n        new TokenManagerConfig(0.7F, 200, 2000, new RetryPolicy(1, 1)));\n\n    AuthXManager manager = spy(new AuthXManager(tokenManager));\n\n    final Token[] tokenHolder = new Token[1];\n    doAnswer(invocation -> {\n      Object[] args = invocation.getArguments();\n      tokenHolder[0] = (Token) args[0];\n      return null;\n    }).when(manager).authenticateConnections(any());\n\n    try {\n      manager.start();\n      assertEquals(tokenHolder[0].getValue(), \"tokenVal\");\n    } finally {\n      manager.stop();\n    }\n  }\n\n  @Test\n  public void testBlockForInitialTokenWhenException() {\n    String exceptionMessage = \"Test exception from identity provider!\";\n    IdentityProvider identityProvider = () -> {\n      throw new RuntimeException(exceptionMessage);\n    };\n\n    TokenManager tokenManager = new TokenManager(identityProvider,\n        new TokenManagerConfig(0.7F, 200, 2000, new TokenManagerConfig.RetryPolicy(5, 100)));\n\n    AuthXManager manager = new AuthXManager(tokenManager);\n    JedisAuthenticationException e = assertThrows(JedisAuthenticationException.class, manager::start);\n\n    assertEquals(exceptionMessage, e.getCause().getCause().getMessage());\n  }\n\n  @Test\n  public void testBlockForInitialTokenWhenHangs() {\n    String exceptionMessage = \"AuthXManager failed to start!\";\n    CountDownLatch latch = new CountDownLatch(1);\n    IdentityProvider identityProvider = () -> {\n      try {\n        latch.await();\n      } catch (InterruptedException e) {\n        // Ignore\n      }\n      return null;\n    };\n\n    TokenManager tokenManager = new TokenManager(identityProvider,\n        new TokenManagerConfig(0.7F, 200, 1000, new TokenManagerConfig.RetryPolicy(2, 100)));\n\n    AuthXManager manager = new AuthXManager(tokenManager);\n    JedisAuthenticationException e = assertThrows(JedisAuthenticationException.class, manager::start);\n\n    latch.countDown();\n    assertEquals(exceptionMessage, e.getMessage());\n  }\n\n  @Test\n  public void testTokenManagerWithFailingTokenRequest()\n      throws InterruptedException, ExecutionException, TimeoutException {\n    int numberOfRetries = 5;\n    CountDownLatch requesLatch = new CountDownLatch(numberOfRetries);\n\n    IdentityProvider identityProvider = mock(IdentityProvider.class);\n    when(identityProvider.requestToken()).thenAnswer(invocation -> {\n      requesLatch.countDown();\n      if (requesLatch.getCount() > 0) {\n        throw new RuntimeException(\"Test exception from identity provider!\");\n      }\n      return new SimpleToken(\"user1\", \"tokenValX\", System.currentTimeMillis() + 50 * 1000,\n          System.currentTimeMillis(), Collections.singletonMap(\"oid\", \"user1\"));\n    });\n\n    ArgumentCaptor<Token> argument = ArgumentCaptor.forClass(Token.class);\n\n    TokenManager tokenManager = new TokenManager(identityProvider, new TokenManagerConfig(0.7F, 200,\n        2000, new TokenManagerConfig.RetryPolicy(numberOfRetries - 1, 100)));\n\n    try {\n      TokenListener listener = mock(TokenListener.class);\n      tokenManager.start(listener, false);\n      requesLatch.await();\n      await().pollDelay(ONE_HUNDRED_MILLISECONDS).atMost(FIVE_HUNDRED_MILLISECONDS)\n          .untilAsserted(() -> verify(listener).onTokenRenewed(argument.capture()));\n      verify(identityProvider, times(numberOfRetries)).requestToken();\n      verify(listener, never()).onError(any());\n      assertEquals(\"tokenValX\", argument.getValue().getValue());\n    } finally {\n      tokenManager.stop();\n    }\n  }\n\n  @Test\n  public void testTokenManagerWithHangingTokenRequest()\n      throws InterruptedException, ExecutionException, TimeoutException {\n    int sleepDuration = 200;\n    int executionTimeout = 100;\n    int tokenLifetime = 50 * 1000;\n    int numberOfRetries = 5;\n    CountDownLatch requesLatch = new CountDownLatch(numberOfRetries);\n\n    IdentityProvider identityProvider = () -> {\n      requesLatch.countDown();\n      if (requesLatch.getCount() > 0) {\n        try {\n          Thread.sleep(sleepDuration);\n        } catch (InterruptedException e) {\n          // Ignore\n        }\n        return null;\n      }\n      return new SimpleToken(\"user1\", \"tokenValX\", System.currentTimeMillis() + tokenLifetime,\n          System.currentTimeMillis(), Collections.singletonMap(\"oid\", \"user1\"));\n    };\n\n    TokenManager tokenManager = new TokenManager(identityProvider, new TokenManagerConfig(0.7F, 200,\n        executionTimeout, new TokenManagerConfig.RetryPolicy(numberOfRetries, 100)));\n\n    AuthXManager manager = spy(new AuthXManager(tokenManager));\n    try {\n      AuthXEventListener listener = mock(AuthXEventListener.class);\n      manager.setListener(listener);\n      manager.start();\n      requesLatch.await();\n      verify(listener, never()).onIdentityProviderError(any());\n      verify(listener, never()).onConnectionAuthenticationError(any());\n\n      await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {\n        verify(manager, times(1)).authenticateConnections(any());\n      });\n    } finally {\n      manager.stop();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/CRC16Benchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.util.Calendar;\n\nimport redis.clients.jedis.util.JedisClusterCRC16;\n\npublic class CRC16Benchmark {\n\n  private static final int TOTAL_OPERATIONS = 100000000;\n\n  private static String[] TEST_SET = {\"\", \"123456789\", \"sfger132515\",\n    \"hae9Napahngaikeethievubaibogiech\", \"AAAAAAAAAAAAAAAAAAAAAA\", \"Hello, World!\"};\n\n  public static void main(String[] args) {\n    long begin = Calendar.getInstance().getTimeInMillis();\n\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      JedisClusterCRC16.getSlot(TEST_SET[n % TEST_SET.length]);\n    }\n\n    long elapsed = Calendar.getInstance().getTimeInMillis() - begin;\n\n    System.out.println(((1000 * TOTAL_OPERATIONS) / elapsed) + \" ops\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/GetSetBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.util.Calendar;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.Endpoints;\n\npublic class GetSetBenchmark {\n\n  private static EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  private static final int TOTAL_OPERATIONS = 100000;\n\n  public static void main(String[] args) throws UnknownHostException, IOException {\n    Jedis jedis = new Jedis(endpoint.getHostAndPort());\n    jedis.connect();\n    jedis.auth(endpoint.getPassword());\n    jedis.flushAll();\n\n    long begin = Calendar.getInstance().getTimeInMillis();\n\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      String key = \"foo\" + n;\n      jedis.set(key, \"bar\" + n);\n      jedis.get(key);\n    }\n\n    long elapsed = Calendar.getInstance().getTimeInMillis() - begin;\n\n    jedis.disconnect();\n\n    System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + \" ops\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/PipelinedGetSetBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.util.Calendar;\n\nimport redis.clients.jedis.*;\n\npublic class PipelinedGetSetBenchmark {\n\n  private static EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  private static final int TOTAL_OPERATIONS = 200000;\n\n  public static void main(String[] args) throws UnknownHostException, IOException {\n    Jedis jedis = new Jedis(endpoint.getHostAndPort());\n    jedis.connect();\n    jedis.auth(endpoint.getPassword());\n    jedis.flushAll();\n\n    long begin = Calendar.getInstance().getTimeInMillis();\n\n    Pipeline p = jedis.pipelined();\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      String key = \"foo\" + n;\n      p.set(key, \"bar\" + n);\n      p.get(key);\n    }\n    p.sync();\n\n    long elapsed = Calendar.getInstance().getTimeInMillis() - begin;\n\n    jedis.disconnect();\n\n    System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + \" ops\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/PoolBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.jedis.*;\n\npublic class PoolBenchmark {\n\n  private static EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  private static final int TOTAL_OPERATIONS = 100000;\n\n  public static void main(String[] args) throws Exception {\n    Jedis j = new Jedis(endpoint.getHostAndPort());\n    j.connect();\n    j.auth(endpoint.getPassword());\n    j.flushAll();\n    j.disconnect();\n    long t = System.currentTimeMillis();\n    // withoutPool();\n    withPool();\n    long elapsed = System.currentTimeMillis() - t;\n    System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + \" ops\");\n  }\n\n  private static void withPool() throws Exception {\n    final JedisPool pool = new JedisPool(new GenericObjectPoolConfig<Jedis>(), endpoint.getHost(),\n        endpoint.getPort(), 2000, endpoint.getPassword());\n    List<Thread> tds = new ArrayList<Thread>();\n\n    final AtomicInteger ind = new AtomicInteger();\n    for (int i = 0; i < 50; i++) {\n      Thread hj = new Thread(new Runnable() {\n        public void run() {\n          for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) {\n            try {\n              Jedis j = pool.getResource();\n              final String key = \"foo\" + i;\n              j.set(key, key);\n              j.get(key);\n              j.close();\n            } catch (Exception e) {\n              e.printStackTrace();\n            }\n          }\n        }\n      });\n      tds.add(hj);\n      hj.start();\n    }\n\n    for (Thread t : tds) {\n      t.join();\n    }\n\n    pool.destroy();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/ProtocolBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.TimeUnit;\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.util.RedisInputStream;\nimport redis.clients.jedis.util.RedisOutputStream;\n\n/**\n * Copyright (c) 2014\n */\npublic class ProtocolBenchmark {\n\n  private static final int TOTAL_OPERATIONS = 500000;\n\n  public static void main(String[] args) throws Exception, IOException {\n    long total = 0;\n    for (int at = 0; at != 10; ++at) {\n      long elapsed = measureInputMulti();\n      long ops = ((1000 * 2 * TOTAL_OPERATIONS) / TimeUnit.NANOSECONDS.toMillis(elapsed));\n      if (at >= 5) {\n        total += ops;\n      }\n    }\n    System.out.println((total / 5) + \" avg\");\n\n    total = 0;\n    for (int at = 0; at != 10; ++at) {\n      long elapsed = measureInputStatus();\n      long ops = ((1000 * 2 * TOTAL_OPERATIONS) / TimeUnit.NANOSECONDS.toMillis(elapsed));\n      if (at >= 5) {\n        total += ops;\n      }\n    }\n\n    System.out.println((total / 5) + \" avg\");\n\n    total = 0;\n    for (int at = 0; at != 10; ++at) {\n      long elapsed = measureCommand();\n      long ops = ((1000 * 2 * TOTAL_OPERATIONS) / TimeUnit.NANOSECONDS.toMillis(elapsed));\n      if (at >= 5) {\n        total += ops;\n      }\n    }\n\n    System.out.println((total / 5) + \" avg\");\n  }\n\n  private static long measureInputMulti() throws Exception {\n    long duration = 0;\n\n    InputStream is = new ByteArrayInputStream(\n        \"*4\\r\\n$3\\r\\nfoo\\r\\n$13\\r\\nbarbarbarfooz\\r\\n$5\\r\\nHello\\r\\n$5\\r\\nWorld\\r\\n\".getBytes());\n\n    RedisInputStream in = new RedisInputStream(is);\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      long start = System.nanoTime();\n      Protocol.read(in);\n      duration += (System.nanoTime() - start);\n      in.reset();\n    }\n\n    return duration;\n  }\n\n  private static long measureInputStatus() throws Exception {\n    long duration = 0;\n\n    InputStream is = new ByteArrayInputStream(\"+OK\\r\\n\".getBytes());\n\n    RedisInputStream in = new RedisInputStream(is);\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      long start = System.nanoTime();\n      Protocol.read(in);\n      duration += (System.nanoTime() - start);\n      in.reset();\n    }\n\n    return duration;\n  }\n\n  private static long measureCommand() throws Exception {\n    long duration = 0;\n\n    byte[] KEY = \"123456789\".getBytes();\n    byte[] VAL = \"FooBar\".getBytes();\n\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      RedisOutputStream out = new RedisOutputStream(new ByteArrayOutputStream(8192));\n      long start = System.nanoTime();\n//      Protocol.sendCommand(out, Protocol.Command.SET, KEY, VAL);\n      Protocol.sendCommand(out, new CommandArguments(Protocol.Command.SET).key(KEY).add(VAL));\n      duration += (System.nanoTime() - start);\n    }\n\n    return duration;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/RedisClientBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport redis.clients.jedis.*;\n\npublic class RedisClientBenchmark {\n\n  private static EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  private static final int TOTAL_OPERATIONS = 100000;\n\n  public static void main(String[] args) throws Exception {\n    try (Jedis j = new Jedis(endpoint.getHost(), endpoint.getPort())) {\n      j.auth(endpoint.getPassword());\n      j.flushAll();\n      j.disconnect();\n    }\n    long t = System.currentTimeMillis();\n    withPool();\n    long elapsed = System.currentTimeMillis() - t;\n    System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + \" ops\");\n  }\n\n  private static void withPool() throws Exception {\n    final RedisClient j = RedisClient.builder()\n        .hostAndPort(endpoint.getHost(), endpoint.getPort())\n        .clientConfig(DefaultJedisClientConfig.builder()\n            .password(endpoint.getPassword())\n            .build())\n        .build();\n    List<Thread> tds = new ArrayList<>();\n\n    final AtomicInteger ind = new AtomicInteger();\n    for (int i = 0; i < 50; i++) {\n      Thread hj = new Thread(new Runnable() {\n        @Override\n        public void run() {\n          for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) {\n            try {\n              final String key = \"foo\" + i;\n              j.set(key, key);\n              j.get(key);\n            } catch (Exception e) {\n              e.printStackTrace();\n            }\n          }\n        }\n      });\n      tds.add(hj);\n      hj.start();\n    }\n\n    for (Thread t : tds) {\n      t.join();\n    }\n\n    j.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/RedisClientCSCBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.TestCache;\n\npublic class RedisClientCSCBenchmark {\n\n    private static EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n    private static final int TOTAL_OPERATIONS = 1000000;\n    private static final int NUMBER_OF_THREADS = 50;\n\n    public static void main(String[] args) throws Exception {\n\n        try (Jedis j = new Jedis(endpoint.getHost(), endpoint.getPort())) {\n            j.auth(endpoint.getPassword());\n            j.flushAll();\n            j.disconnect();\n        }\n\n        int totalRounds = 50;\n        long withoutCache = 0;\n        long withCache = 0;\n\n        for (int i = 0; i < totalRounds; i++) {\n            withoutCache += runBenchmark(null);\n            withCache += runBenchmark(new TestCache());\n        }\n        for (int i = 0; i < totalRounds; i++) {\n        }\n        System.out.println(String.format(\"after %d rounds withoutCache: %d ms,  withCache: %d ms\", totalRounds,\n                withoutCache, withCache));\n        System.out.println(\"execution time ratio: \" + (double) withCache / withoutCache);\n    }\n\n    private static long runBenchmark(Cache cache) throws Exception {\n        long start = System.currentTimeMillis();\n        withPool(cache);\n        long elapsed = System.currentTimeMillis() - start;\n        System.out.println(String.format(\"%s round elapsed: %d ms\", cache == null ? \"no cache\" : \"cached\", elapsed));\n        return elapsed;\n    }\n\n    private static void withPool(Cache cache) throws Exception {\n        JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3)\n                .password(endpoint.getPassword()).build();\n        List<Thread> tds = new ArrayList<>();\n        final AtomicInteger ind = new AtomicInteger();\n        try (RedisClient jedis = RedisClient.builder()\n                .hostAndPort(endpoint.getHostAndPort())\n                .clientConfig(config)\n                .cache(cache)\n                .build()) {\n            for (int i = 0; i < NUMBER_OF_THREADS; i++) {\n                Thread hj = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) {\n                            try {\n                                final String key = \"foo\" + i;\n                                jedis.set(key, key);\n                                jedis.get(key);\n                            } catch (Exception e) {\n                                e.printStackTrace();\n                                throw e;\n                            }\n                        }\n                    }\n                });\n                tds.add(hj);\n                hj.start();\n            }\n\n            for (Thread t : tds) {\n                t.join();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/benchmark/SafeEncoderBenchmark.java",
    "content": "package redis.clients.jedis.benchmark;\n\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.util.Calendar;\n\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class SafeEncoderBenchmark {\n\n  private static final int TOTAL_OPERATIONS = 10000000;\n\n  public static void main(String[] args) throws UnknownHostException, IOException {\n    long begin = Calendar.getInstance().getTimeInMillis();\n\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      SafeEncoder.encode(\"foo bar!\");\n    }\n\n    long elapsed = Calendar.getInstance().getTimeInMillis() - begin;\n\n    System.out.println(((1000 * TOTAL_OPERATIONS) / elapsed) + \" ops to build byte[]\");\n\n    begin = Calendar.getInstance().getTimeInMillis();\n\n    byte[] bytes = \"foo bar!\".getBytes();\n    for (int n = 0; n <= TOTAL_OPERATIONS; n++) {\n      SafeEncoder.encode(bytes);\n    }\n\n    elapsed = Calendar.getInstance().getTimeInMillis() - begin;\n\n    System.out.println(((1000 * TOTAL_OPERATIONS) / elapsed) + \" ops to build Strings\");\n\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/ClientBuilderTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.*;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Collections;\n\nimport redis.clients.jedis.util.ReflectionTestUtil;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.FTSearchParams;\n\n@ExtendWith(MockitoExtension.class)\nclass ClientBuilderTest {\n\n  @Mock\n  CommandExecutor exec;\n  @Mock\n  ConnectionProvider provider;\n  @Captor\n  ArgumentCaptor<CommandObject<?>> cap;\n\n  private static List<String> argsToStrings(CommandObject<?> co) {\n    List<String> out = new ArrayList<>();\n    for (Rawable r : co.getArguments()) {\n      out.add(new String(r.getRaw(), StandardCharsets.UTF_8));\n    }\n    return out;\n  }\n\n  @Test\n  void appliesKeyPreprocessorToCommandObjects() {\n    try (RedisClient client = RedisClient.builder().commandExecutor(exec)\n        .connectionProvider(provider).keyPreProcessor(k -> \"prefix:\" + k).build()) {\n\n      client.set(\"key\", \"v\");\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()), contains(\"SET\", \"prefix:key\", \"v\"));\n  }\n\n  @Test\n  void appliesJsonObjectMapper() {\n    JsonObjectMapper mapper = mock(JsonObjectMapper.class);\n    when(mapper.toJson(any())).thenReturn(\"JSON:{a=1}\");\n\n    try (RedisClient client = RedisClient.builder().commandExecutor(exec)\n        .connectionProvider(provider).jsonObjectMapper(mapper).build()) {\n\n      client.jsonSetWithEscape(\"k\", Path2.ROOT_PATH, Collections.singletonMap(\"a\", 1));\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()), contains(\"JSON.SET\", \"k\", \"$\", \"JSON:{a=1}\"));\n  }\n\n  @Test\n  void appliesSearchDialect() {\n    try (RedisClient client = RedisClient.builder().commandExecutor(exec)\n        .connectionProvider(provider).searchDialect(3).build()) {\n\n      client.ftSearch(\"idx\", \"q\", new FTSearchParams());\n    }\n    verify(exec, atLeastOnce()).executeCommand(cap.capture());\n    List<String> args = argsToStrings(cap.getValue());\n    assertThat(args, contains(\"FT.SEARCH\", \"idx\", \"q\", \"DIALECT\", \"3\"));\n  }\n\n  @Test\n  void cacheRequiresRESP3() {\n    Cache cache = mock(Cache.class);\n\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> RedisClient\n        .builder().commandExecutor(exec).connectionProvider(provider).cache(cache).build(),\n      \"Cache requires RESP3\");\n\n    assertThat(ex.getMessage(), containsString(\"Client-side caching is only supported with RESP3\"));\n\n  }\n\n  @Test\n  void standaloneValidateHostPortRequired() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClient.builder().hostAndPort(null).build());\n\n    assertThat(ex.getMessage(), containsString(\"Either URI or host/port must be specified\"));\n  }\n\n  @Test\n  void sentinelValidateMasterAndSentinels() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisSentinelClient.builder().build());\n    assertThat(ex.getMessage(), containsString(\"Master name is required for Sentinel mode\"));\n\n    ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisSentinelClient.builder().masterName(\"mymaster\").build());\n    assertThat(ex.getMessage(),\n      containsString(\"At least one sentinel must be specified for Sentinel mode\"));\n\n    ex = assertThrows(IllegalArgumentException.class, () -> RedisSentinelClient.builder()\n        .masterName(\"mymaster\").sentinels(Collections.emptySet()).build());\n    assertThat(ex.getMessage(),\n      containsString(\"At least one sentinel must be specified for Sentinel mode\"));\n  }\n\n  @Test\n  void setWithValueCondition() {\n    try (JedisPooled client = JedisPooled.builder().commandExecutor(exec)\n        .connectionProvider(provider).build()) {\n\n      client.set(\"key\", \"value\",\n        SetParams.setParams().xx().condition(CompareCondition.valueEq(\"oldValue\")));\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()),\n      contains(\"SET\", \"key\", \"value\", \"IFEQ\", \"oldValue\", \"XX\"));\n  }\n\n  @Test\n  void setWithDigestCondition() {\n    try (JedisPooled client = JedisPooled.builder().commandExecutor(exec)\n        .connectionProvider(provider).build()) {\n\n      client.set(\"key\", \"value\", SetParams.setParams().nx().ex(100)\n          .condition(CompareCondition.digestEq(\"0123456789abcdef\")));\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()),\n      contains(\"SET\", \"key\", \"value\", \"IFDEQ\", \"0123456789abcdef\", \"NX\", \"EX\", \"100\"));\n  }\n\n  @Test\n  void delexWithValueCondition() {\n    when(exec.executeCommand(any())).thenReturn(1L);\n\n    try (JedisPooled client = JedisPooled.builder().commandExecutor(exec)\n        .connectionProvider(provider).build()) {\n\n      client.delex(\"key\", CompareCondition.valueNe(\"value\"));\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()), contains(\"DELEX\", \"key\", \"IFNE\", \"value\"));\n  }\n\n  @Test\n  void delexWithDigestCondition() {\n    when(exec.executeCommand(any())).thenReturn(1L);\n\n    try (JedisPooled client = JedisPooled.builder().commandExecutor(exec)\n        .connectionProvider(provider).build()) {\n\n      client.delex(\"key\", CompareCondition.digestNe(\"fedcba9876543210\"));\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()),\n      contains(\"DELEX\", \"key\", \"IFDNE\", \"fedcba9876543210\"));\n  }\n\n  @Test\n  void digestKey() {\n    try (JedisPooled client = JedisPooled.builder().commandExecutor(exec)\n        .connectionProvider(provider).build()) {\n\n      client.digestKey(\"key\");\n    }\n    verify(exec).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()), contains(\"DIGEST\", \"key\"));\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Test\n  void fromURI_withInvalidURI_throwsException() {\n    // URI with credentials\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClient.builder().fromURI(\"uriuser:uripass@localhost:6379\"));\n\n    assertThat(ex.getMessage(), containsString(\"Invalid Redis URI\"));\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  void fromUri_existingClientConfigIsPreserved() {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().user(\"testuser\")\n        .password(\"testpass\").connectionTimeoutMillis(5000).build();\n\n    // URI without credentials - should preserve config credentials\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder().clientConfig(config)\n        .fromURI(\"redis://localhost:6379\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertThat(resultConfig.getUser(), equalTo(\"testuser\"));\n    assertThat(resultConfig.getPassword(), equalTo(\"testpass\"));\n    assertThat(resultConfig.getConnectionTimeoutMillis(), equalTo(5000));\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  void fromURI_withCredentials_overridesClientConfigCredentials() {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().user(\"olduser\")\n        .password(\"oldpass\").build();\n\n    // URI with credentials should override config credentials\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder().clientConfig(config)\n        .fromURI(\"redis://newuser:newpass@localhost:6379\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertThat(resultConfig.getUser(), equalTo(\"newuser\"));\n    assertThat(resultConfig.getPassword(), equalTo(\"newpass\"));\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  void fromURI_ThenClientConfig_configWins() {\n    // URI with credentials\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder()\n        .fromURI(\"redis://uriuser:uripass@localhost:6379\").clientConfig(\n          DefaultJedisClientConfig.builder().user(\"configuser\").password(\"configpass\").build());\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertThat(resultConfig.getUser(), equalTo(\"configuser\"));\n    assertThat(resultConfig.getPassword(), equalTo(\"configpass\"));\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  void fromUri_multipleFromURICalls_lastWins() {\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder()\n        .fromURI(\"redis://user1:pass1@localhost:6379/1\")\n        .fromURI(\"redis://user2:pass2@localhost:6380/2\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertThat(resultConfig.getUser(), equalTo(\"user2\"));\n    assertThat(resultConfig.getPassword(), equalTo(\"pass2\"));\n    assertThat(resultConfig.getDatabase(), equalTo(2));\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  void fromUri_partialURIOverride_preservesNonURIValues() {\n    // Real-world scenario from issue #4416\n    JedisClientConfig config = DefaultJedisClientConfig.builder().user(\"mark\").password(\"secret\")\n        .connectionTimeoutMillis(5000).socketTimeoutMillis(3000).build();\n\n    // URI without credentials, only host/port\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder().clientConfig(config)\n        .fromURI(\"redis://localhost:6379\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    // Should preserve credentials and timeouts from config\n    assertThat(resultConfig.getUser(), equalTo(\"mark\"));\n    assertThat(resultConfig.getPassword(), equalTo(\"secret\"));\n    assertThat(resultConfig.getConnectionTimeoutMillis(), equalTo(5000));\n    assertThat(resultConfig.getSocketTimeoutMillis(), equalTo(3000));\n  }\n\n  @Test\n  void fromUri_withProtocol_OverridesClientConfig() {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP2)\n        .build();\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder().clientConfig(config)\n        .fromURI(\"redis://localhost:6379?protocol=3\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertThat(resultConfig.getRedisProtocol(), equalTo(RedisProtocol.RESP3));\n  }\n\n  @Test\n  void fromUri_noProtocol_PreservesClientConfig() {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP2)\n        .build();\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder().clientConfig(config)\n        .fromURI(\"redis://localhost:6379\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertThat(resultConfig.getRedisProtocol(), equalTo(RedisProtocol.RESP2));\n  }\n\n  @Test\n  void fromUri_noProtocol_preservesDefault() {\n    StandaloneClientBuilder<RedisClient> builder = RedisClient.builder()\n        .fromURI(\"redis://localhost:6379\");\n\n    JedisClientConfig resultConfig = getClientConfig(builder);\n    assertNull(resultConfig.getRedisProtocol());\n  }\n\n  /**\n   * Helper method to access the protected clientConfig field from the builder using reflection.\n   */\n  private JedisClientConfig getClientConfig(\n      redis.clients.jedis.builders.AbstractClientBuilder<?, ?> builder) {\n    return ReflectionTestUtil.getField(builder, \"clientConfig\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/ClusterClientBuilderTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport static org.mockito.Mockito.atLeastOnce;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.MockedConstruction;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.RedisClusterClient;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.executors.ClusterCommandExecutor;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.search.FTSearchParams;\n\n@ExtendWith(MockitoExtension.class)\nclass ClusterClientBuilderTest {\n\n  @Mock\n  CommandExecutor exec;\n  @Mock\n  ConnectionProvider provider;\n  @Captor\n  ArgumentCaptor<CommandObject<?>> cap;\n\n  private static List<String> argsToStrings(CommandObject<?> co) {\n    List<String> out = new ArrayList<>();\n    for (Rawable r : co.getArguments()) {\n      out.add(new String(r.getRaw(), StandardCharsets.UTF_8));\n    }\n    return out;\n  }\n\n  private static Set<HostAndPort> someNodes() {\n    Set<HostAndPort> nodes = new HashSet<>();\n    nodes.add(new HostAndPort(\"127.0.0.1\", 7000));\n    return nodes;\n  }\n\n  @Test\n  void clusterNodesEmptyShouldThrow() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClusterClient.builder().nodes(new HashSet<>()).build());\n\n    assertThat(ex.getMessage(),\n      containsString(\"At least one cluster node must be specified for cluster mode\"));\n  }\n\n  @Test\n  void negativeMaxTotalRetriesDurationShouldThrow() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClusterClient.builder().nodes(someNodes())\n          .maxTotalRetriesDuration(Duration.ofMillis(-1)).build());\n\n    assertThat(ex.getMessage(),\n      containsString(\"Max total retries duration cannot be negative for cluster mode\"));\n  }\n\n  @Test\n  void negativeTopologyRefreshShouldThrow() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClusterClient.builder().nodes(someNodes())\n          .topologyRefreshPeriod(Duration.ofMillis(-1)).build());\n\n    assertThat(ex.getMessage(),\n      containsString(\"Topology refresh period cannot be negative for cluster mode\"));\n  }\n\n  @Test\n  void buildWithPositiveDurationsAndConfig_usesProvidedExecAndProvider() {\n    try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n        .clientConfig(redis.clients.jedis.DefaultJedisClientConfig.builder().build()).maxAttempts(3)\n        .maxTotalRetriesDuration(Duration.ofMillis(10)).topologyRefreshPeriod(Duration.ofMillis(50))\n        .connectionProvider(provider).commandExecutor(exec).build()) {\n\n      client.ping();\n    }\n    verify(exec, atLeastOnce()).executeCommand(cap.capture());\n    assertThat(argsToStrings(cap.getValue()).get(0), containsString(\"PING\"));\n  }\n\n  @Test\n  void nodesNotProvidedShouldThrow() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClusterClient.builder().build());\n\n    assertThat(ex.getMessage(),\n      containsString(\"At least one cluster node must be specified for cluster mode\"));\n  }\n\n  @Test\n  void searchDialectZeroShouldThrow() {\n    IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n      () -> RedisClusterClient.builder().searchDialect(0));\n\n    assertThat(ex.getMessage(), containsString(\"DIALECT=0 cannot be set.\"));\n  }\n\n  @Test\n  void keyPreprocessorAppliedInCluster() {\n    try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n        .connectionProvider(provider).commandExecutor(exec).keyPreProcessor(k -> \"prefix:\" + k)\n        .build()) {\n\n      client.set(\"k\", \"v\");\n      verify(exec).executeCommand(cap.capture());\n      List<String> args = argsToStrings(cap.getValue());\n      // SET prefix:k v\n      assertThat(args.get(0), containsString(\"SET\"));\n      assertEquals(\"prefix:k\", args.get(1));\n      assertEquals(\"v\", args.get(2));\n    }\n  }\n\n  @Test\n  void jsonObjectMapperAppliedInCluster() {\n    JsonObjectMapper mapper = Mockito.mock(JsonObjectMapper.class);\n    when(mapper.toJson(Mockito.any())).thenReturn(\"JSON:obj\");\n\n    try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n        .connectionProvider(provider).commandExecutor(exec).jsonObjectMapper(mapper).build()) {\n\n      client.jsonSetWithEscape(\"k\", Path2.ROOT_PATH, Collections.singletonMap(\"a\", 1));\n      verify(exec).executeCommand(cap.capture());\n      List<String> args = argsToStrings(cap.getValue());\n      // JSON.SET k $ JSON:obj\n      assertEquals(\"JSON.SET\", args.get(0));\n      assertEquals(\"k\", args.get(1));\n      assertEquals(\"$\", args.get(2));\n      assertEquals(\"JSON:obj\", args.get(3));\n    }\n  }\n\n  @Test\n  void searchDialectAppliedInCluster() {\n    try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n        .connectionProvider(provider).commandExecutor(exec).searchDialect(3).build()) {\n\n      client.ftSearch(\"idx\", \"q\", new FTSearchParams());\n      verify(exec, atLeastOnce()).executeCommand(cap.capture());\n      List<String> args = argsToStrings(cap.getValue());\n      // FT.SEARCH idx q DIALECT 3\n      assertEquals(\"FT.SEARCH\", args.get(0));\n      assertEquals(\"idx\", args.get(1));\n      assertEquals(\"q\", args.get(2));\n      assertEquals(\"DIALECT\", args.get(3));\n      assertEquals(\"3\", args.get(4));\n    }\n  }\n\n  @Test\n  void maxTotalRetriesDurationUsesDefaultValues() {\n    // Test that when nothing is explicitly set, it uses default values\n    // Default socketTimeout = 2000ms (Protocol.DEFAULT_TIMEOUT)\n    // Default maxAttempts = 5 (JedisCluster.DEFAULT_MAX_ATTEMPTS)\n    // Expected maxTotalRetriesDuration = 2000 * 5 = 10000ms\n    int defaultSocketTimeout = 2000; // Protocol.DEFAULT_TIMEOUT\n    int defaultMaxAttempts = 5; // JedisCluster.DEFAULT_MAX_ATTEMPTS\n    Duration expectedMaxTotalRetriesDuration = Duration\n        .ofMillis((long) defaultSocketTimeout * defaultMaxAttempts); // 10000ms\n\n    ClusterConnectionProvider mockProvider = Mockito.mock(ClusterConnectionProvider.class);\n\n    // Use MockedConstruction to capture ClusterCommandExecutor constructor arguments\n    try (MockedConstruction<ClusterCommandExecutor> mockedExecutor = Mockito\n        .mockConstruction(ClusterCommandExecutor.class, (mock, context) -> {\n          // Verify constructor was called with default values\n          assertEquals(4, context.arguments().size(),\n            \"ClusterCommandExecutor should have 4 constructor arguments\");\n          assertEquals(mockProvider, context.arguments().get(0),\n            \"First argument should be the connection provider\");\n          assertEquals(defaultMaxAttempts, context.arguments().get(1),\n            \"Second argument should be default maxAttempts (5)\");\n          assertEquals(expectedMaxTotalRetriesDuration, context.arguments().get(2),\n            \"Third argument should be calculated from default values (2000ms * 5 = 10000ms)\");\n        })) {\n\n      try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n          .connectionProvider(mockProvider).build()) {\n        // Verify that ClusterCommandExecutor was constructed\n        assertEquals(1, mockedExecutor.constructed().size(),\n          \"ClusterCommandExecutor should have been constructed once\");\n      }\n    }\n  }\n\n  @Test\n  void maxTotalRetriesDurationCalculatedFromSocketTimeoutAndMaxAttempts() {\n    // Test that when maxTotalRetriesDuration is not explicitly set,\n    // it is calculated as socketTimeout * maxAttempts\n    int socketTimeout = 5000; // 5 seconds\n    int maxAttempts = 10;\n    Duration expectedMaxTotalRetriesDuration = Duration\n        .ofMillis((long) socketTimeout * maxAttempts); // 50000ms\n\n    DefaultJedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .socketTimeoutMillis(socketTimeout).build();\n\n    ClusterConnectionProvider mockProvider = Mockito.mock(ClusterConnectionProvider.class);\n\n    // Use MockedConstruction to capture ClusterCommandExecutor constructor arguments\n    try (MockedConstruction<ClusterCommandExecutor> mockedExecutor = Mockito\n        .mockConstruction(ClusterCommandExecutor.class, (mock, context) -> {\n          // Verify constructor was called with correct arguments\n          assertEquals(4, context.arguments().size(),\n            \"ClusterCommandExecutor should have 4 constructor arguments\");\n          assertEquals(mockProvider, context.arguments().get(0),\n            \"First argument should be the connection provider\");\n          assertEquals(maxAttempts, context.arguments().get(1),\n            \"Second argument should be maxAttempts\");\n          assertEquals(expectedMaxTotalRetriesDuration, context.arguments().get(2),\n            \"Third argument should be calculated maxTotalRetriesDuration (socketTimeout * maxAttempts)\");\n        })) {\n\n      try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n          .clientConfig(clientConfig).maxAttempts(maxAttempts).connectionProvider(mockProvider)\n          .build()) {\n        // Verify that ClusterCommandExecutor was constructed\n        assertEquals(1, mockedExecutor.constructed().size(),\n          \"ClusterCommandExecutor should have been constructed once\");\n      }\n    }\n  }\n\n  @Test\n  void maxTotalRetriesDurationUsesExplicitValueWhenSet() {\n    // Test that when maxTotalRetriesDuration is explicitly set,\n    // it uses that value instead of calculating from socketTimeout * maxAttempts\n    int socketTimeout = 5000; // 5 seconds\n    int maxAttempts = 10;\n    Duration explicitMaxTotalRetriesDuration = Duration.ofSeconds(100); // 100 seconds\n\n    DefaultJedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .socketTimeoutMillis(socketTimeout).build();\n\n    ClusterConnectionProvider mockProvider = Mockito.mock(ClusterConnectionProvider.class);\n\n    // Use MockedConstruction to capture ClusterCommandExecutor constructor arguments\n    try (MockedConstruction<ClusterCommandExecutor> mockedExecutor = Mockito\n        .mockConstruction(ClusterCommandExecutor.class, (mock, context) -> {\n          // Verify constructor was called with the explicitly set maxTotalRetriesDuration\n          assertEquals(4, context.arguments().size(),\n            \"ClusterCommandExecutor should have 4 constructor arguments\");\n          assertEquals(mockProvider, context.arguments().get(0),\n            \"First argument should be the connection provider\");\n          assertEquals(maxAttempts, context.arguments().get(1),\n            \"Second argument should be maxAttempts\");\n          assertEquals(explicitMaxTotalRetriesDuration, context.arguments().get(2),\n            \"Third argument should be the explicitly set maxTotalRetriesDuration\");\n        })) {\n\n      try (RedisClusterClient client = RedisClusterClient.builder().nodes(someNodes())\n          .clientConfig(clientConfig).maxAttempts(maxAttempts)\n          .maxTotalRetriesDuration(explicitMaxTotalRetriesDuration).connectionProvider(mockProvider)\n          .build()) {\n        // Verify that ClusterCommandExecutor was constructed\n        assertEquals(1, mockedExecutor.constructed().size(),\n          \"ClusterCommandExecutor should have been constructed once\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/JedisClusterConstructorReflectionTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.lang.reflect.Constructor;\nimport java.time.Duration;\nimport java.util.Set;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfSystemProperty;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.HostAndPortMapper;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n/**\n * Reflection-based coverage test for JedisCluster constructors against builder API. The test\n * verifies each constructor parameter can be provided via: - ClusterClientBuilder methods, or -\n * DefaultJedisClientConfig.Builder methods, or - Custom ConnectionProvider (manual coverage)\n */\n@EnabledIfSystemProperty(named = \"with-param-names\", matches = \"true\")\npublic class JedisClusterConstructorReflectionTest {\n\n  private static final Logger log = LoggerFactory\n      .getLogger(JedisClusterConstructorReflectionTest.class);\n\n  @Test\n  @DisplayName(\"Builder coverage of JedisCluster constructors (reflection)\")\n  void testConstructorParameterCoverageReport() {\n    Constructor<?>[] ctors = JedisCluster.class.getConstructors();\n    int total = 0, covered = 0;\n\n    StringBuilder uncoveredReport = new StringBuilder();\n\n    for (Constructor<?> ctor : ctors) {\n      total++;\n      java.lang.reflect.Parameter[] params = ctor.getParameters();\n\n      boolean[] paramCovered = new boolean[params.length];\n      String[] paramCoverageBy = new String[params.length];\n      String[] paramWhyMissing = new String[params.length];\n\n      for (int i = 0; i < params.length; i++) {\n        java.lang.reflect.Parameter p = params[i];\n        Class<?> t = p.getType();\n        String name = safeName(p);\n\n        if (t == Set.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().nodes(Set<HostAndPort>)\";\n        } else if (t == HostAndPort.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().nodes(Set.of(HostAndPort))\";\n        } else if (t == JedisClientConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().clientConfig(DefaultJedisClientConfig...)\";\n        } else if (t == GenericObjectPoolConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().poolConfig(...)\";\n        } else if (t == Cache.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().cache(...)\";\n        } else if (t == CacheConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().cacheConfig(...)\";\n        } else if (t == Duration.class) {\n          // Either maxTotalRetriesDuration or topologyRefreshPeriod\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisCluster.builder().maxTotalRetriesDuration(..)/topologyRefreshPeriod(..)\";\n        } else if (t == HostAndPortMapper.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().hostAndPortMapper(...)\";\n        } else if (t == int.class || t == Integer.class) {\n          String lname = name.toLowerCase();\n          if (lname.contains(\"attempt\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"JedisCluster.builder().maxAttempts(...)\";\n          } else if (lname.contains(\"port\")) {\n            // part of HostAndPort in string,int combos -> handled by string/host mapping below\n            paramCoverageBy[i] = \"JedisCluster.builder().nodes(Set.of(new HostAndPort(host,port)))\";\n          } else if (lname.contains(\"db\") || lname.contains(\"database\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().database(...)\";\n          } else if (lname.contains(\"conn\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis(...)\";\n          } else if ((lname.contains(\"so\") && lname.contains(\"timeout\"))\n              || lname.equals(\"sockettimeout\")) {\n                paramCovered[i] = true;\n                paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().socketTimeoutMillis(...)\";\n              } else\n            if (lname.contains(\"infinite\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().blockingSocketTimeoutMillis(...)\";\n            } else if (lname.contains(\"timeout\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis(...)+socketTimeoutMillis(...)\";\n            }\n        } else if (t == boolean.class || t == Boolean.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().ssl(...)\";\n        } else if (t == String.class) {\n          String lname = name.toLowerCase();\n          if (lname.contains(\"host\")) {\n            paramCoverageBy[i] = \"JedisCluster.builder().nodes(Set.of(new HostAndPort(host,port)))\";\n          } else if (lname.contains(\"user\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().user(...)\";\n          } else if (lname.contains(\"pass\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().password(...)\";\n          } else if (lname.contains(\"client\") && lname.contains(\"name\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().clientName(...)\";\n          } else {\n            // could be password; mark to client config\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().password/clientName/...\";\n          }\n        } else if (t == SSLSocketFactory.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslSocketFactory(...)\";\n        } else if (t == SSLParameters.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslParameters(...)\";\n        } else if (t == HostnameVerifier.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().hostnameVerifier(...)\";\n        } else if (t == ConnectionProvider.class || t == ClusterConnectionProvider.class\n            || t == PooledObjectFactory.class) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"Custom ConnectionProvider via builder.connectionProvider(...)\";\n            } else\n          if (t == CommandExecutor.class || t == CommandObjects.class) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = t == CommandExecutor.class ? \"builder.commandExecutor(...)\"\n                : \"Internal (builder-provided CommandObjects)\";\n          } else if (t == RedisProtocol.class) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"builder.redisProtocol(...)\";\n          } else {\n            paramCovered[i] = false;\n            paramWhyMissing[i] = \"No known builder mapping for type: \" + t.getSimpleName();\n          }\n      }\n\n      // Pair host/port pattern for (String,int,...) convenience ctors\n      int hostIdx = findPotentialHostStringIndex(params);\n      int portIdx = findPortIndex(params);\n      if (hostIdx != -1 && portIdx != -1) {\n        paramCovered[hostIdx] = true;\n        if (paramCoverageBy[hostIdx] == null)\n          paramCoverageBy[hostIdx] = \"JedisCluster.builder().nodes(Set.of(new HostAndPort(host,port)))\";\n        paramCovered[portIdx] = true;\n        if (paramCoverageBy[portIdx] == null)\n          paramCoverageBy[portIdx] = \"JedisCluster.builder().nodes(Set.of(new HostAndPort(host,port)))\";\n      }\n\n      boolean ctorCovered = true;\n      StringBuilder missingParams = new StringBuilder();\n      for (int i = 0; i < params.length; i++) {\n        if (!paramCovered[i]) {\n          ctorCovered = false;\n          missingParams.append(\"  - \").append(prettyParam(params[i])).append(\" -> \")\n              .append(paramWhyMissing[i] != null ? paramWhyMissing[i] : \"unknown\").append(\"\\n\");\n        }\n      }\n\n      if (!ctorCovered) {\n        uncoveredReport.append(\"\\nUncovered constructor: \").append(prettySignature(ctor))\n            .append(\"\\n\");\n        uncoveredReport.append(\"Missing parameters:\\n\").append(missingParams);\n      } else {\n        covered++;\n      }\n    }\n\n    log.info(\"Analyzed {} constructors; fully covered: {}\", total, covered);\n    if (covered < total) {\n      log.warn(\"Uncovered constructors detected:{}\", uncoveredReport);\n    }\n\n    assertEquals(total, covered, \"Expected all constructors to be covered by builders\");\n    assertTrue(total > 0, \"Expected at least one constructor to analyze\");\n  }\n\n  // ===== Helpers copied from pooled test, adapted =====\n  private static boolean hasType(java.lang.reflect.Parameter[] params, Class<?> type) {\n    for (java.lang.reflect.Parameter p : params)\n      if (p.getType() == type) return true;\n    return false;\n  }\n\n  private static int findPortIndex(java.lang.reflect.Parameter[] params) {\n    for (int i = 0; i < params.length; i++) {\n      Class<?> t = params[i].getType();\n      if (t == int.class || t == Integer.class) {\n        String n = safeName(params[i]).toLowerCase();\n        if (n.contains(\"port\")) return i;\n      }\n    }\n    int hostIdx = findPotentialHostStringIndex(params);\n    if (hostIdx != -1) {\n      for (int i = 0; i < params.length; i++)\n        if (params[i].getType() == int.class || params[i].getType() == Integer.class) return i;\n    }\n    return -1;\n  }\n\n  private static int findPotentialHostStringIndex(java.lang.reflect.Parameter[] params) {\n    for (int i = 0; i < params.length; i++) {\n      if (params[i].getType() == String.class) {\n        String n = safeName(params[i]).toLowerCase();\n        if (n.contains(\"host\")) return i;\n      }\n    }\n    boolean hasInt = false;\n    int stringCount = 0;\n    int stringIdx = -1;\n    for (int i = 0; i < params.length; i++) {\n      Class<?> t = params[i].getType();\n      if (t == int.class || t == Integer.class) hasInt = true;\n      if (t == String.class) {\n        stringCount++;\n        stringIdx = i;\n      }\n    }\n    if (hasInt && stringCount == 1) return stringIdx;\n    return -1;\n  }\n\n  private static String prettySignature(Constructor<?> ctor) {\n    StringBuilder sb = new StringBuilder();\n    sb.append(ctor.getDeclaringClass().getSimpleName()).append('(');\n    java.lang.reflect.Parameter[] ps = ctor.getParameters();\n    for (int i = 0; i < ps.length; i++) {\n      if (i > 0) sb.append(\", \");\n      sb.append(prettyParam(ps[i]));\n    }\n    sb.append(')');\n    return sb.toString();\n  }\n\n  private static String prettyParam(java.lang.reflect.Parameter p) {\n    return p.getType().getSimpleName() + \" \" + safeName(p);\n  }\n\n  private static String safeName(java.lang.reflect.Parameter p) {\n    try {\n      String n = p.getName();\n      return n != null ? n : (\"arg\" + p.getParameterizedType().getTypeName().hashCode());\n    } catch (Exception e) {\n      return \"arg\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/JedisPooledConstructorReflectionTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.lang.reflect.Constructor;\nimport java.net.URI;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfSystemProperty;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\n\n/**\n * Reflection-based coverage test that iterates over all public JedisPooled constructors and\n * validates that each constructor parameter can be provided by either\n * DefaultJedisClientConfig.builder() or JedisPooled.builder() (directly or via a custom\n * ConnectionProvider). It reports any uncovered constructors and missing parameters.\n */\n@EnabledIfSystemProperty(named = \"with-param-names\", matches = \"true\")\npublic class JedisPooledConstructorReflectionTest {\n\n  private static final Logger log = LoggerFactory\n      .getLogger(JedisPooledConstructorReflectionTest.class);\n\n  @Test\n  @DisplayName(\"Builder coverage of JedisPooled constructors (reflection)\")\n  void testConstructorParameterCoverageReport() {\n    Constructor<?>[] ctors = JedisPooled.class.getConstructors();\n    int total = 0, covered = 0;\n\n    StringBuilder uncoveredReport = new StringBuilder();\n\n    for (Constructor<?> ctor : ctors) {\n      log.info(\"Testing constructor: {}\", ctor);\n      total++;\n      java.lang.reflect.Parameter[] params = ctor.getParameters();\n\n      boolean[] paramCovered = new boolean[params.length];\n      String[] paramCoverageBy = new String[params.length];\n      String[] paramWhyMissing = new String[params.length];\n\n      // Pass 1: mark simple, unambiguous mappings by type/name\n      for (int i = 0; i < params.length; i++) {\n        java.lang.reflect.Parameter p = params[i];\n        Class<?> t = p.getType();\n        String name = safeName(p);\n\n        if (t == HostAndPort.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().hostAndPort(HostAndPort)\";\n        } else if (t == URI.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().fromURI(URI)\";\n        } else if (t == GenericObjectPoolConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().poolConfig(...)\";\n        } else if (t == JedisClientConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().clientConfig(DefaultJedisClientConfig...)\";\n        } else if (t == SSLSocketFactory.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslSocketFactory(...)\";\n        } else if (t == SSLParameters.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslParameters(...)\";\n        } else if (t == HostnameVerifier.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().hostnameVerifier(...)\";\n        } else if (t == Cache.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().cache(...)\";\n        } else if (t == CacheConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().cacheConfig(...)\";\n        } else if (t == CommandExecutor.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().commandExecutor(...)\";\n        } else if (t == RedisProtocol.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().redisProtocol(...)\";\n        } else if (t == ConnectionProvider.class || t == PooledConnectionProvider.class\n            || t == PooledObjectFactory.class || t == JedisSocketFactory.class) {\n              // Considered covered via custom provider path\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"Custom ConnectionProvider via JedisPooled.builder().connectionProvider(...)\";\n            } else\n          if (t == String.class) {\n            String lname = name.toLowerCase();\n            if (lname.contains(\"url\") || lname.contains(\"uri\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"JedisPooled.builder().fromURI(String)\";\n            } else if (lname.contains(\"host\")) {\n              // Will associate with a port int in pass 2\n              paramCoverageBy[i] = \"JedisPooled.builder().hostAndPort(host,int)\";\n            } else if (lname.contains(\"user\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().user(...)\";\n            } else if (lname.contains(\"pass\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().password(...)\";\n            } else if (lname.contains(\"client\") && lname.contains(\"name\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().clientName(...)\";\n            } else {\n              // Heuristic: if any int param exists, assume this is host to pair with port\n              if (hasType(params, int.class) || hasType(params, Integer.class)) {\n                paramCoverageBy[i] = \"JedisPooled.builder().hostAndPort(host,int)\";\n              } else {\n                // Otherwise assume URL\n                paramCovered[i] = true;\n                paramCoverageBy[i] = \"JedisPooled.builder().fromURI(String)\";\n              }\n            }\n          } else if (t == int.class || t == Integer.class) {\n            String lname = name.toLowerCase();\n            if (lname.contains(\"port\")) {\n              // Will be paired with host\n              paramCoverageBy[i] = \"JedisPooled.builder().hostAndPort(host,int)\";\n            } else if (lname.contains(\"db\") || lname.contains(\"database\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().database(...)\";\n            } else if (lname.contains(\"conn\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis(...)\";\n            } else if ((lname.contains(\"so\") && lname.contains(\"timeout\"))\n                || lname.equals(\"sockettimeout\")) {\n                  paramCovered[i] = true;\n                  paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().socketTimeoutMillis(...)\";\n                } else\n              if (lname.contains(\"infinite\")) {\n                paramCovered[i] = true;\n                paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().blockingSocketTimeoutMillis(...)\";\n              } else if (lname.contains(\"timeout\")) {\n                // Legacy single timeout: maps to both connection and socket timeouts\n                paramCovered[i] = true;\n                paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis(...)+socketTimeoutMillis(...)\";\n              } else {\n                // Generic: map the first int after host to port if not yet paired, else treat as\n                // timeout\n                paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis/socketTimeoutMillis(...)\";\n              }\n          } else if (t == boolean.class || t == Boolean.class) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().ssl(...)\";\n          } else if (t == CommandObjects.class) {\n            // Provided internally by builder, not directly configurable\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"Internal (builder-provided CommandObjects)\";\n          } else {\n            paramCovered[i] = false;\n            paramWhyMissing[i] = \"No known builder mapping for type: \" + t.getSimpleName();\n          }\n      }\n\n      // Pass 2: pair host/port if needed\n      int hostIdx = findPotentialHostStringIndex(params);\n      int portIdx = findPortIndex(params);\n      if (hostIdx != -1 && portIdx != -1) {\n        // Both are coverable via hostAndPort\n        paramCovered[hostIdx] = true;\n        if (paramCoverageBy[hostIdx] == null)\n          paramCoverageBy[hostIdx] = \"JedisPooled.builder().hostAndPort(host,int)\";\n        paramCovered[portIdx] = true;\n        if (paramCoverageBy[portIdx] == null)\n          paramCoverageBy[portIdx] = \"JedisPooled.builder().hostAndPort(host,int)\";\n      }\n\n      // Evaluate constructor coverage\n      boolean ctorCovered = true;\n      StringBuilder missingParams = new StringBuilder();\n      for (int i = 0; i < params.length; i++) {\n        if (!paramCovered[i]) {\n          ctorCovered = false;\n          missingParams.append(\"  - \").append(prettyParam(params[i])).append(\" -> \")\n              .append(paramWhyMissing[i] != null ? paramWhyMissing[i] : \"unknown\").append(\"\\n\");\n        }\n      }\n\n      if (!ctorCovered) {\n        uncoveredReport.append(\"\\nUncovered constructor: \").append(prettySignature(ctor))\n            .append(\"\\n\");\n        uncoveredReport.append(\"Missing parameters:\\n\").append(missingParams);\n      } else {\n        covered++;\n      }\n    }\n\n    log.info(\"Analyzed {} constructors; fully covered: {}\", total, covered);\n\n    if (covered < total) {\n      log.warn(\"Uncovered constructors detected:{}\", uncoveredReport);\n    }\n    assertEquals(total, covered, \"Expected all constructors to be covered by builders\");\n    assertTrue(total > 0, \"Expected at least one constructor to analyze\");\n    // This test reports coverage; it does not fail on gaps, but logs them clearly.\n  }\n\n  // ===== Mapping helpers for coverage test =====\n\n  private static boolean hasType(java.lang.reflect.Parameter[] params, Class<?> type) {\n    for (java.lang.reflect.Parameter p : params)\n      if (p.getType() == type) return true;\n    return false;\n  }\n\n  private static int findPortIndex(java.lang.reflect.Parameter[] params) {\n    for (int i = 0; i < params.length; i++) {\n      Class<?> t = params[i].getType();\n      if (t == int.class || t == Integer.class) {\n        String n = safeName(params[i]).toLowerCase();\n        if (n.contains(\"port\")) return i;\n      }\n    }\n    // fallback: choose the first int when there is also a host string present\n    int hostIdx = findPotentialHostStringIndex(params);\n    if (hostIdx != -1) {\n      for (int i = 0; i < params.length; i++)\n        if (params[i].getType() == int.class || params[i].getType() == Integer.class) return i;\n    }\n    return -1;\n  }\n\n  private static int findPotentialHostStringIndex(java.lang.reflect.Parameter[] params) {\n    for (int i = 0; i < params.length; i++) {\n      if (params[i].getType() == String.class) {\n        String n = safeName(params[i]).toLowerCase();\n        if (n.contains(\"host\")) return i;\n      }\n    }\n    // fallback: if there is an int parameter and exactly one String among (String,int,...), treat\n    // that String as host\n    boolean hasInt = false;\n    int stringCount = 0;\n    int stringIdx = -1;\n    for (int i = 0; i < params.length; i++) {\n      Class<?> t = params[i].getType();\n      if (t == int.class || t == Integer.class) hasInt = true;\n      if (t == String.class) {\n        stringCount++;\n        stringIdx = i;\n      }\n    }\n    if (hasInt && stringCount == 1) return stringIdx;\n    return -1;\n  }\n\n  private static String prettySignature(Constructor<?> ctor) {\n    StringBuilder sb = new StringBuilder();\n    sb.append(ctor.getDeclaringClass().getSimpleName()).append('(');\n    java.lang.reflect.Parameter[] ps = ctor.getParameters();\n    for (int i = 0; i < ps.length; i++) {\n      if (i > 0) sb.append(\", \");\n      sb.append(prettyParam(ps[i]));\n    }\n    sb.append(')');\n    return sb.toString();\n  }\n\n  private static String prettyParam(java.lang.reflect.Parameter p) {\n    return p.getType().getSimpleName() + \" \" + safeName(p);\n  }\n\n  private static String safeName(java.lang.reflect.Parameter p) {\n    try {\n      String n = p.getName();\n      return n != null ? n : (\"arg\" + p.getParameterizedType().getTypeName().hashCode());\n    } catch (Exception e) {\n      return \"arg\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/JedisSentineledConstructorReflectionTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.lang.reflect.Constructor;\nimport java.util.Set;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfSystemProperty;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.SentineledConnectionProvider;\n\n/**\n * Reflection-based coverage test for JedisSentineled constructors against builder API.\n */\n@EnabledIfSystemProperty(named = \"with-param-names\", matches = \"true\")\npublic class JedisSentineledConstructorReflectionTest {\n\n  private static final Logger log = LoggerFactory\n      .getLogger(JedisSentineledConstructorReflectionTest.class);\n\n  @Test\n  @DisplayName(\"Builder coverage of JedisSentineled constructors (reflection)\")\n  void testConstructorParameterCoverageReport() {\n    Constructor<?>[] ctors = JedisSentineled.class.getConstructors();\n    int total = 0, covered = 0;\n\n    StringBuilder uncoveredReport = new StringBuilder();\n\n    for (Constructor<?> ctor : ctors) {\n      total++;\n      java.lang.reflect.Parameter[] params = ctor.getParameters();\n\n      boolean[] paramCovered = new boolean[params.length];\n      String[] paramCoverageBy = new String[params.length];\n      String[] paramWhyMissing = new String[params.length];\n\n      for (int i = 0; i < params.length; i++) {\n        java.lang.reflect.Parameter p = params[i];\n        Class<?> t = p.getType();\n        String name = safeName(p);\n\n        if (t == String.class) {\n          String lname = name.toLowerCase();\n          if (lname.contains(\"master\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"JedisSentineled.builder().masterName(String)\";\n          } else if (lname.contains(\"user\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().user(...)\";\n          } else if (lname.contains(\"pass\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().password(...)\";\n          } else if (lname.contains(\"client\") && lname.contains(\"name\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().clientName(...)\";\n          } else {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().password/clientName/...\";\n          }\n        } else if (t == Set.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisSentineled.builder().sentinels(Set<HostAndPort>)\";\n        } else if (t == JedisClientConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisSentineled.builder().masterClientConfig(...)/sentinelClientConfig(...)\";\n        } else if (t == GenericObjectPoolConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisSentineled.builder().poolConfig(...)\";\n        } else if (t == Cache.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisSentineled.builder().cache(...)\";\n        } else if (t == CacheConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisSentineled.builder().cacheConfig(...)\";\n        } else if (t == int.class || t == Integer.class) {\n          String lname = name.toLowerCase();\n          if (lname.contains(\"db\") || lname.contains(\"database\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().database(...)\";\n          } else if (lname.contains(\"conn\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis(...)\";\n          } else if ((lname.contains(\"so\") && lname.contains(\"timeout\"))\n              || lname.equals(\"sockettimeout\")) {\n                paramCovered[i] = true;\n                paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().socketTimeoutMillis(...)\";\n              } else\n            if (lname.contains(\"infinite\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().blockingSocketTimeoutMillis(...)\";\n            } else if (lname.contains(\"timeout\")) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis(...)+socketTimeoutMillis(...)\";\n            }\n        } else if (t == boolean.class || t == Boolean.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().ssl(...)\";\n        } else if (t == SSLSocketFactory.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslSocketFactory(...)\";\n        } else if (t == SSLParameters.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslParameters(...)\";\n        } else if (t == HostnameVerifier.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().hostnameVerifier(...)\";\n        } else if (t == ConnectionProvider.class || t == SentineledConnectionProvider.class\n            || t == PooledObjectFactory.class) {\n              paramCovered[i] = true;\n              paramCoverageBy[i] = \"Custom ConnectionProvider via builder.connectionProvider(...)\";\n            } else\n          if (t == CommandExecutor.class || t == CommandObjects.class) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = t == CommandExecutor.class ? \"builder.commandExecutor(...)\"\n                : \"Internal (builder-provided CommandObjects)\";\n          } else if (t == RedisProtocol.class) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"builder.redisProtocol(...)\";\n          } else {\n            paramCovered[i] = false;\n            paramWhyMissing[i] = \"No known builder mapping for type: \" + t.getSimpleName();\n          }\n      }\n\n      boolean ctorCovered = true;\n      StringBuilder missingParams = new StringBuilder();\n      for (int i = 0; i < params.length; i++) {\n        if (!paramCovered[i]) {\n          ctorCovered = false;\n          missingParams.append(\"  - \").append(prettyParam(params[i])).append(\" -> \")\n              .append(paramWhyMissing[i] != null ? paramWhyMissing[i] : \"unknown\").append(\"\\n\");\n        }\n      }\n\n      if (!ctorCovered) {\n        uncoveredReport.append(\"\\nUncovered constructor: \").append(prettySignature(ctor))\n            .append(\"\\n\");\n        uncoveredReport.append(\"Missing parameters:\\n\").append(missingParams);\n      } else {\n        covered++;\n      }\n    }\n\n    log.info(\"Analyzed {} constructors; fully covered: {}\", total, covered);\n    if (covered < total) {\n      log.warn(\"Uncovered constructors detected:{}\", uncoveredReport);\n    }\n\n    assertEquals(total, covered, \"Expected all constructors to be covered by builders\");\n    assertTrue(total > 0, \"Expected at least one constructor to analyze\");\n  }\n\n  private static String prettySignature(Constructor<?> ctor) {\n    StringBuilder sb = new StringBuilder();\n    sb.append(ctor.getDeclaringClass().getSimpleName()).append('(');\n    java.lang.reflect.Parameter[] ps = ctor.getParameters();\n    for (int i = 0; i < ps.length; i++) {\n      if (i > 0) sb.append(\", \");\n      sb.append(prettyParam(ps[i]));\n    }\n    sb.append(')');\n    return sb.toString();\n  }\n\n  private static String prettyParam(java.lang.reflect.Parameter p) {\n    return p.getType().getSimpleName() + \" \" + safeName(p);\n  }\n\n  private static String safeName(java.lang.reflect.Parameter p) {\n    try {\n      String n = p.getName();\n      return n != null ? n : (\"arg\" + p.getParameterizedType().getTypeName().hashCode());\n    } catch (Exception e) {\n      return \"arg\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/RedisClusterClientMigrationIntegrationTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport redis.clients.jedis.*;\n\n/**\n * Integration test that verifies migration compatibility from the legacy JedisCluster constructor\n * to the new RedisClusterClient.Builder pattern.\n * <p>\n * This test demonstrates that the following legacy JedisCluster constructor:\n * \n * <pre>\n * public JedisCluster(Set&lt;HostAndPort&gt; clusterNodes, JedisClientConfig clientConfig, \n *                     int maxAttempts, GenericObjectPoolConfig&lt;Connection&gt; poolConfig)\n * </pre>\n * \n * can be replaced with the RedisClusterClient.Builder pattern while maintaining the same\n * functionality.\n */\n@Tag(\"integration\")\npublic class RedisClusterClientMigrationIntegrationTest {\n\n  private static EndpointConfig endpoint;\n\n  private static Set<HostAndPort> CLUSTER_NODES;\n  private static String PASSWORD;\n  private static final int MAX_ATTEMPTS = 3;\n\n  private JedisCluster legacyCluster;\n  private RedisClusterClient newCluster;\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-stable\");\n    CLUSTER_NODES = new HashSet<>(endpoint.getHostsAndPorts());\n    PASSWORD = endpoint.getPassword();\n  }\n\n  @BeforeEach\n  public void setUp() {\n    // Clean up any existing data before each test\n    cleanClusterData();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    // Close connections\n    if (legacyCluster != null) {\n      legacyCluster.close();\n      legacyCluster = null;\n    }\n    if (newCluster != null) {\n      newCluster.close();\n      newCluster = null;\n    }\n\n    // Clean up data after tests\n    cleanClusterData();\n  }\n\n  /**\n   * Test that verifies both approaches can handle cluster operations correctly. Tests constructor:\n   * JedisCluster(Set&lt;HostAndPort&gt;, JedisClientConfig, int,\n   * GenericObjectPoolConfig&lt;Connection&gt;)\n   */\n  @Test\n  public void testSpringDataRedisConstructor() {\n    // Prepare common configuration\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().password(PASSWORD)\n        .socketTimeoutMillis(2000).connectionTimeoutMillis(2000).build();\n\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n\n    // Create both cluster clients\n    legacyCluster = new JedisCluster(CLUSTER_NODES, clientConfig, MAX_ATTEMPTS, poolConfig);\n    newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)\n        .maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  /**\n   * Test migration from constructor with username, password, clientName, and SSL. Tests\n   * constructor: JedisCluster(Set&lt;HostAndPort&gt;, int, int, int, String, String, String,\n   * GenericObjectPoolConfig&lt;Connection&gt;, boolean)\n   */\n  @Test\n  public void testConstructorWithUsernamePasswordClientNameAndSsl() {\n    int connectionTimeout = 2000;\n    int socketTimeout = 2000;\n    String username = \"default\";\n    String clientName = \"test-client\";\n    boolean ssl = false; // SSL requires special cluster setup\n\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n\n    // Legacy constructor with username\n    legacyCluster = new JedisCluster(CLUSTER_NODES, connectionTimeout, socketTimeout, MAX_ATTEMPTS,\n        username, PASSWORD, clientName, poolConfig, ssl);\n\n    // New Builder pattern\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(socketTimeout)\n        .user(username).password(PASSWORD).clientName(clientName).ssl(ssl).build();\n\n    newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)\n        .maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  /**\n   * Test migration from constructor with password, clientName, and SSL (no username). Tests\n   * constructor: JedisCluster(Set&lt;HostAndPort&gt;, int, int, int, String, String,\n   * GenericObjectPoolConfig&lt;Connection&gt;, boolean)\n   */\n  @Test\n  public void testConstructorWithPasswordClientNameAndSsl() {\n    int connectionTimeout = 2000;\n    int socketTimeout = 2000;\n    String clientName = \"test-client\";\n    boolean ssl = false; // SSL requires special cluster setup\n\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n\n    // Legacy constructor without username\n    legacyCluster = new JedisCluster(CLUSTER_NODES, connectionTimeout, socketTimeout, MAX_ATTEMPTS,\n        PASSWORD, clientName, poolConfig, ssl);\n\n    // New Builder pattern\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(socketTimeout)\n        .password(PASSWORD).clientName(clientName).ssl(ssl).build();\n\n    newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)\n        .maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  /**\n   * Test migration from simple constructor with just nodes and poolConfig. Tests constructor:\n   * JedisCluster(Set&lt;HostAndPort&gt;, GenericObjectPoolConfig&lt;Connection&gt;)\n   */\n  @Test\n  public void testConstructorWithNodesAndPoolConfig() {\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n    poolConfig.setMaxTotal(8);\n    poolConfig.setMaxIdle(8);\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().password(PASSWORD).build();\n\n    // Legacy constructor\n    legacyCluster = new JedisCluster(CLUSTER_NODES, clientConfig, poolConfig);\n\n    // New Builder pattern - need to add password via clientConfig\n    newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)\n        .poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  /**\n   * Test migration from constructor with connection timeout, socket timeout, maxAttempts, password,\n   * and poolConfig. Tests constructor: JedisCluster(Set&lt;HostAndPort&gt;, int, int, int, String,\n   * GenericObjectPoolConfig&lt;Connection&gt;)\n   */\n  @Test\n  public void testConstructorWithTimeoutsPasswordAndPoolConfig() {\n    int connectionTimeout = 2000;\n    int socketTimeout = 2000;\n\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n\n    // Legacy constructor\n    legacyCluster = new JedisCluster(CLUSTER_NODES, connectionTimeout, socketTimeout, MAX_ATTEMPTS,\n        PASSWORD, poolConfig);\n\n    // New Builder pattern\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(socketTimeout)\n        .password(PASSWORD).build();\n\n    newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)\n        .maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  /**\n   * Test migration from constructor with timeout, maxAttempts, and poolConfig. Tests constructor:\n   * JedisCluster(Set&lt;HostAndPort&gt;, int, int, GenericObjectPoolConfig&lt;Connection&gt;)\n   */\n  @Test\n  public void testConstructorWithTimeoutMaxAttemptsAndPoolConfig() {\n    int timeout = 2000;\n\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().timeoutMillis(timeout)\n        .password(PASSWORD).build();\n\n    // Legacy constructor - uses same timeout for connection and socket\n    legacyCluster = new JedisCluster(CLUSTER_NODES, timeout, timeout, MAX_ATTEMPTS, PASSWORD,\n        poolConfig);\n\n    // New Builder pattern\n    newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)\n        .maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  /**\n   * Test migration from single HostAndPort constructor with poolConfig. Tests constructor:\n   * JedisCluster(HostAndPort, GenericObjectPoolConfig&lt;Connection&gt;)\n   */\n  @Test\n  public void testConstructorWithSingleNodeAndPoolConfig() {\n    int timeout = 2000;\n    HostAndPort singleNode = endpoint.getHostsAndPorts().get(0);\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().password(PASSWORD).build();\n\n    // Legacy constructor with single node\n    legacyCluster = new JedisCluster(singleNode, timeout, timeout, MAX_ATTEMPTS, PASSWORD,\n        poolConfig);\n\n    // New Builder pattern - need to add password and wrap single node in a Set\n    Set<HostAndPort> singleNodeSet = new HashSet<>();\n    singleNodeSet.add(singleNode);\n\n    newCluster = RedisClusterClient.builder().nodes(singleNodeSet).clientConfig(clientConfig)\n        .poolConfig(poolConfig).build();\n\n    verifyBothClients(legacyCluster, newCluster);\n  }\n\n  protected void verifyBothClients(JedisCluster legacyCluster, RedisClusterClient newCluster) {\n    // Verify both discovered the cluster nodes\n    assertNotNull(legacyCluster.getClusterNodes(), \"Legacy cluster should discover cluster nodes\");\n    assertNotNull(newCluster.getClusterNodes(), \"New cluster should discover cluster nodes\");\n\n    // Both should have discovered the same number of nodes\n    assertEquals(legacyCluster.getClusterNodes().size(), newCluster.getClusterNodes().size(),\n      \"Both approaches should discover the same number of cluster nodes\");\n\n    // Test basic string operations\n    String key1 = \"test-string-key\";\n    legacyCluster.set(key1, \"value1\");\n    assertEquals(\"value1\", newCluster.get(key1));\n\n    // Test increment operations\n    String key2 = \"test-counter-key\";\n    legacyCluster.set(key2, \"0\");\n    newCluster.incr(key2);\n    assertEquals(\"1\", legacyCluster.get(key2));\n\n    // Clean up\n    newCluster.del(key1);\n    newCluster.del(key2);\n  }\n\n  /**\n   * Helper method to clean up cluster data before and after tests.\n   */\n  private void cleanClusterData() {\n    // Connect to each stable cluster node and flush data\n    for (HostAndPort node : endpoint.getHostsAndPorts()) {\n      try (redis.clients.jedis.Jedis jedis = new redis.clients.jedis.Jedis(node)) {\n        jedis.auth(PASSWORD);\n        jedis.flushDB();\n      } catch (Exception e) {\n        // Ignore errors during cleanup\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/builders/UnifiedJedisConstructorReflectionTest.java",
    "content": "package redis.clients.jedis.builders;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.lang.reflect.Constructor;\nimport java.net.URI;\nimport java.util.Set;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.pool2.PooledObjectFactory;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfSystemProperty;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.csc.Cache;\nimport redis.clients.jedis.csc.CacheConfig;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n/**\n * Reflection-based coverage test for UnifiedJedis constructors against builder API. Since\n * UnifiedJedis is the base, we treat many low-level components as covered via custom\n * ConnectionProvider or builder-provided internals.\n */\n@EnabledIfSystemProperty(named = \"with-param-names\", matches = \"true\")\npublic class UnifiedJedisConstructorReflectionTest {\n\n  private static final Logger log = LoggerFactory\n      .getLogger(UnifiedJedisConstructorReflectionTest.class);\n\n  @Test\n  @DisplayName(\"Builder coverage of UnifiedJedis constructors (reflection)\")\n  void testConstructorParameterCoverageReport() {\n    Constructor<?>[] ctors = UnifiedJedis.class.getConstructors();\n    int total = 0, covered = 0;\n\n    StringBuilder uncoveredReport = new StringBuilder();\n\n    for (Constructor<?> ctor : ctors) {\n      if (isUnsafeConstructor(ctor) || clusterConstructorThatShouldBeDeprecatedAndRemoved(ctor)\n          || retriesConstructorThatShouldBeIncorporatedIntoBuilderAsDefault(ctor)\n          || multiDbConnectionProviderShouldBeReplacedWithMultiDbClient(ctor)) {\n        // Exclude unsafe constructors from analysis as requested\n        continue;\n      }\n      total++;\n      java.lang.reflect.Parameter[] params = ctor.getParameters();\n\n      boolean[] paramCovered = new boolean[params.length];\n      String[] paramCoverageBy = new String[params.length];\n      String[] paramWhyMissing = new String[params.length];\n\n      for (int i = 0; i < params.length; i++) {\n        java.lang.reflect.Parameter p = params[i];\n        Class<?> t = p.getType();\n        String name = safeName(p);\n\n        if (t == HostAndPort.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"JedisPooled.builder().hostAndPort(HostAndPort)\";\n        } else if (t == URI.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"builder().fromURI(URI)\";\n        } else if (t == ConnectionProvider.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"Custom ConnectionProvider via builder.connectionProvider(...)\";\n        } else if (t == GenericObjectPoolConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"builder.poolConfig(...) (in concrete builders)\";\n        } else if (t == JedisClientConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"builder.clientConfig(DefaultJedisClientConfig...)\";\n        } else if (t == Cache.class || t == CacheConfig.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = t == Cache.class ? \"builder.cache(...)\" : \"builder.cacheConfig(...)\";\n        } else if (t == CommandExecutor.class || t == CommandObjects.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = t == CommandExecutor.class ? \"builder.commandExecutor(...)\"\n              : \"Internal (builder-provided CommandObjects)\";\n        } else if (t == RedisProtocol.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"builder.redisProtocol(...)\";\n        } else if (t == SSLSocketFactory.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslSocketFactory(...)\";\n        } else if (t == SSLParameters.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().sslParameters(...)\";\n        } else if (t == HostnameVerifier.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().hostnameVerifier(...)\";\n        } else if (t == String.class) {\n          String lname = name.toLowerCase();\n          if (lname.contains(\"url\") || lname.contains(\"uri\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"JedisPooled.builder().fromURI(String)\";\n          } else {\n            paramCovered[i] = false;\n            paramWhyMissing[i] = \"No known builder mapping for type: \" + t.getSimpleName();\n          }\n        } else if (t == int.class || t == Integer.class) {\n          String lname = name.toLowerCase();\n          if (lname.contains(\"timeout\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().connectionTimeoutMillis/socketTimeoutMillis/blockingSocketTimeoutMillis(...)\";\n          } else if (lname.contains(\"attempt\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"Cluster/Sentinel builders manage attempts\";\n          } else if (lname.contains(\"db\") || lname.contains(\"database\")) {\n            paramCovered[i] = true;\n            paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().database(...)\";\n          }\n        } else if (t == boolean.class || t == Boolean.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"DefaultJedisClientConfig.builder().ssl(...)\";\n        } else if (t == PooledObjectFactory.class) {\n          paramCovered[i] = true;\n          paramCoverageBy[i] = \"Custom provider via PooledObjectFactory in concrete builders\";\n        } else {\n          paramCovered[i] = false;\n          paramWhyMissing[i] = \"No known builder mapping for type: \" + t.getSimpleName();\n        }\n      }\n\n      boolean ctorCovered = true;\n      StringBuilder missingParams = new StringBuilder();\n      for (int i = 0; i < params.length; i++) {\n        if (!paramCovered[i]) {\n          ctorCovered = false;\n          missingParams.append(\"  - \").append(prettyParam(params[i])).append(\" -> \")\n              .append(paramWhyMissing[i] != null ? paramWhyMissing[i] : \"unknown\").append(\"\\n\");\n        }\n      }\n\n      if (!ctorCovered) {\n        uncoveredReport.append(\"\\nUncovered constructor: \").append(prettySignature(ctor))\n            .append(\"\\n\");\n        uncoveredReport.append(\"Missing parameters:\\n\").append(missingParams);\n      } else {\n        covered++;\n      }\n    }\n\n    log.info(\"Analyzed {} constructors; fully covered: {}\", total, covered);\n    if (covered < total) {\n      log.warn(\"Uncovered constructors detected:{}\", uncoveredReport);\n    }\n\n    assertEquals(total, covered, \"Expected all constructors to be covered by builders\");\n    assertTrue(total > 0, \"Expected at least one constructor to analyze\");\n  }\n\n  private static boolean isUnsafeConstructor(Constructor<?> ctor) {\n    Class<?>[] types = ctor.getParameterTypes();\n    for (Class<?> t : types) {\n      if (t == Connection.class || t == JedisSocketFactory.class) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  // FIXME: Remove this when we use command executor with retries by default\n  private static boolean retriesConstructorThatShouldBeIncorporatedIntoBuilderAsDefault(\n      Constructor<?> ctor) {\n    return ctor.toString().equals(\n      \"public redis.clients.jedis.UnifiedJedis(redis.clients.jedis.providers.ConnectionProvider,int,java.time.Duration)\");\n  }\n\n  // FIXME: Remove this when we remove the deprecated Cluster-related constructors from UnifiedJedis\n  private static boolean clusterConstructorThatShouldBeDeprecatedAndRemoved(Constructor<?> ctor) {\n    Class<?>[] types = ctor.getParameterTypes();\n    return types.length > 1 && ((types[0] == Set.class && types[1] == JedisClientConfig.class)\n        || types[0].getSimpleName().equals(\"ClusterConnectionProvider\"));\n  }\n\n  // FIXME: Remove this when we add convince class and builder for ResilientClient\n  private static boolean multiDbConnectionProviderShouldBeReplacedWithMultiDbClient(\n      Constructor<?> ctor) {\n    Class<?>[] types = ctor.getParameterTypes();\n    return types.length == 1 && types[0].getSimpleName().equals(\"MultiDbConnectionProvider\");\n  }\n\n  private static String prettySignature(Constructor<?> ctor) {\n    StringBuilder sb = new StringBuilder();\n    sb.append(ctor.getDeclaringClass().getSimpleName()).append('(');\n    java.lang.reflect.Parameter[] ps = ctor.getParameters();\n    for (int i = 0; i < ps.length; i++) {\n      if (i > 0) sb.append(\", \");\n      sb.append(prettyParam(ps[i]));\n    }\n    sb.append(')');\n    return sb.toString();\n  }\n\n  private static String prettyParam(java.lang.reflect.Parameter p) {\n    return p.getType().getSimpleName() + \" \" + safeName(p);\n  }\n\n  private static String safeName(java.lang.reflect.Parameter p) {\n    try {\n      String n = p.getName();\n      return n != null ? n : (\"arg\" + p.getParameterizedType().getTypeName().hashCode());\n    } catch (Exception e) {\n      return \"arg\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/codegen/CommandFlagsRegistryGenerator.java",
    "content": "package redis.clients.jedis.codegen;\n\nimport com.google.gson.Gson;\nimport com.google.gson.reflect.TypeToken;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.Module;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.resps.CommandInfo;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * Code generator for StaticCommandFlagsRegistry. This generator connects to a Redis server,\n * retrieves all command metadata using the COMMAND command, and automatically generates the\n * StaticCommandFlagsRegistry class that implements CommandFlagsRegistry interface.\n * <p>\n * Usage:\n *\n * <pre>\n * java -cp ... redis.clients.jedis.codegen.CommandFlagsRegistryGenerator [host] [port]\n * </pre>\n * <p>\n * Arguments:\n * <ul>\n * <li>host - Redis server hostname (default: localhost)</li>\n * <li>port - Redis server port (default: 6379)</li>\n * </ul>\n * <p>\n * Note: This is a code generation tool and should NOT be executed as part of regular tests.\n */\npublic class CommandFlagsRegistryGenerator {\n\n  private static final String JAVA_FILE = \"src/main/java/redis/clients/jedis/StaticCommandFlagsRegistryInitializer.java\";\n  private static final String BACKUP_JSON_FILE = \"redis_commands_metadata.json\";\n\n  private final String redisHost;\n  private final int redisPort;\n\n  // Server metadata collected during generation\n  private ServerMetadata serverMetadata;\n\n  // Map JSON flag names to Java enum names\n  private static final Map<String, String> FLAG_MAPPING = new LinkedHashMap<>();\n  static {\n    FLAG_MAPPING.put(\"readonly\", \"READONLY\");\n    FLAG_MAPPING.put(\"write\", \"WRITE\");\n    FLAG_MAPPING.put(\"denyoom\", \"DENYOOM\");\n    FLAG_MAPPING.put(\"admin\", \"ADMIN\");\n    FLAG_MAPPING.put(\"pubsub\", \"PUBSUB\");\n    FLAG_MAPPING.put(\"noscript\", \"NOSCRIPT\");\n    FLAG_MAPPING.put(\"random\", \"RANDOM\");\n    FLAG_MAPPING.put(\"sort_for_script\", \"SORT_FOR_SCRIPT\");\n    FLAG_MAPPING.put(\"loading\", \"LOADING\");\n    FLAG_MAPPING.put(\"stale\", \"STALE\");\n    FLAG_MAPPING.put(\"skip_monitor\", \"SKIP_MONITOR\");\n    FLAG_MAPPING.put(\"skip_slowlog\", \"SKIP_SLOWLOG\");\n    FLAG_MAPPING.put(\"asking\", \"ASKING\");\n    FLAG_MAPPING.put(\"fast\", \"FAST\");\n    FLAG_MAPPING.put(\"movablekeys\", \"MOVABLEKEYS\");\n    FLAG_MAPPING.put(\"module\", \"MODULE\");\n    FLAG_MAPPING.put(\"blocking\", \"BLOCKING\");\n    FLAG_MAPPING.put(\"no_auth\", \"NO_AUTH\");\n    FLAG_MAPPING.put(\"no_async_loading\", \"NO_ASYNC_LOADING\");\n    FLAG_MAPPING.put(\"no_multi\", \"NO_MULTI\");\n    FLAG_MAPPING.put(\"no_mandatory_keys\", \"NO_MANDATORY_KEYS\");\n    FLAG_MAPPING.put(\"allow_busy\", \"ALLOW_BUSY\");\n  }\n\n  /**\n   * Manual command flag overrides that take precedence over Redis server metadata. These overrides\n   * allow defining custom flag combinations, request policies, and response policies for specific\n   * commands when the server-provided metadata is incorrect or needs customization.\n   * <p>\n   * Key: Command name (uppercase, e.g., \"KEYS\" or \"ACL CAT\" for subcommands) Value: CommandMetadata\n   * with the override values\n   * <p>\n   * To add a new override, add an entry to this map in the static initializer block.\n   */\n  private static final Map<String, CommandMetadata> MANUAL_OVERRIDES = new LinkedHashMap<>();\n  static {\n    // Override INFO: change request policy from ALL_SHARDS to DEFAULT\n    // Reason: SPECIAL response policy not yet supported in the library and defaults to return\n    // single result INFO should be executed on a single node, not broadcast to all shards\n    MANUAL_OVERRIDES.put(\"INFO\",\n      new CommandMetadata(Arrays.asList(\"loading\", \"stale\"), \"default\", \"special\"));\n\n    // Override FUNCTION STATS: change request policy from ALL_SHARDS to DEFAULT\n    // Reason: SPECIAL response policy not yet supported in the library and defaults to return\n    // single result FUNCTION STATS should be executed on a single node, not broadcast to all shards\n    MANUAL_OVERRIDES.put(\"FUNCTION STATS\",\n      new CommandMetadata(Arrays.asList(\"noscript\", \"allow_busy\"), \"default\", \"special\"));\n  }\n\n  /**\n   * Known parent commands that have subcommands. When the generator encounters a command name\n   * containing a space (e.g., \"FUNCTION LOAD\"), the parent part (e.g., \"FUNCTION\") must be listed\n   * here for proper subcommand registration.\n   * <p>\n   * If a new Redis command with subcommands is added, add the parent command name to this set. The\n   * generator will fail with a helpful error message if an unknown parent command is encountered.\n   */\n  private static final Set<String> KNOWN_PARENT_COMMANDS = new HashSet<>(Arrays.asList(\"ACL\",\n    \"CLIENT\", \"CLUSTER\", \"COMMAND\", \"CONFIG\", \"FUNCTION\", \"HOTKEYS\", \"LATENCY\", \"MEMORY\", \"MODULE\",\n    \"OBJECT\", \"PUBSUB\", \"SCRIPT\", \"SLOWLOG\", \"XGROUP\", \"XINFO\", \"FT.CONFIG\", \"FT.CURSOR\"));\n\n  public CommandFlagsRegistryGenerator(String host, int port) {\n    this.redisHost = host;\n    this.redisPort = port;\n  }\n\n  public static void main(String[] args) {\n    printLine();\n    System.out.println(\"StaticCommandFlagsRegistry Generator\");\n    printLine();\n\n    // Parse command line arguments\n    String host = args.length > 0 ? args[0] : \"localhost\";\n    int port = args.length > 1 ? Integer.parseInt(args[1]) : 6379;\n\n    System.out.println(\"Redis server: \" + host + \":\" + port);\n    System.out.println();\n\n    try {\n      CommandFlagsRegistryGenerator generator = new CommandFlagsRegistryGenerator(host, port);\n      generator.generate();\n\n      System.out.println();\n      printLine();\n      System.out.println(\"✓ Code generation completed successfully!\");\n      printLine();\n    } catch (Exception e) {\n      System.err.println();\n      printLine();\n      System.err.println(\"✗ Code generation failed!\");\n      printLine();\n      e.printStackTrace();\n      System.exit(1);\n    }\n  }\n\n  private static void printLine() {\n    for (int i = 0; i < 80; i++) {\n      System.out.print(\"=\");\n    }\n    System.out.println();\n  }\n\n  public void generate() throws IOException {\n    Map<String, CommandMetadata> commandsMetadata;\n\n    // Step 1: Retrieve commands from Redis\n    System.out.println(\"\\nStep 1: Connecting to Redis at \" + redisHost + \":\" + redisPort + \"...\");\n    try {\n      commandsMetadata = retrieveCommandsFromRedis();\n      System.out.println(\"✓ Retrieved \" + commandsMetadata.size() + \" commands from Redis\");\n\n      // Save to backup JSON file\n      saveToJsonFile(commandsMetadata);\n    } catch (JedisConnectionException e) {\n      System.err.println(\"✗ Failed to connect to Redis: \" + e.getMessage());\n      System.out.println(\"\\nAttempting to use backup JSON file: \" + BACKUP_JSON_FILE);\n      commandsMetadata = readJsonFile();\n      System.out.println(\"✓ Loaded \" + commandsMetadata.size() + \" commands from backup file\");\n    }\n\n    // Step 2: Process commands and group by metadata combinations\n    System.out.println(\"\\nStep 2: Processing commands and grouping by metadata...\");\n    Map<MetadataKey, List<String>> metadataCombinations = groupByMetadata(commandsMetadata);\n    System.out.println(\"✓ Found \" + metadataCombinations.size() + \" unique metadata combinations\");\n\n    // Step 3: Generate StaticCommandFlagsRegistry class\n    System.out.println(\"\\nStep 3: Generating StaticCommandFlagsRegistry class...\");\n    String classContent = generateRegistryClass(metadataCombinations);\n    System.out.println(\"✓ Generated \" + classContent.split(\"\\n\").length + \" lines of code\");\n\n    // Step 4: Write StaticCommandFlagsRegistry.java\n    System.out.println(\"\\nStep 4: Writing \" + JAVA_FILE + \"...\");\n    writeJavaFile(classContent);\n    System.out.println(\"✓ Successfully created StaticCommandFlagsRegistry.java\");\n  }\n\n  private Map<String, CommandMetadata> retrieveCommandsFromRedis() {\n    Map<String, CommandMetadata> result = new LinkedHashMap<>();\n\n    try (Jedis jedis = new Jedis(redisHost, redisPort)) {\n\n      jedis.connect();\n      jedis.auth(Endpoints.getRedisEndpoint(\"standalone0\").getPassword());\n\n      // Collect server metadata\n      String infoServer = jedis.info(\"server\");\n      String version = extractInfoValue(infoServer, \"redis_version\");\n      String mode = extractInfoValue(infoServer, \"redis_mode\");\n\n      // Get loaded modules\n      List<String> modules = new ArrayList<>();\n      try {\n        List<Module> moduleList = jedis.moduleList();\n        for (Module module : moduleList) {\n          modules.add(module.getName());\n        }\n      } catch (Exception e) {\n        // Module list might not be available in all Redis versions\n        System.out.println(\"  Note: Could not retrieve module list: \" + e.getMessage());\n      }\n\n      serverMetadata = new ServerMetadata(version, mode, modules);\n\n      // Get all commands using COMMAND\n      Map<String, CommandInfo> commands = jedis.command();\n\n      for (Map.Entry<String, CommandInfo> entry : commands.entrySet()) {\n        CommandInfo cmdInfo = entry.getValue();\n        String commandName = normalizeCommandName(cmdInfo.getName());\n\n        // Check for subcommands\n        Map<String, CommandInfo> subcommands = cmdInfo.getSubcommands();\n\n        if (subcommands != null && !subcommands.isEmpty()) {\n          // This command has subcommands - add parent command with its flags first\n          if (!shouldExcludeCommand(commandName)) {\n            CommandMetadata parentMetadata = extractCommandMetadata(cmdInfo);\n            result.put(commandName, parentMetadata);\n          }\n\n          // Then process subcommands\n          for (Map.Entry<String, CommandInfo> subEntry : subcommands.entrySet()) {\n            CommandInfo subCmdInfo = subEntry.getValue();\n            String subCommandName = normalizeCommandName(subCmdInfo.getName());\n\n            // Filter out unwanted commands\n            if (shouldExcludeCommand(subCommandName)) {\n              continue;\n            }\n\n            CommandMetadata metadata = extractCommandMetadata(subCmdInfo);\n            result.put(subCommandName, metadata);\n          }\n        } else {\n          // Regular command without subcommands\n          // Filter out unwanted commands\n          if (!shouldExcludeCommand(commandName)) {\n            CommandMetadata metadata = extractCommandMetadata(cmdInfo);\n            result.put(commandName, metadata);\n          }\n        }\n      }\n\n    }\n    // Ignore close errors\n\n    return result;\n  }\n\n  /**\n   * Extract command metadata (flags, request_policy, response_policy) from CommandInfo.\n   */\n  private CommandMetadata extractCommandMetadata(CommandInfo cmdInfo) {\n    // Get flags\n    List<String> flags = new ArrayList<>();\n    if (cmdInfo.getFlags() != null) {\n      for (String flag : cmdInfo.getFlags()) {\n        flags.add(flag.toLowerCase());\n      }\n    }\n\n    // Extract request_policy and response_policy from tips\n    String requestPolicy = null;\n    String responsePolicy = null;\n    List<String> tips = cmdInfo.getTips();\n    if (tips != null) {\n      for (String tip : tips) {\n        String tipLower = tip.toLowerCase();\n        if (tipLower.startsWith(\"request_policy:\")) {\n          requestPolicy = tipLower.substring(\"request_policy:\".length());\n        } else if (tipLower.startsWith(\"response_policy:\")) {\n          responsePolicy = tipLower.substring(\"response_policy:\".length());\n        }\n      }\n    }\n\n    return new CommandMetadata(flags, requestPolicy, responsePolicy);\n  }\n\n  /**\n   * Normalize command name: replace pipe separators with spaces and convert to uppercase. Redis\n   * returns command names like \"acl|help\" but Jedis uses \"ACL HELP\".\n   */\n  private String normalizeCommandName(String commandName) {\n    return commandName.replace('|', ' ').toUpperCase();\n  }\n\n  /**\n   * Check if a command should be excluded from the registry.\n   * <p>\n   * Exclusion rules:\n   * <ul>\n   * <li>All HELP subcommands (e.g., \"ACL HELP\", \"CONFIG HELP\", \"XINFO HELP\")</li>\n   * <li>All FT.DEBUG subcommands (e.g., \"FT.DEBUG DUMP_TERMS\", \"FT.DEBUG GIT_SHA\")</li>\n   * <li>All _FT.DEBUG subcommands (internal RediSearch debug commands)</li>\n   * </ul>\n   */\n  private boolean shouldExcludeCommand(String commandName) {\n    // Exclude all HELP subcommands\n    if (commandName.endsWith(\" HELP\")) {\n      return true;\n    }\n\n    // Exclude FT.DEBUG and _FT.DEBUG subcommands\n    return commandName.startsWith(\"FT.DEBUG \") || commandName.startsWith(\"_FT.DEBUG \")\n        || commandName.startsWith(\"_FT.CONFIG \");\n  }\n\n  private String extractInfoValue(String info, String key) {\n    String[] lines = info.split(\"\\n\");\n    for (String line : lines) {\n      if (line.startsWith(key + \":\")) {\n        return line.substring(key.length() + 1).trim();\n      }\n    }\n    return \"unknown\";\n  }\n\n  private void saveToJsonFile(Map<String, CommandMetadata> commandsMetadata) throws IOException {\n    Gson gson = new Gson();\n    String json = gson.toJson(commandsMetadata);\n\n    Path jsonPath = Paths.get(BACKUP_JSON_FILE);\n    Files.write(jsonPath, json.getBytes(StandardCharsets.UTF_8));\n    System.out.println(\"✓ Saved backup to \" + BACKUP_JSON_FILE);\n  }\n\n  private Map<String, CommandMetadata> readJsonFile() throws IOException {\n    Path jsonPath = Paths.get(BACKUP_JSON_FILE);\n    if (!Files.exists(jsonPath)) {\n      throw new IOException(\"Backup file not found: \" + BACKUP_JSON_FILE);\n    }\n\n    // JDK 8 compatible: read file as bytes and convert to string\n    byte[] bytes = Files.readAllBytes(jsonPath);\n    String jsonContent = new String(bytes, StandardCharsets.UTF_8);\n\n    Gson gson = new Gson();\n\n    // Parse JSON with proper type\n    Type type = new TypeToken<Map<String, CommandMetadata>>() {\n    }.getType();\n    Map<String, CommandMetadata> parsed = gson.fromJson(jsonContent, type);\n\n    return new LinkedHashMap<>(parsed);\n  }\n\n  private Map<MetadataKey, List<String>> groupByMetadata(\n      Map<String, CommandMetadata> commandsMetadata) {\n    Map<MetadataKey, List<String>> result = new LinkedHashMap<>();\n\n    for (Map.Entry<String, CommandMetadata> entry : commandsMetadata.entrySet()) {\n      String command = entry.getKey();\n      String commandUpper = command.toUpperCase();\n\n      // Check for manual override first - overrides take precedence over server metadata\n      CommandMetadata metadata;\n      if (MANUAL_OVERRIDES.containsKey(commandUpper)) {\n        metadata = MANUAL_OVERRIDES.get(commandUpper);\n        System.out.println(\"  Applying manual override for command: \" + commandUpper);\n      } else {\n        metadata = entry.getValue();\n      }\n\n      // Convert JSON flags to Java enum names and sort\n      List<String> javaFlags = metadata.flags.stream().map(f -> FLAG_MAPPING.get(f.toLowerCase()))\n          .filter(Objects::nonNull).sorted().collect(Collectors.toList());\n\n      // Convert request and response policies to Java enum names (uppercase)\n      String requestPolicy = metadata.requestPolicy != null ? metadata.requestPolicy.toUpperCase()\n          : null;\n      String responsePolicy = metadata.responsePolicy != null\n          ? metadata.responsePolicy.toUpperCase()\n          : null;\n\n      MetadataKey key = new MetadataKey(javaFlags, requestPolicy, responsePolicy);\n      result.computeIfAbsent(key, k -> new ArrayList<>()).add(commandUpper);\n    }\n\n    return result;\n  }\n\n  private String generateRegistryClass(Map<MetadataKey, List<String>> metadataCombinations) {\n    StringBuilder sb = new StringBuilder();\n\n    // Package and imports\n    sb.append(\"package redis.clients.jedis;\\n\\n\");\n    sb.append(\"import java.util.EnumSet;\\n\");\n    sb.append(\"import static redis.clients.jedis.StaticCommandFlagsRegistry.EMPTY_FLAGS;\\n\");\n    sb.append(\"import static redis.clients.jedis.CommandFlagsRegistry.CommandFlag;\\n\");\n    sb.append(\"import static redis.clients.jedis.CommandFlagsRegistry.RequestPolicy;\\n\");\n    sb.append(\"import static redis.clients.jedis.CommandFlagsRegistry.ResponsePolicy;\\n\");\n\n    // Class javadoc\n    sb.append(\"/**\\n\");\n    sb.append(\n      \" * Static implementation of CommandFlagsRegistry. This class is auto-generated by\\n\");\n    sb.append(\" * CommandFlagsRegistryGenerator. DO NOT EDIT MANUALLY.\\n\");\n\n    // Add server metadata if available\n    if (serverMetadata != null) {\n      sb.append(\" * <p>Generated from Redis Server:\\n\");\n      sb.append(\" * <ul>\\n\");\n      sb.append(\" * <li>Version: \").append(serverMetadata.version).append(\"</li>\\n\");\n      sb.append(\" * <li>Mode: \").append(serverMetadata.mode).append(\"</li>\\n\");\n      if (!serverMetadata.modules.isEmpty()) {\n        sb.append(\" * <li>Loaded Modules: \").append(String.join(\", \", serverMetadata.modules))\n            .append(\"</li>\\n\");\n      } else {\n        sb.append(\" * <li>Loaded Modules: none</li>\\n\");\n      }\n      sb.append(\" * <li>Generated at: \").append(serverMetadata.generatedAt).append(\"</li>\\n\");\n      sb.append(\" * </ul>\\n\");\n    }\n\n    sb.append(\" */\\n\");\n    sb.append(\"final class StaticCommandFlagsRegistryInitializer {\\n\\n\");\n\n    // Static initializer block\n    sb.append(\"  static void initialize(StaticCommandFlagsRegistry.Builder builder) {\\n\");\n\n    // Organize commands into parent commands and simple commands\n    Map<String, Map<String, MetadataKey>> parentCommands = new LinkedHashMap<>();\n    Map<String, MetadataKey> simpleCommands = new LinkedHashMap<>();\n\n    // Categorize commands\n    for (Map.Entry<MetadataKey, List<String>> entry : metadataCombinations.entrySet()) {\n      MetadataKey metadataKey = entry.getKey();\n      for (String command : entry.getValue()) {\n        int spaceIndex = command.indexOf(' ');\n        if (spaceIndex > 0) {\n          // This is a compound command (e.g., \"FUNCTION LOAD\")\n          String parent = command.substring(0, spaceIndex);\n          String subcommand = command.substring(spaceIndex + 1);\n\n          if (KNOWN_PARENT_COMMANDS.contains(parent)) {\n            parentCommands.computeIfAbsent(parent, k -> new LinkedHashMap<>()).put(subcommand,\n              metadataKey);\n          } else {\n            // Unknown parent command with subcommands - fail with helpful error message\n            throw new IllegalStateException(String.format(\n              \"Unknown command with subcommands encountered: '%s' (parent: '%s', subcommand: '%s'). \"\n                  + \"Command names cannot contain spaces unless the parent command is registered. \"\n                  + \"To fix this, add '%s' to the 'KNOWN_PARENT_COMMANDS' set in CommandFlagsRegistryGenerator.java.\",\n              command, parent, subcommand, parent));\n          }\n        } else {\n          // Simple command without subcommands\n          simpleCommands.put(command, metadataKey);\n        }\n      }\n    }\n\n    // Generate parent command registries\n    for (String parent : KNOWN_PARENT_COMMANDS) {\n      // Use parent command's actual metadata if available, otherwise use EMPTY_FLAGS\n      MetadataKey parentMetadata = simpleCommands.remove(parent);\n      if (parentMetadata != null) {\n        sb.append(generateRegisterCall(parent, null, parentMetadata));\n      } else {\n        sb.append(String.format(\"    builder.register(\\\"%s\\\", EMPTY_FLAGS);\\n\", parent));\n      }\n\n      Map<String, MetadataKey> subcommands = parentCommands.get(parent);\n      if (subcommands != null && !subcommands.isEmpty()) {\n        sb.append(String.format(\"    // %s subcommands\\n\", parent));\n        // Add subcommands\n        List<String> sortedSubcommands = new ArrayList<>(subcommands.keySet());\n        Collections.sort(sortedSubcommands);\n\n        for (String subcommand : sortedSubcommands) {\n          MetadataKey metadataKey = subcommands.get(subcommand);\n          sb.append(generateRegisterCall(parent, subcommand, metadataKey));\n        }\n      }\n    }\n\n    // Generate simple commands grouped by metadata\n    Map<MetadataKey, List<String>> simpleCommandsByMetadata = new LinkedHashMap<>();\n    for (Map.Entry<String, MetadataKey> entry : simpleCommands.entrySet()) {\n      simpleCommandsByMetadata.computeIfAbsent(entry.getValue(), k -> new ArrayList<>())\n          .add(entry.getKey());\n    }\n\n    // Sort by flag count, then alphabetically\n    List<Map.Entry<MetadataKey, List<String>>> sortedEntries = simpleCommandsByMetadata.entrySet()\n        .stream()\n        .sorted(\n          Comparator.comparing((Map.Entry<MetadataKey, List<String>> e) -> e.getKey().flags.size())\n              .thenComparing(e -> e.getKey().toString()))\n        .collect(Collectors.toList());\n\n    for (Map.Entry<MetadataKey, List<String>> entry : sortedEntries) {\n      MetadataKey metadataKey = entry.getKey();\n      List<String> commands = entry.getValue();\n      Collections.sort(commands);\n\n      // Add comment describing the metadata\n      sb.append(String.format(\"    // %d command(s) with: %s\\n\", commands.size(),\n        metadataKey.toDescription()));\n\n      // Add registry entries\n      for (String command : commands) {\n        sb.append(generateRegisterCall(command, null, metadataKey));\n      }\n      sb.append(\"\\n\");\n    }\n\n    // Close initializer block\n    sb.append(\"  }\\n\\n\");\n\n    // Close class\n    sb.append(\"}\\n\");\n\n    return sb.toString();\n  }\n\n  /**\n   * Generate a builder.register() call for a command with its metadata.\n   */\n  private String generateRegisterCall(String command, String subcommand, MetadataKey metadataKey) {\n    String enumSetExpr = createEnumSetExpression(metadataKey.flags);\n    String requestPolicyExpr = metadataKey.requestPolicy != null\n        ? \"RequestPolicy.\" + metadataKey.requestPolicy\n        : \"null\";\n    String responsePolicyExpr = metadataKey.responsePolicy != null\n        ? \"ResponsePolicy.\" + metadataKey.responsePolicy\n        : \"null\";\n\n    // Check if we need to use the extended register method (with policies)\n    boolean hasPolicies = metadataKey.requestPolicy != null || metadataKey.responsePolicy != null;\n\n    if (subcommand != null) {\n      // Subcommand registration\n      if (hasPolicies) {\n        return String.format(\"    builder.register(\\\"%s\\\", \\\"%s\\\", %s, %s, %s);\\n\", command,\n          subcommand, enumSetExpr, requestPolicyExpr, responsePolicyExpr);\n      } else {\n        return String.format(\"    builder.register(\\\"%s\\\", \\\"%s\\\", %s);\\n\", command, subcommand,\n          enumSetExpr);\n      }\n    } else {\n      // Simple command registration\n      if (hasPolicies) {\n        return String.format(\"    builder.register(\\\"%s\\\", %s, %s, %s);\\n\", command, enumSetExpr,\n          requestPolicyExpr, responsePolicyExpr);\n      } else {\n        return String.format(\"    builder.register(\\\"%s\\\", %s);\\n\", command, enumSetExpr);\n      }\n    }\n  }\n\n  private String createEnumSetExpression(List<String> flags) {\n    if (flags.isEmpty()) {\n      return \"EMPTY_FLAGS\";\n    } else if (flags.size() == 1) {\n      return \"EnumSet.of(CommandFlag.\" + flags.get(0) + \")\";\n    } else {\n      String flagsList = flags.stream().map(f -> \"CommandFlag.\" + f)\n          .collect(Collectors.joining(\", \"));\n      return \"EnumSet.of(\" + flagsList + \")\";\n    }\n  }\n\n  private void writeJavaFile(String classContent) throws IOException {\n    Path javaPath = Paths.get(JAVA_FILE);\n\n    // JDK 8 compatible: write string as bytes\n    Files.write(javaPath, classContent.getBytes(StandardCharsets.UTF_8));\n  }\n\n  /**\n   * Holds command metadata extracted from Redis (flags, request_policy, response_policy). Used for\n   * JSON serialization/deserialization.\n   */\n  private static class CommandMetadata {\n    final List<String> flags;\n    final String requestPolicy;\n    final String responsePolicy;\n\n    CommandMetadata(List<String> flags, String requestPolicy, String responsePolicy) {\n      this.flags = flags != null ? flags : new ArrayList<>();\n      this.requestPolicy = requestPolicy;\n      this.responsePolicy = responsePolicy;\n    }\n  }\n\n  /**\n   * Represents a unique combination of flags, request policy, and response policy for grouping\n   * commands.\n   */\n  private static class MetadataKey {\n    final List<String> flags;\n    final String requestPolicy;\n    final String responsePolicy;\n    final int hashCode;\n\n    MetadataKey(List<String> flags, String requestPolicy, String responsePolicy) {\n      this.flags = new ArrayList<>(flags);\n      this.requestPolicy = requestPolicy;\n      this.responsePolicy = responsePolicy;\n      this.hashCode = Objects.hash(this.flags, requestPolicy, responsePolicy);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) return true;\n      if (!(o instanceof MetadataKey)) return false;\n      MetadataKey that = (MetadataKey) o;\n      return flags.equals(that.flags) && Objects.equals(requestPolicy, that.requestPolicy)\n          && Objects.equals(responsePolicy, that.responsePolicy);\n    }\n\n    @Override\n    public int hashCode() {\n      return hashCode;\n    }\n\n    @Override\n    public String toString() {\n      return String.format(\"flags=%s, request=%s, response=%s\", flags, requestPolicy,\n        responsePolicy);\n    }\n\n    /**\n     * Generate a human-readable description for comments.\n     */\n    String toDescription() {\n      StringBuilder sb = new StringBuilder();\n      if (flags.isEmpty()) {\n        sb.append(\"no flags\");\n      } else {\n        sb.append(flags.stream().map(String::toLowerCase).collect(Collectors.joining(\", \")));\n      }\n      if (requestPolicy != null) {\n        sb.append(\"; request_policy=\").append(requestPolicy.toLowerCase());\n      }\n      if (responsePolicy != null) {\n        sb.append(\"; response_policy=\").append(responsePolicy.toLowerCase());\n      }\n      return sb.toString();\n    }\n  }\n\n  /**\n   * Holds metadata about the Redis server used for generation\n   */\n  private static class ServerMetadata {\n    final String version;\n    final String mode;\n    final List<String> modules;\n    final String generatedAt;\n\n    ServerMetadata(String version, String mode, List<String> modules) {\n      this.version = version;\n      this.mode = mode;\n      this.modules = modules;\n      this.generatedAt = new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss z\")\n          .format(new java.util.Date());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/codegen/README.md",
    "content": "# Code Generators\n\nThis package contains code generation tools for the Jedis project. These are **not tests** and should not be executed as part of the test suite.\n\n## CommandFlagsRegistryGenerator\n\nAutomatically generates and updates the static flags registry in `CommandObject.java` by retrieving command metadata from a running Redis server.\n\n### Purpose\n\nThe `CommandObject` class uses a static registry to map Redis command names to their flags.\n\n### How It Works\n\n1. **Connects** to a Redis server (default: localhost:6379)\n2. **Retrieves** all command metadata using the `COMMAND` command\n3. **Processes** commands and subcommands, extracting their flags\n4. **Groups** commands by their flag combinations\n5. **Generates** a static initializer block with inline `EnumSet` creation\n6. **Updates** `CommandObject.java` automatically using regex pattern matching\n7. **Saves** a backup JSON file for offline use\n\n### Prerequisites\n\n- A running Redis server (version 7.0+ recommended for full command metadata)\n- The Redis server should have all modules loaded if you want to include module commands\n\n### When to Run\n\nRun this generator whenever:\n- Upgrading to a new Redis version\n- New Redis modules are added to your server\n- Command flags are modified in Redis\n- You want to ensure the registry is up-to-date with your Redis server\n\n### Fallback Mode\n\nIf the generator cannot connect to Redis, it will automatically fall back to using the backup JSON file (`redis_commands_flags.json`) if available.\n\n### Output\n\nThe generator will:\n- ✓ Connect to Redis and retrieve command metadata\n- ✓ Process commands and subcommands\n- ✓ Group commands by flag combinations\n- ✓ Generate the complete static initializer block\n- ✓ Update `src/main/java/redis/clients/jedis/CommandObject.java` in-place\n- ✓ Save a backup JSON file for offline use\n- ✓ Preserve original command names (with spaces, dots, hyphens)\n\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/collections/JedisByteHashMapTest.java",
    "content": "package redis.clients.jedis.collections;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.util.JedisByteHashMap;\nimport redis.clients.jedis.util.JedisByteMap;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class JedisByteHashMapTest {\n  private static JedisByteHashMap map = new JedisByteHashMap();\n  private static JedisByteMap<byte[]> map2 = new JedisByteMap<>();\n\n  private byte[][] keys = { { 'k', 'e', 'y', '1' }, { 'k', 'e', 'y', '2' }, { 'k', 'e', 'y', '3' } };\n  private byte[][] vals = { { 'v', 'a', 'l', '1' }, { 'v', 'a', 'l', '2' }, { 'v', 'a', 'l', '3' } };\n\n  @BeforeEach\n  public void before() throws Exception {\n    map.clear();\n    map2.clear();\n  }\n\n  private boolean arrayContainsKey(byte[][] arr, byte[] key) {\n    for (byte[] anArr : arr) {\n      if (Arrays.equals(anArr, key)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private boolean entryContainsKV(Set<Map.Entry<byte[], byte[]>> s, byte[] key, byte[] value) {\n    for (Map.Entry<byte[], byte[]> en : s) {\n      if (Arrays.equals(en.getKey(), key) && Arrays.equals(en.getValue(), value)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private boolean entrySetSame(Set<Map.Entry<byte[], byte[]>> s1, Set<Map.Entry<byte[], byte[]>> s2) {\n    for (Map.Entry<byte[], byte[]> en1 : s1) {\n      if (!entryContainsKV(s2, en1.getKey(), en1.getValue())) {\n        return false;\n      }\n    }\n    for (Map.Entry<byte[], byte[]> en2 : s2) {\n      if (!entryContainsKV(s1, en2.getKey(), en2.getValue())) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  @Test\n  public void mapOperations() {\n    // put\n    map.put(keys[0], vals[0]);\n    assertEquals(1, map.size());\n\n    // putAll\n    Map<byte[], byte[]> kvMap = new HashMap<>();\n    kvMap.put(keys[1], vals[1]);\n    kvMap.put(keys[2], vals[2]);\n    map.putAll(kvMap);\n    assertEquals(3, map.size());\n\n    // containsKey\n    assertTrue(map.containsKey(keys[0]));\n\n    // containsValue\n    assertTrue(map.containsValue(vals[0]));\n\n    // entrySet\n    Set<Entry<byte[], byte[]>> entries = map.entrySet();\n    assertEquals(3, entries.size());\n    for (Entry<byte[], byte[]> entry : entries) {\n      assertTrue(arrayContainsKey(keys, entry.getKey()));\n      assertTrue(arrayContainsKey(vals, entry.getValue()));\n    }\n\n    // get\n    assertArrayEquals(vals[0], map.get(keys[0]));\n\n    // isEmpty\n    assertFalse(map.isEmpty());\n\n    // keySet\n    for (byte[] key : map.keySet()) {\n      assertTrue(arrayContainsKey(keys, key));\n    }\n\n    // values\n    for (byte[] value : map.values()) {\n      assertTrue(arrayContainsKey(vals, value));\n    }\n\n    // remove\n    map.remove(keys[0]);\n    assertEquals(2, map.size());\n\n    // clear\n    map.clear();\n    assertEquals(0, map.size());\n  }\n\n  @Test\n  public void serialize() throws Exception {\n    for (int i = 0; i < keys.length; i++) {\n      map.put(keys[i], vals[i]);\n    }\n\n    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();\n    ObjectOutputStream objOut = new ObjectOutputStream(byteOut);\n    objOut.writeObject(map);\n\n    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());\n    ObjectInputStream objIn = new ObjectInputStream(byteIn);\n    JedisByteHashMap mapRead = (JedisByteHashMap) objIn.readObject();\n\n    assertTrue(entrySetSame(map.entrySet(), mapRead.entrySet()));\n  }\n\n  @Test\n  public void map2Operations() {\n    // put\n    map2.put(keys[0], vals[0]);\n    assertEquals(1, map2.size());\n\n    // putAll\n    Map<byte[], byte[]> kvMap = new HashMap<>();\n    kvMap.put(keys[1], vals[1]);\n    kvMap.put(keys[2], vals[2]);\n    map2.putAll(kvMap);\n    assertEquals(3, map2.size());\n\n    // containsKey\n    assertTrue(map2.containsKey(keys[0]));\n\n    // containsValue\n    assertTrue(map2.containsValue(vals[0]));\n\n    // entrySet\n    Set<Map.Entry<byte[], byte[]>> entries = map2.entrySet();\n    assertEquals(3, entries.size());\n    for (Map.Entry<byte[], byte[]> entry : entries) {\n      assertTrue(arrayContainsKey(keys, entry.getKey()));\n      assertTrue(arrayContainsKey(vals, entry.getValue()));\n    }\n\n    // get\n    assertArrayEquals(vals[0], map2.get(keys[0]));\n\n    // isEmpty\n    assertFalse(map2.isEmpty());\n\n    // keySet\n    for (byte[] key : map2.keySet()) {\n      assertTrue(arrayContainsKey(keys, key));\n    }\n\n    // values\n    for (byte[] value : map2.values()) {\n      assertTrue(arrayContainsKey(vals, value));\n    }\n\n    // remove\n    map2.remove(keys[0]);\n    assertEquals(2, map2.size());\n\n    // clear\n    map2.clear();\n    assertEquals(0, map2.size());\n  }\n\n  @Test\n  public void serialize2() throws Exception {\n    for (int i = 0; i < keys.length; i++) {\n      map2.put(keys[i], vals[i]);\n    }\n\n    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();\n    ObjectOutputStream objOut = new ObjectOutputStream(byteOut);\n    objOut.writeObject(map2);\n\n    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());\n    ObjectInputStream objIn = new ObjectInputStream(byteIn);\n    JedisByteMap<byte[]> mapRead = (JedisByteMap<byte[]>) objIn.readObject();\n\n    assertTrue(entrySetSame(map2.entrySet(), mapRead.entrySet()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/collections/SetFromListTest.java",
    "content": "package redis.clients.jedis.collections;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class SetFromListTest {\n\n  private static Method method;\n\n  @BeforeAll\n  public static void beforeClass() throws Exception {\n    Class<?> clazz = Class.forName(\"redis.clients.jedis.BuilderFactory$SetFromList\");\n    method = clazz.getDeclaredMethod(\"of\", List.class);\n    method.setAccessible(true);\n  }\n\n  /**\n   * Instantiate SetFromList class by reflection because it is protected static inner class of\n   * BinaryJedis.\n   */\n  @SuppressWarnings(\"unchecked\")\n  private <E> Set<E> setFromList(List<E> list) throws Exception {\n    return (Set<E>) method.invoke(null, list);\n  }\n\n  @Test\n  public void setOperations() throws Exception {\n\n    // add\n    Set<String> cut = setFromList(new ArrayList<String>());\n    cut.add(\"A\");\n    cut.add(\"B\");\n    cut.add(\"A\");\n\n    assertEquals(2, cut.size());\n\n    // remove\n    cut.remove(\"A\");\n    assertEquals(1, cut.size());\n\n    cut.remove(\"C\");\n    assertEquals(1, cut.size());\n\n    // contains\n    assertTrue(cut.contains(\"B\"));\n    assertFalse(cut.contains(\"A\"));\n\n    cut.add(\"C\");\n    cut.add(\"D\");\n\n    // containsAll\n    assertTrue(cut.containsAll(cut));\n\n    // retainAll\n    cut.retainAll(Arrays.asList(\"C\", \"D\"));\n    assertEquals(2, cut.size());\n    assertTrue(cut.contains(\"C\"));\n    assertTrue(cut.contains(\"D\"));\n\n    // removeAll\n    cut.removeAll(Arrays.asList(\"C\"));\n    assertEquals(1, cut.size());\n    assertTrue(cut.contains(\"D\"));\n\n    // clear\n    cut.clear();\n    assertTrue(cut.isEmpty());\n  }\n\n  @Test\n  public void iteration() throws Exception {\n\n    List<String> list = a2z();\n\n    Set<String> cut = setFromList(list);\n\n    // ordering guarantee\n    int i = 0;\n    for (String x : cut) {\n      assertEquals(list.get(i++), x);\n    }\n  }\n\n  @Test\n  public void equals() throws Exception {\n\n    List<String> list = a2z();\n\n    Set<String> hashSet = new HashSet<String>(list);\n\n    Set<String> cut = setFromList(list);\n\n    assertEquals(hashSet, cut);\n  }\n\n  @Test\n  public void serialize() throws Exception {\n\n    Set<String> set = setFromList(a2z());\n\n    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();\n    ObjectOutputStream objOut = new ObjectOutputStream(byteOut);\n    objOut.writeObject(set);\n\n    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());\n    ObjectInputStream objIn = new ObjectInputStream(byteIn);\n\n    Set<String> setRead = (Set<String>) objIn.readObject();\n\n    assertEquals(set, setRead);\n  }\n\n  private List<String> a2z() {\n    List<String> list = new ArrayList<String>();\n\n    for (int i = 'a'; i <= 'z'; i++) {\n      list.add(String.valueOf((char) i));\n    }\n\n    Collections.shuffle(list);\n    return list;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/CommandsTestsParameters.java",
    "content": "package redis.clients.jedis.commands;\n\nimport java.util.Arrays;\nimport java.util.Collection;\n\nimport redis.clients.jedis.RedisProtocol;\n\npublic class CommandsTestsParameters {\n\n  /**\n   * RESP protocol versions we want our commands related tests to run against.\n   * {@code null} means to use the default protocol which is assumed to be RESP2.\n   */\n  public static Collection<Object[]> respVersions() {\n    return Arrays.asList(\n        new Object[]{ null },\n        new Object[]{ RedisProtocol.RESP2 },\n        new Object[]{ RedisProtocol.RESP3 }\n    );\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsBitmapCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.equalTo;\n\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=bitmap\">Bitmap</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsBitmapCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsBitmapCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testSetbitAndGetbit() {\n    String key = \"bitKey\";\n    long offset = 10;\n\n    Boolean initialValue = exec(commandObjects.getbit(key, offset));\n    assertThat(initialValue, equalTo(false));\n\n    Boolean setbit = exec(commandObjects.setbit(key, offset, true));\n    assertThat(setbit, equalTo(false)); // original value returned\n\n    Boolean finalValue = exec(commandObjects.getbit(key, offset));\n    assertThat(finalValue, equalTo(true));\n  }\n\n  @Test\n  public void testSetbitAndGetbitBinary() {\n    byte[] key = \"bitKeyBytes\".getBytes();\n    long offset = 10;\n\n    Boolean initialValue = exec(commandObjects.getbit(key, offset));\n    assertThat(initialValue, equalTo(false));\n\n    Boolean setbit = exec(commandObjects.setbit(key, offset, true));\n    assertThat(setbit, equalTo(false)); // original value returned\n\n    Boolean finalValue = exec(commandObjects.getbit(key, offset));\n    assertThat(finalValue, equalTo(true));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Starting with Redis version 7.0.0: Added the BYTE|BIT option.\")\n  public void testBitcount() {\n    String key = \"bitcountKey\";\n    byte[] keyBytes = key.getBytes();\n\n    // Set some bits\n    exec(commandObjects.setbit(key, 1, true));\n    exec(commandObjects.setbit(key, 2, true));\n    exec(commandObjects.setbit(key, 7, true)); // This makes 1 byte with 3 bits set\n    exec(commandObjects.setbit(key, 8, true)); // Next byte, first bit set\n\n    Long bitcountFullString = exec(commandObjects.bitcount(key));\n    assertThat(bitcountFullString, equalTo(4L));\n\n    Long bitcountFirstByte = exec(commandObjects.bitcount(key, 0, 0));\n    assertThat(bitcountFirstByte, equalTo(3L));\n\n    Long bitcountFullStringBinary = exec(commandObjects.bitcount(keyBytes));\n    assertThat(bitcountFullStringBinary, equalTo(4L));\n\n    Long bitcountFirstByteBinary = exec(commandObjects.bitcount(keyBytes, 0, 0));\n    assertThat(bitcountFirstByteBinary, equalTo(3L));\n\n    Long bitcountFirstSixBits = exec(commandObjects.bitcount(key, 0, 5, BitCountOption.BIT));\n    assertThat(bitcountFirstSixBits, equalTo(2L));\n\n    Long bitcountFirstSixBitsBinary = exec(commandObjects.bitcount(keyBytes, 0, 5, BitCountOption.BIT));\n    assertThat(bitcountFirstSixBitsBinary, equalTo(2L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message=\"Starting with Redis version 7.0.0: Added the BYTE|BIT option.\")\n  public void testBitpos() {\n    String key = \"bitposKey\";\n    byte[] keyBytes = key.getBytes();\n\n    // Set some bits\n    exec(commandObjects.setbit(key, 10, true));\n    exec(commandObjects.setbit(key, 22, true));\n    exec(commandObjects.setbit(key, 30, true));\n\n    Long firstSetBit = exec(commandObjects.bitpos(key, true));\n    assertThat(firstSetBit, equalTo(10L));\n\n    Long firstUnsetBit = exec(commandObjects.bitpos(key, false));\n    assertThat(firstUnsetBit, equalTo(0L));\n\n    BitPosParams params = new BitPosParams(15, 25).modifier(BitCountOption.BIT);\n\n    Long firstSetBitInRange = exec(commandObjects.bitpos(key, true, params));\n    assertThat(firstSetBitInRange, equalTo(22L));\n\n    Long firstUnsetBitInRange = exec(commandObjects.bitpos(key, false, params));\n    assertThat(firstUnsetBitInRange, equalTo(15L));\n\n    Long firstSetBitBinary = exec(commandObjects.bitpos(keyBytes, true));\n    assertThat(firstSetBitBinary, equalTo(10L));\n\n    Long firstUnsetBitBinary = exec(commandObjects.bitpos(keyBytes, false));\n    assertThat(firstUnsetBitBinary, equalTo(0L));\n\n    Long firstSetBitInRangeBinary = exec(commandObjects.bitpos(keyBytes, true, params));\n    assertThat(firstSetBitInRangeBinary, equalTo(22L));\n\n    Long firstUnsetBitInRangeBinary = exec(commandObjects.bitpos(keyBytes, false, params));\n    assertThat(firstUnsetBitInRangeBinary, equalTo(15L));\n  }\n\n  @Test\n  public void testBitfield() {\n    String key = \"bitfieldKey\";\n\n    List<Long> bitfieldResult = exec(commandObjects.bitfield(\n        key, \"INCRBY\", \"i5\", \"100\", \"7\", \"GET\", \"i5\", \"100\"));\n\n    // Contains the result of the INCRBY operation, and the result of the GET operation.\n    assertThat(bitfieldResult, contains(7L, 7L));\n\n    List<Long> bitfieldRoResult = exec(commandObjects.bitfieldReadonly(\n        key, \"GET\", \"i4\", \"100\"));\n    assertThat(bitfieldRoResult, contains(3L));\n  }\n\n  @Test\n  public void testBitfieldBinary() {\n    byte[] key = \"bitfieldKeyBytes\".getBytes();\n\n    List<Long> bitfieldResult = exec(commandObjects.bitfield(key,\n        \"INCRBY\".getBytes(), \"i5\".getBytes(), \"100\".getBytes(), \"7\".getBytes(),\n        \"GET\".getBytes(), \"i5\".getBytes(), \"100\".getBytes()));\n\n    // Contains the result of the INCRBY operation, and the result of the GET operation.\n    assertThat(bitfieldResult, contains(7L, 7L));\n\n    List<Long> bitfieldRoResult = exec(commandObjects.bitfieldReadonly(key,\n        \"GET\".getBytes(), \"i4\".getBytes(), \"100\".getBytes()));\n    assertThat(bitfieldRoResult, contains(3L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBitop() {\n    String srcKey1 = \"srcKey1\";\n    String srcKey2 = \"srcKey2\";\n    String destKey = \"destKey\";\n\n    // Set some bits\n    exec(commandObjects.setbit(srcKey1, 1, true));\n    exec(commandObjects.setbit(srcKey1, 2, true));\n    exec(commandObjects.setbit(srcKey1, 3, true));\n\n    exec(commandObjects.setbit(srcKey2, 1, true));\n    exec(commandObjects.setbit(srcKey2, 3, true));\n\n    Long bitopResult = exec(commandObjects.bitop(BitOP.AND, destKey, srcKey1, srcKey2));\n    assertThat(bitopResult, equalTo(1L)); // 1 byte stored\n\n    assertThat(exec(commandObjects.getbit(destKey, 1)), equalTo(true));\n    assertThat(exec(commandObjects.getbit(destKey, 2)), equalTo(false));\n    assertThat(exec(commandObjects.getbit(destKey, 3)), equalTo(true));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBitopBinary() {\n    byte[] srcKey1 = \"srcKey1\".getBytes();\n    byte[] srcKey2 = \"srcKey2\".getBytes();\n    byte[] destKey = \"destKey\".getBytes();\n\n    // Set some bits\n    exec(commandObjects.setbit(srcKey1, 1, true));\n    exec(commandObjects.setbit(srcKey1, 2, true));\n    exec(commandObjects.setbit(srcKey1, 3, true));\n\n    exec(commandObjects.setbit(srcKey2, 1, true));\n    exec(commandObjects.setbit(srcKey2, 3, true));\n\n    Long bitopResult = exec(commandObjects.bitop(BitOP.XOR, destKey, srcKey1, srcKey2));\n    assertThat(bitopResult, equalTo(1L)); // 1 byte stored\n\n    assertThat(exec(commandObjects.getbit(new String(destKey), 1)), equalTo(false));\n    assertThat(exec(commandObjects.getbit(new String(destKey), 2)), equalTo(true));\n    assertThat(exec(commandObjects.getbit(new String(destKey), 3)), equalTo(false));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsBloomFilterCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.notNullValue;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.bloom.BFInsertParams;\nimport redis.clients.jedis.bloom.BFReserveParams;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=bf\">Bloom Filter</a> commands.\n */\npublic class CommandObjectsBloomFilterCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsBloomFilterCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testBfAddAndExists() {\n    String key = \"testBf\";\n\n    String reserve = exec(commandObjects.bfReserve(key, 0.01, 1000));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    boolean add = exec(commandObjects.bfAdd(key, \"item1\"));\n    assertThat(add, equalTo(true));\n\n    boolean exists = exec(commandObjects.bfExists(key, \"item1\"));\n    assertThat(exists, equalTo(true));\n\n    boolean notExists = exec(commandObjects.bfExists(key, \"item2\"));\n    assertThat(notExists, equalTo(false));\n  }\n\n  @Test\n  public void testBfInsert() {\n    String key = \"testBf\";\n\n    String reserve = exec(commandObjects.bfReserve(key, 0.01, 1000));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    List<Boolean> insert = exec(commandObjects.bfInsert(key, \"item1\", \"item2\"));\n    assertThat(insert, contains(true, true));\n\n    BFInsertParams insertParams = new BFInsertParams().noCreate().capacity(1000);\n\n    List<Boolean> insertWithParams = exec(commandObjects.bfInsert(key, insertParams, \"item1\", \"item2\"));\n    assertThat(insertWithParams, contains(false, false));\n\n    assertThat(exec(commandObjects.bfExists(key, \"item1\")), equalTo(true));\n    assertThat(exec(commandObjects.bfExists(key, \"item2\")), equalTo(true));\n    assertThat(exec(commandObjects.bfExists(key, \"item3\")), equalTo(false));\n  }\n\n  @Test\n  public void testBfMAddMExistsAndCard() {\n    String key = \"testBf\";\n\n    String reserve = exec(commandObjects.bfReserve(key, 0.01, 1000));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    List<Boolean> mAdd = exec(commandObjects.bfMAdd(key, \"item1\", \"item2\", \"item3\"));\n    assertThat(mAdd, contains(true, true, true));\n\n    List<Boolean> mExists = exec(commandObjects.bfMExists(key, \"item1\", \"item2\", \"item3\", \"item4\"));\n    assertThat(mExists, contains(true, true, true, false));\n\n    Long card = exec(commandObjects.bfCard(key));\n    assertThat(card, equalTo(3L));\n  }\n\n  @Test\n  public void testBfScanDumpAndLoadChunk() {\n    String key = \"test\";\n\n    String reserve = exec(commandObjects.bfReserve(key, 0.01, 5000));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    for (int i = 0; i < 1000; i++) {\n      Boolean add = exec(commandObjects.bfAdd(key, \"item\" + i));\n      assertThat(add, equalTo(true));\n    }\n\n    String newKey = \"testBfLoadChunk\";\n\n    long iterator = 0;\n    do {\n      Map.Entry<Long, byte[]> scanDumpResult = exec(commandObjects.bfScanDump(key, iterator));\n\n      iterator = scanDumpResult.getKey();\n\n      if (iterator > 0) {\n        byte[] data = scanDumpResult.getValue();\n\n        assertThat(data, notNullValue());\n\n        String loadChunk = exec(commandObjects.bfLoadChunk(newKey, iterator, data));\n        assertThat(loadChunk, equalTo(\"OK\"));\n      }\n    } while (iterator != 0);\n\n    // verify destination\n    for (int i = 0; i < 1000; i++) {\n      Boolean exists = exec(commandObjects.bfExists(newKey, \"item\" + i));\n      assertThat(exists, equalTo(true));\n    }\n\n    Boolean missingItem = exec(commandObjects.bfExists(newKey, \"item1001\"));\n    assertThat(missingItem, equalTo(false));\n  }\n\n  @Test\n  public void testBfInfo() {\n    String key = \"testBf\";\n\n    double errorRate = 0.01;\n    long capacity = 1000;\n    BFReserveParams reserveParams = new BFReserveParams().expansion(2);\n\n    String reserve = exec(commandObjects.bfReserve(key, errorRate, capacity, reserveParams));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    Boolean add = exec(commandObjects.bfAdd(key, \"item1\"));\n    assertThat(add, equalTo(true));\n\n    Map<String, Object> info = exec(commandObjects.bfInfo(key));\n    assertThat(info, hasEntry(\"Capacity\", 1000L));\n    assertThat(info, hasEntry(\"Number of items inserted\", 1L));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsCountMinSketchCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.hamcrest.Matchers.notNullValue;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=cms\">Count-min sketch</a> commands.\n */\npublic class CommandObjectsCountMinSketchCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsCountMinSketchCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testIncrByAndQuery() {\n    String key = \"testCMS\";\n\n    String init = exec(commandObjects.cmsInitByDim(key, 10000, 5));\n    assertThat(init, equalTo(\"OK\"));\n\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"apple\", 30L);\n    itemIncrements.put(\"banana\", 20L);\n    itemIncrements.put(\"carrot\", 10L);\n\n    List<Long> incrBy = exec(commandObjects.cmsIncrBy(key, itemIncrements));\n    // due to Map's unpredictable order, we can't assert ordering of the result\n    assertThat(incrBy, containsInAnyOrder(10L, 20L, 30L));\n\n    List<Long> query = exec(commandObjects.cmsQuery(key, \"apple\", \"banana\", \"carrot\", \"date\"));\n\n    assertThat(query, notNullValue());\n    assertThat(query.size(), equalTo(4));\n\n    assertThat(query.get(0), greaterThanOrEqualTo(30L)); // apple\n    assertThat(query.get(1), greaterThanOrEqualTo(20L)); // banana\n    assertThat(query.get(2), greaterThanOrEqualTo(10L)); // carrot\n    assertThat(query.get(3), lessThanOrEqualTo(1L)); // date, in practice, could be >0 due to estimation error\n  }\n\n  @Test\n  public void testCMSInitByProb() {\n    String key = \"testCMS\";\n\n    String init = exec(commandObjects.cmsInitByProb(key, 0.01, 0.99));\n    assertThat(init, equalTo(\"OK\"));\n\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"apple\", 5L);\n    itemIncrements.put(\"banana\", 3L);\n    itemIncrements.put(\"carrot\", 8L);\n\n    List<Long> incrBy = exec(commandObjects.cmsIncrBy(key, itemIncrements));\n    assertThat(incrBy, containsInAnyOrder(3L, 5L, 8L));\n\n    List<Long> query = exec(commandObjects.cmsQuery(key, \"apple\", \"banana\", \"carrot\", \"dragonfruit\"));\n\n    assertThat(query, notNullValue());\n    assertThat(query.size(), equalTo(4));\n\n    assertThat(query.get(0), greaterThanOrEqualTo(5L)); // apple\n    assertThat(query.get(1), greaterThanOrEqualTo(3L)); // banana\n    assertThat(query.get(2), greaterThanOrEqualTo(8L)); // carrot\n    // \"dragonfruit\" was not incremented, its count should be minimal, but due to the probabilistic nature of CMS, it might not be exactly 0.\n    assertThat(query.get(3), lessThanOrEqualTo(1L));\n  }\n\n  @Test\n  public void testCMSMerge() {\n    String cmsKey1 = \"testCMS1\";\n    String cmsKey2 = \"testCMS2\";\n    String cmsDestKey = \"testCMSMerged\";\n\n    long width = 10000;\n    long depth = 5;\n\n    String init1 = exec(commandObjects.cmsInitByDim(cmsKey1, width, depth));\n    assertThat(init1, equalTo(\"OK\"));\n\n    String init2 = exec(commandObjects.cmsInitByDim(cmsKey2, width, depth));\n    assertThat(init2, equalTo(\"OK\"));\n\n    Map<String, Long> itemIncrements1 = new HashMap<>();\n    itemIncrements1.put(\"apple\", 2L);\n    itemIncrements1.put(\"banana\", 3L);\n\n    List<Long> incrBy1 = exec(commandObjects.cmsIncrBy(cmsKey1, itemIncrements1));\n    assertThat(incrBy1, containsInAnyOrder(2L, 3L));\n\n    Map<String, Long> itemIncrements2 = new HashMap<>();\n    itemIncrements2.put(\"carrot\", 5L);\n    itemIncrements2.put(\"date\", 4L);\n\n    List<Long> incrBy2 = exec(commandObjects.cmsIncrBy(cmsKey2, itemIncrements2));\n    assertThat(incrBy2, containsInAnyOrder(4L, 5L));\n\n    String init3 = exec(commandObjects.cmsInitByDim(cmsDestKey, width, depth));\n    assertThat(init3, equalTo(\"OK\"));\n\n    String merge = exec(commandObjects.cmsMerge(cmsDestKey, cmsKey1, cmsKey2));\n    assertThat(merge, equalTo(\"OK\"));\n\n    List<Long> query = exec(commandObjects.cmsQuery(cmsDestKey, \"apple\", \"banana\", \"carrot\", \"date\"));\n\n    assertThat(query, notNullValue());\n    assertThat(query.size(), equalTo(4));\n\n    assertThat(query.get(0), greaterThanOrEqualTo(2L)); // apple\n    assertThat(query.get(1), greaterThanOrEqualTo(3L)); // banana\n    assertThat(query.get(2), greaterThanOrEqualTo(5L)); // carrot\n    assertThat(query.get(3), greaterThanOrEqualTo(4L)); // date\n  }\n\n  @Test\n  public void testCMSMergeWithWeights() {\n    String cmsKey1 = \"testCMS1\";\n    String cmsKey2 = \"testCMS2\";\n    String cmsDestKey = \"testCMSMerged\";\n\n    long width = 10000;\n    long depth = 5;\n\n    String init1 = exec(commandObjects.cmsInitByDim(cmsKey1, width, depth));\n    assertThat(init1, equalTo(\"OK\"));\n\n    String init2 = exec(commandObjects.cmsInitByDim(cmsKey2, width, depth));\n    assertThat(init2, equalTo(\"OK\"));\n\n    Map<String, Long> itemIncrements1 = new HashMap<>();\n    itemIncrements1.put(\"apple\", 2L);\n    itemIncrements1.put(\"banana\", 3L);\n\n    List<Long> incrBy1 = exec(commandObjects.cmsIncrBy(cmsKey1, itemIncrements1));\n    assertThat(incrBy1, containsInAnyOrder(2L, 3L));\n\n    Map<String, Long> itemIncrements2 = new HashMap<>();\n    itemIncrements2.put(\"carrot\", 5L);\n    itemIncrements2.put(\"date\", 4L);\n\n    List<Long> incrBy2 = exec(commandObjects.cmsIncrBy(cmsKey2, itemIncrements2));\n    assertThat(incrBy2, containsInAnyOrder(4L, 5L));\n\n    String init3 = exec(commandObjects.cmsInitByDim(cmsDestKey, width, depth));\n    assertThat(init3, equalTo(\"OK\"));\n\n    // Weights for the CMS keys to be merged\n    Map<String, Long> keysAndWeights = new HashMap<>();\n    keysAndWeights.put(cmsKey1, 1L);\n    keysAndWeights.put(cmsKey2, 2L);\n\n    String merge = exec(commandObjects.cmsMerge(cmsDestKey, keysAndWeights));\n    assertThat(merge, equalTo(\"OK\"));\n\n    List<Long> query = exec(commandObjects.cmsQuery(cmsDestKey, \"apple\", \"banana\", \"carrot\", \"date\"));\n\n    assertThat(query, notNullValue());\n    assertThat(query.size(), equalTo(4));\n\n    assertThat(query.get(0), greaterThanOrEqualTo(2L)); // apple, weight of 1\n    assertThat(query.get(1), greaterThanOrEqualTo(3L)); // banana, weight of 1\n    assertThat(query.get(2), greaterThanOrEqualTo(10L)); // carrot, weight of 2, so 5 * 2\n    assertThat(query.get(3), greaterThanOrEqualTo(8L)); // date, weight of 2, so 4 * 2\n  }\n\n  @Test\n  public void testCMSInfo() {\n    String key = \"testCMS\";\n\n    long width = 10000;\n    long depth = 5;\n\n    String init = exec(commandObjects.cmsInitByDim(key, width, depth));\n    assertThat(init, equalTo(\"OK\"));\n\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"apple\", 3L);\n    itemIncrements.put(\"banana\", 2L);\n    itemIncrements.put(\"carrot\", 1L);\n\n    List<Long> incrBy = exec(commandObjects.cmsIncrBy(key, itemIncrements));\n    assertThat(incrBy, hasSize(3));\n\n    Map<String, Object> info = exec(commandObjects.cmsInfo(key));\n\n    assertThat(info, hasEntry(\"width\", 10000L));\n    assertThat(info, hasEntry(\"depth\", 5L));\n    assertThat(info, hasEntry(\"count\", 6L)); // 3 + 2 + 1\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsCuckooFilterCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.everyItem;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.bloom.CFInsertParams;\nimport redis.clients.jedis.bloom.CFReserveParams;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=cf\">Cuckoo filter</a> commands.\n */\npublic class CommandObjectsCuckooFilterCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsCuckooFilterCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testCuckooFilterAdd() {\n    String key = \"testCuckooFilter\";\n\n    String reserve = exec(commandObjects.cfReserve(key, 1000));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    Boolean add = exec(commandObjects.cfAdd(key, \"apple\"));\n    assertThat(add, equalTo(true));\n\n    Boolean addNx = exec(commandObjects.cfAddNx(key, \"apple\"));\n    assertThat(addNx, equalTo(false)); // \"apple\" already exists, NX makes this fail\n\n    Boolean addNx2 = exec(commandObjects.cfAddNx(key, \"banana\"));\n    assertThat(addNx2, equalTo(true));\n\n    Long count = exec(commandObjects.cfCount(key, \"apple\"));\n    assertThat(count, greaterThanOrEqualTo(1L));\n  }\n\n  @Test\n  public void testCuckooFilterReserveInsertAndCount() {\n    String key = \"testCuckooFilterAdvanced\";\n\n    CFReserveParams reserveParams = new CFReserveParams()\n        .bucketSize(4).maxIterations(500).expansion(1);\n\n    String reserve = exec(commandObjects.cfReserve(key, 5000, reserveParams));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    List<Boolean> insert = exec(commandObjects.cfInsert(\n        key, \"apple\", \"banana\", \"carrot\", \"date\"));\n    assertThat(insert, everyItem(equalTo(true)));\n\n    CFInsertParams insertParams = new CFInsertParams().noCreate();\n\n    List<Boolean> insertWithParams = exec(commandObjects.cfInsert(\n        key, insertParams, \"eggplant\", \"fig\", \"grape\", \"apple\"));\n    assertThat(insertWithParams, everyItem(equalTo(true)));\n\n    Long countApple = exec(commandObjects.cfCount(key, \"apple\"));\n    assertThat(countApple, greaterThanOrEqualTo(2L));\n\n    Long countBanana = exec(commandObjects.cfCount(key, \"banana\"));\n    assertThat(countBanana, greaterThanOrEqualTo(1L));\n\n    Long countNonExisting = exec(commandObjects.cfCount(key, \"watermelon\"));\n    assertThat(countNonExisting, equalTo(0L));\n  }\n\n  @Test\n  public void testCuckooFilterInsertNx() {\n    String key = \"testCf\";\n\n    String[] items = { \"item1\", \"item2\", \"item3\" };\n\n    CFInsertParams insertParams = new CFInsertParams().capacity(1000L).noCreate();\n\n    List<Boolean> insertNx1 = exec(commandObjects.cfInsertNx(key, items));\n    assertThat(insertNx1, not(empty()));\n    assertThat(insertNx1, everyItem(equalTo(true)));\n\n    long countAfterFirstInsert = exec(commandObjects.cfCount(key, \"item1\"));\n    assertThat(countAfterFirstInsert, greaterThanOrEqualTo(1L));\n\n    List<Boolean> insertNx2 = exec(commandObjects.cfInsertNx(key, insertParams, items));\n    assertThat(insertNx2, not(empty()));\n    assertThat(insertNx2, everyItem(equalTo(false)));\n\n    long countAfterSecondInsert = exec(commandObjects.cfCount(key, \"item1\"));\n    assertThat(countAfterSecondInsert, greaterThanOrEqualTo(1L)); // count should remain the same\n  }\n\n  @Test\n  public void testCuckooFilterExistsAndDel() {\n    String key = \"testCf\";\n    String item = \"item1\";\n\n    boolean existsBeforeInsert = exec(commandObjects.cfExists(key, item));\n    assertThat(existsBeforeInsert, equalTo(false));\n\n    Boolean add = exec(commandObjects.cfAdd(key, item));\n    assertThat(add, equalTo(true));\n\n    boolean existsAfterInsert = exec(commandObjects.cfExists(key, item));\n    assertThat(existsAfterInsert, equalTo(true));\n\n    boolean delete = exec(commandObjects.cfDel(key, item));\n    assertThat(delete, equalTo(true));\n\n    boolean existsAfterDelete = exec(commandObjects.cfExists(key, item));\n    assertThat(existsAfterDelete, equalTo(false));\n  }\n\n  @Test\n  public void testCuckooFilterMExists() {\n    String key = \"testCf\";\n\n    exec(commandObjects.cfInsert(key, \"item1\", \"item2\", \"item3\"));\n\n    List<Boolean> mExists = exec(commandObjects.cfMExists(\n        key, \"item1\", \"item2\", \"item3\", \"item4\", \"item5\"));\n\n    assertThat(mExists, contains(true, true, true, false, false));\n  }\n\n  @Test\n  public void testCuckooFilterScanDumpAndLoadChunk() {\n    long capacity = 5000;\n\n    CFReserveParams reserveParams = new CFReserveParams()\n        .bucketSize(4).maxIterations(500).expansion(1);\n\n    String key = \"testCf\";\n\n    String reserve = exec(commandObjects.cfReserve(key, capacity, reserveParams));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    // add some items to the source\n    for (int i = 0; i < 1000; i++) {\n      exec(commandObjects.cfAdd(key, \"item\" + i));\n    }\n\n    String newKey = \"testCfLoadChunk\";\n\n    // scandump and load\n    long iterator = 0;\n    do {\n      Map.Entry<Long, byte[]> scanDumpResult = exec(commandObjects.cfScanDump(key, iterator));\n\n      iterator = scanDumpResult.getKey();\n      if (iterator > 0) {\n        byte[] data = scanDumpResult.getValue();\n        assertThat(data, notNullValue());\n\n        String loadChunk = exec(commandObjects.cfLoadChunk(newKey, iterator, data));\n        assertThat(loadChunk, equalTo(\"OK\"));\n      }\n    } while (iterator != 0);\n\n    // verify destination\n    for (int i = 0; i < 1000; i++) {\n      boolean exists = exec(commandObjects.cfExists(newKey, \"item\" + i));\n      assertThat(exists, equalTo(true));\n    }\n\n    boolean missingItem = exec(commandObjects.cfExists(newKey, \"item1001\"));\n    assertThat(missingItem, equalTo(false));\n  }\n\n  @Test\n  public void testCuckooFilterInfo() {\n    String key = \"testCfInfo\";\n\n    exec(commandObjects.cfReserve(key, 1000));\n\n    exec(commandObjects.cfAdd(key, \"item1\"));\n\n    Map<String, Object> info = exec(commandObjects.cfInfo(key));\n\n    assertThat(info, hasEntry(\"Number of items inserted\", 1L));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsGenericCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=generic\">Generic</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsGenericCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsGenericCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testExists() {\n    String key1 = \"existsKey1\";\n    String key2 = \"existsKey2\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key1, value));\n    exec(commandObjects.set(key2, value));\n\n    Boolean existsSingle = exec(commandObjects.exists(key1));\n    assertThat(existsSingle, equalTo(true));\n\n    Long existsMultiple = exec(commandObjects.exists(key1, key2, \"nonExistingKey\"));\n    assertThat(existsMultiple, equalTo(2L));\n\n    Boolean existsSingleByte = exec(commandObjects.exists(key1.getBytes()));\n    assertThat(existsSingleByte, equalTo(true));\n\n    Long existsMultipleByte = exec(commandObjects.exists(key1.getBytes(), key2.getBytes(), \"nonExistingKey\".getBytes()));\n    assertThat(existsMultipleByte, equalTo(2L));\n\n    Boolean existsNonExisting = exec(commandObjects.exists(\"nonExistingKey\"));\n    assertThat(existsNonExisting, equalTo(false));\n\n    Boolean existsNonExistingBytes = exec(commandObjects.exists(\"nonExistingKey\".getBytes()));\n    assertThat(existsNonExistingBytes, equalTo(false));\n  }\n\n  @Test\n  public void testPersist() {\n    String key1 = \"persistKey1\";\n    byte[] key2 = \"persistKey2\".getBytes();\n    String value = \"value\";\n    int expireTime = 10; // seconds\n\n    exec(commandObjects.setex(key1, expireTime, value));\n    exec(commandObjects.setex(key2, expireTime, value.getBytes()));\n\n    Long ttlBeforePersist1 = exec(commandObjects.ttl(key1));\n    assertThat(ttlBeforePersist1, greaterThan(0L));\n\n    Long ttlBeforePersist2 = exec(commandObjects.ttl(key2));\n    assertThat(ttlBeforePersist2, greaterThan(0L));\n\n    Long persist1 = exec(commandObjects.persist(key1));\n    assertThat(persist1, equalTo(1L));\n\n    Long persist2 = exec(commandObjects.persist(key2));\n    assertThat(persist2, equalTo(1L));\n\n    Long ttlAfterPersist1 = exec(commandObjects.ttl(key1));\n    assertThat(ttlAfterPersist1, equalTo(-1L));\n\n    Long ttlAfterPersist2 = exec(commandObjects.ttl(key2));\n    assertThat(ttlAfterPersist2, equalTo(-1L));\n  }\n\n  @Test\n  public void testType() {\n    String stringKey = \"stringKey\";\n    String listKey = \"listKey\";\n    byte[] hashKey = \"hashKey\".getBytes();\n\n    exec(commandObjects.set(stringKey, \"value\"));\n    exec(commandObjects.rpush(listKey, \"value\"));\n    exec(commandObjects.hset(hashKey, \"field\".getBytes(), \"hvalue\".getBytes()));\n\n    String stringKeyType = exec(commandObjects.type(stringKey));\n    assertThat(stringKeyType, equalTo(\"string\"));\n\n    String listKeyType = exec(commandObjects.type(listKey));\n    assertThat(listKeyType, equalTo(\"list\"));\n\n    String hashKeyType = exec(commandObjects.type(hashKey));\n    assertThat(hashKeyType, equalTo(\"hash\"));\n\n    String nonExistingKeyType = exec(commandObjects.type(\"nonExistingKey\"));\n    assertThat(nonExistingKeyType, equalTo(\"none\"));\n  }\n\n  @Test\n  public void testDumpAndRestore() {\n    String key = \"dumpRestoreKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    byte[] dumpedValue = exec(commandObjects.dump(key));\n    assertThat(dumpedValue, notNullValue());\n\n    exec(commandObjects.del(key));\n\n    Boolean existsAfterDel = exec(commandObjects.exists(key));\n    assertThat(existsAfterDel, equalTo(false));\n\n    String restore = exec(commandObjects.restore(key, 0, dumpedValue));\n    assertThat(restore, equalTo(\"OK\"));\n\n    String restoredValue = exec(commandObjects.get(key));\n    assertThat(restoredValue, equalTo(value));\n\n    Long ttlAfterRestore = exec(commandObjects.pttl(key));\n    assertThat(ttlAfterRestore, equalTo(-1L));\n\n    exec(commandObjects.del(key));\n\n    Boolean existsAfterSecondDel = exec(commandObjects.exists(key));\n    assertThat(existsAfterSecondDel, equalTo(false));\n\n    long ttl = 5000; // milliseconds\n    RestoreParams params = new RestoreParams().idleTime(500);\n\n    String restoreWithParams = exec(commandObjects.restore(key, ttl, dumpedValue, params));\n    assertThat(restoreWithParams, equalTo(\"OK\"));\n\n    String secondRestoredValue = exec(commandObjects.get(key));\n    assertThat(secondRestoredValue, equalTo(value));\n\n    Long ttlAfterSecondRestore = exec(commandObjects.pttl(key));\n    assertThat(ttlAfterSecondRestore, greaterThan(0L));\n  }\n\n  @Test\n  public void testDumpAndRestoreBinary() {\n    byte[] key = \"dumpRestoreKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    byte[] dumpedValue = exec(commandObjects.dump(key));\n    assertThat(dumpedValue, notNullValue());\n\n    exec(commandObjects.del(key));\n\n    Boolean existsAfterDel = exec(commandObjects.exists(key));\n    assertThat(existsAfterDel, equalTo(false));\n\n    String restore = exec(commandObjects.restore(key, 0, dumpedValue));\n    assertThat(restore, equalTo(\"OK\"));\n\n    byte[] restoredValue = exec(commandObjects.get(key));\n    assertThat(restoredValue, equalTo(value));\n\n    Long ttlAfterRestore = exec(commandObjects.pttl(key));\n    assertThat(ttlAfterRestore, equalTo(-1L));\n\n    exec(commandObjects.del(key));\n\n    Boolean existsAfterSecondDel = exec(commandObjects.exists(key));\n    assertThat(existsAfterSecondDel, equalTo(false));\n\n    long ttl = 5000; // milliseconds\n    RestoreParams params = new RestoreParams().idleTime(500);\n\n    String restoreWithParams = exec(commandObjects.restore(key, ttl, dumpedValue, params));\n    assertThat(restoreWithParams, equalTo(\"OK\"));\n\n    byte[] secondRestoredValue = exec(commandObjects.get(key));\n    assertThat(secondRestoredValue, equalTo(value));\n\n    Long ttlAfterSecondRestore = exec(commandObjects.pttl(key));\n    assertThat(ttlAfterSecondRestore, greaterThan(0L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireAndExpireTime() {\n    String key = \"expireKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    Long expireTimeBefore = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeBefore, equalTo(-1L));\n\n    long seconds = 60;\n\n    Long expire = exec(commandObjects.expire(key, seconds));\n    assertThat(expire, equalTo(1L));\n\n    Long expireTimeAfter = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfter, greaterThan(System.currentTimeMillis() / 1000));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireAndExpireTimeBinary() {\n    byte[] key = \"expireKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    Long expireTimeBefore = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeBefore, equalTo(-1L));\n\n    long seconds = 60;\n\n    Long expire = exec(commandObjects.expire(key, seconds));\n    assertThat(expire, equalTo(1L));\n\n    Long expireTimeAfter = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfter, greaterThan(System.currentTimeMillis() / 1000));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireWithExpiryOption() {\n    String key = \"expireWithOptionKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    Long expireTimeBefore = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeBefore, equalTo(-1L));\n\n    long seconds = 120;\n    ExpiryOption expiryOptionNX = ExpiryOption.NX;\n\n    Long expireNx = exec(commandObjects.expire(key, seconds, expiryOptionNX));\n    assertThat(expireNx, equalTo(1L));\n\n    Long expireNxAgain = exec(commandObjects.expire(key, seconds, expiryOptionNX));\n    assertThat(expireNxAgain, equalTo(0L));\n\n    Long expireTimeAfter = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfter, greaterThan(System.currentTimeMillis() / 1000));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireWithExpiryOptionTimeBinary() {\n    byte[] key = \"expireWithOptionKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    Long expireTimeBefore = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeBefore, equalTo(-1L));\n\n    long seconds = 120;\n    ExpiryOption expiryOptionNX = ExpiryOption.NX;\n\n    Long expireNx = exec(commandObjects.expire(key, seconds, expiryOptionNX));\n    assertThat(expireNx, equalTo(1L));\n\n    Long expireNxAgain = exec(commandObjects.expire(key, seconds, expiryOptionNX));\n    assertThat(expireNxAgain, equalTo(0L));\n\n    Long expireTimeAfter = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfter, greaterThan(System.currentTimeMillis() / 1000));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testPexpireAndPexpireTime() {\n    String key = \"pexpireKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    long expireTimeMillis = 15000; // 15 seconds\n\n    Long pexpireTimeBefore = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeBefore, equalTo(-1L));\n\n    Long pexpire = exec(commandObjects.pexpire(key, expireTimeMillis));\n    assertThat(pexpire, equalTo(1L));\n\n    Long pexpireTimeAfter = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfter, greaterThan(System.currentTimeMillis()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testPexpireAndPexpireTimeBinary() {\n    byte[] key = \"pexpireKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    long expireTimeMillis = 15000; // 15 seconds\n\n    Long pexpireTimeBefore = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeBefore, equalTo(-1L));\n\n    Long pexpire = exec(commandObjects.pexpire(key, expireTimeMillis));\n    assertThat(pexpire, equalTo(1L));\n\n    Long pexpireTimeAfter = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfter, greaterThan(System.currentTimeMillis()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testPexpireWithOptionsAndPexpireTime() {\n    String key = \"pexpireWithOptionsKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    long expireTimeMillis = 20000; // 20 seconds\n\n    Long pexpireTimeBefore = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeBefore, equalTo(-1L));\n\n    Long pexpire = exec(commandObjects.pexpire(key, expireTimeMillis, ExpiryOption.NX));\n    assertThat(pexpire, equalTo(1L));\n\n    Long pexpireTimeAfterSet = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterSet, greaterThan(System.currentTimeMillis()));\n\n    Long pexpireWithNx = exec(commandObjects.pexpire(key, expireTimeMillis, ExpiryOption.NX));\n    assertThat(pexpireWithNx, equalTo(0L));\n\n    Long pexpireWithXx = exec(commandObjects.pexpire(key, expireTimeMillis, ExpiryOption.XX));\n    assertThat(pexpireWithXx, equalTo(1L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void testPexpireWithOptionsAndPexpireTimeBinary() {\n    byte[] key = \"pexpireWithOptionsKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    long expireTimeMillis = 20000; // 20 seconds\n\n    Long pexpireTimeBefore = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeBefore, equalTo(-1L));\n\n    Long pexpire = exec(commandObjects.pexpire(key, expireTimeMillis, ExpiryOption.NX));\n    assertThat(pexpire, equalTo(1L));\n\n    Long pexpireTimeAfterSet = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterSet, greaterThan(System.currentTimeMillis()));\n\n    Long pexpireWithNx = exec(commandObjects.pexpire(key, expireTimeMillis, ExpiryOption.NX));\n    assertThat(pexpireWithNx, equalTo(0L));\n\n    Long pexpireWithXx = exec(commandObjects.pexpire(key, expireTimeMillis, ExpiryOption.XX));\n    assertThat(pexpireWithXx, equalTo(1L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireAtAndExpireTime() {\n    String key = \"expireAtKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    long futureExpireTime = System.currentTimeMillis() / 1000 + 10; // 10 seconds from now\n\n    // Setting expire at in the future\n    Long expireAt = exec(commandObjects.expireAt(key, futureExpireTime));\n    assertThat(expireAt, equalTo(1L));\n\n    Long expireTime = exec(commandObjects.expireTime(key));\n    assertThat(expireTime, equalTo(futureExpireTime));\n\n    // Setting expire at in the past should delete the key\n    long pastExpireTime = System.currentTimeMillis() / 1000 - 10;\n    Long expireAtPast = exec(commandObjects.expireAt(key, pastExpireTime));\n    assertThat(expireAtPast, equalTo(1L));\n\n    Long expireTimeAfterPast = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterPast, equalTo(-2L)); // Key does not exist\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireAtAndExpireTimeBinary() {\n    byte[] key = \"expireAtKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    long futureExpireTime = System.currentTimeMillis() / 1000 + 10; // 10 seconds from now\n\n    // Setting expire at in the future\n    Long expireAt = exec(commandObjects.expireAt(key, futureExpireTime));\n    assertThat(expireAt, equalTo(1L));\n\n    Long expireTime = exec(commandObjects.expireTime(key));\n    assertThat(expireTime, equalTo(futureExpireTime));\n\n    // Setting expire at in the past should delete the key\n    long pastExpireTime = System.currentTimeMillis() / 1000 - 10;\n    Long expireAtPast = exec(commandObjects.expireAt(key, pastExpireTime));\n    assertThat(expireAtPast, equalTo(1L));\n\n    Long expireTimeAfterPast = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterPast, equalTo(-2L)); // Key does not exist\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireAtWithOptionsAndExpireTime() {\n    String key = \"expireAtWithOptionsKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    long futureExpireTime = System.currentTimeMillis() / 1000 + 20; // 20 seconds from now\n\n    // Setting expire at in the future, with NX\n    Long expireAtNx = exec(commandObjects.expireAt(key, futureExpireTime, ExpiryOption.NX));\n    assertThat(expireAtNx, equalTo(1L));\n\n    Long expireTimeAfterNx = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterNx, equalTo(futureExpireTime));\n\n    // Update expire at in the future, with XX\n    long laterFutureExpireTime = futureExpireTime + 10;\n    Long expireAtXx = exec(commandObjects.expireAt(key, laterFutureExpireTime, ExpiryOption.XX));\n    assertThat(expireAtXx, equalTo(1L));\n\n    Long expireTimeAfterXx = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterXx, equalTo(laterFutureExpireTime));\n\n    // Try to reset with NX, should fail\n    Long expireAtNxAgain = exec(commandObjects.expireAt(key, futureExpireTime, ExpiryOption.NX));\n    assertThat(expireAtNxAgain, equalTo(0L));\n\n    Long expireTimeAfterNxAgain = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterNxAgain, equalTo(laterFutureExpireTime));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testExpireAtWithOptionsAndExpireTimeBinary() {\n    byte[] key = \"expireAtWithOptionsKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    long futureExpireTime = System.currentTimeMillis() / 1000 + 20; // 20 seconds from now\n\n    // Setting expire at in the future, with NX\n    Long expireAtNx = exec(commandObjects.expireAt(key, futureExpireTime, ExpiryOption.NX));\n    assertThat(expireAtNx, equalTo(1L));\n\n    Long expireTimeAfterNx = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterNx, equalTo(futureExpireTime));\n\n    // Update expire at in the future, with XX\n    long laterFutureExpireTime = futureExpireTime + 10;\n    Long expireAtXx = exec(commandObjects.expireAt(key, laterFutureExpireTime, ExpiryOption.XX));\n    assertThat(expireAtXx, equalTo(1L));\n\n    Long expireTime = exec(commandObjects.expireTime(key));\n    assertThat(expireTime, equalTo(laterFutureExpireTime));\n\n    // Try to reset with NX, should fail\n    Long expireAtNxAgain = exec(commandObjects.expireAt(key, futureExpireTime, ExpiryOption.NX));\n    assertThat(expireAtNxAgain, equalTo(0L));\n\n    Long expireTimeAfterNxAgain = exec(commandObjects.expireTime(key));\n    assertThat(expireTimeAfterNxAgain, equalTo(laterFutureExpireTime));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testPexpireAtAndPexpireTime() {\n    String key = \"pexpireAtKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    long futureTimestampMillis = System.currentTimeMillis() + 20000; // 20 seconds from now\n\n    Long pexpireAt = exec(commandObjects.pexpireAt(key, futureTimestampMillis));\n    assertThat(pexpireAt, equalTo(1L));\n\n    Long pexpireTime = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTime, equalTo(futureTimestampMillis));\n\n    // Setting pexpire at a past timestamp should delete the key\n    long pastTimestampMillis = System.currentTimeMillis() - 20000;\n    Long pexpireAtPast = exec(commandObjects.pexpireAt(key, pastTimestampMillis));\n    assertThat(pexpireAtPast, equalTo(1L));\n\n    Long pexpireTimeAfterPast = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterPast, equalTo(-2L)); // Key does not exist\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testPexpireAtAndPexpireTimeBinary() {\n    byte[] key = \"pexpireAtKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    long futureTimestampMillis = System.currentTimeMillis() + 20000; // 20 seconds from now\n\n    Long pexpireAt = exec(commandObjects.pexpireAt(key, futureTimestampMillis));\n    assertThat(pexpireAt, equalTo(1L));\n\n    Long pexpireTime = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTime, equalTo(futureTimestampMillis));\n\n    // Setting pexpire at a past timestamp should delete the key\n    long pastTimestampMillis = System.currentTimeMillis() - 20000;\n    Long pexpireAtPast = exec(commandObjects.pexpireAt(key, pastTimestampMillis));\n    assertThat(pexpireAtPast, equalTo(1L));\n\n    Long pexpireTimeAfterPast = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterPast, equalTo(-2L)); // Key does not exist\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void testPexpireAtWithOptionsAndPexpireTime() {\n    String key = \"pexpireAtWithOptionsKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    long futureTimestampMillis = System.currentTimeMillis() + 30000; // 30 seconds from now\n\n    // Setting with NX\n    Long pexpireAtNx = exec(commandObjects.pexpireAt(key, futureTimestampMillis, ExpiryOption.NX));\n    assertThat(pexpireAtNx, equalTo(1L));\n\n    Long pexpireTimeAfterNx = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterNx, equalTo(futureTimestampMillis));\n\n    // Updating with XX\n    long laterFutureTimestampMillis = futureTimestampMillis + 10000; // Further 10 seconds in the future\n    Long pexpireAtXx = exec(commandObjects.pexpireAt(key, laterFutureTimestampMillis, ExpiryOption.XX));\n    assertThat(pexpireAtXx, equalTo(1L));\n\n    Long pexpireTimeAfterXx = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterXx, equalTo(laterFutureTimestampMillis));\n\n    // Updating with NX fails\n    Long pexpireAtNxAgain = exec(commandObjects.pexpireAt(key, futureTimestampMillis, ExpiryOption.NX));\n    assertThat(pexpireAtNxAgain, equalTo(0L));\n\n    Long pexpireTimeAfterNxAgain = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterNxAgain, equalTo(laterFutureTimestampMillis));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testPexpireAtWithOptionsAndPexpireTimeBinary() {\n    byte[] key = \"pexpireAtWithOptionsKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    long futureTimestampMillis = System.currentTimeMillis() + 30000; // 30 seconds from now\n\n    // Setting with NX\n    Long pexpireAtNx = exec(commandObjects.pexpireAt(key, futureTimestampMillis, ExpiryOption.NX));\n    assertThat(pexpireAtNx, equalTo(1L));\n\n    Long pexpireTimeAfterNx = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterNx, equalTo(futureTimestampMillis));\n\n    // Updating with XX\n    long laterFutureTimestampMillis = futureTimestampMillis + 10000; // Further 10 seconds in the future\n    Long pexpireAtXx = exec(commandObjects.pexpireAt(key, laterFutureTimestampMillis, ExpiryOption.XX));\n    assertThat(pexpireAtXx, equalTo(1L));\n\n    Long pexpireTimeAfterXx = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterXx, equalTo(laterFutureTimestampMillis));\n\n    // Updating with NX fails\n    Long pexpireAtNxAgain = exec(commandObjects.pexpireAt(key, futureTimestampMillis, ExpiryOption.NX));\n    assertThat(pexpireAtNxAgain, equalTo(0L));\n\n    Long pexpireTimeAfterNxAgain = exec(commandObjects.pexpireTime(key));\n    assertThat(pexpireTimeAfterNxAgain, equalTo(laterFutureTimestampMillis));\n  }\n\n  @Test\n  public void testTtl() {\n    String key = \"ttlKey\";\n    String value = \"value\";\n\n    long seconds = 10;\n\n    exec(commandObjects.set(key, value));\n    exec(commandObjects.expire(key, seconds));\n\n    Long ttl = exec(commandObjects.ttl(key));\n    assertThat(ttl, greaterThan(0L));\n  }\n\n  @Test\n  public void testTtlBinary() {\n    byte[] key = \"ttlKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    long seconds = 10;\n\n    exec(commandObjects.set(key, value));\n    exec(commandObjects.expire(key, seconds));\n\n    Long ttl = exec(commandObjects.ttl(key));\n    assertThat(ttl, greaterThan(1L));\n  }\n\n  @Test\n  public void testPttl() {\n    String key = \"pttlKey\";\n    String value = \"value\";\n\n    long milliseconds = 10000; // 10 seconds\n\n    exec(commandObjects.set(key, value));\n    exec(commandObjects.pexpire(key, milliseconds));\n\n    Long pttl = exec(commandObjects.pttl(key));\n    assertThat(pttl, greaterThan(0L));\n  }\n\n  @Test\n  public void testPttlBinary() {\n    byte[] key = \"pttlKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    long milliseconds = 10000; // 10 seconds\n\n    exec(commandObjects.set(key, value));\n    exec(commandObjects.pexpire(key, milliseconds));\n\n    Long pttl = exec(commandObjects.pttl(key));\n    assertThat(pttl, greaterThan(1L));\n  }\n\n  @Test\n  public void testTouch() {\n    String key = \"touchKey\";\n\n    exec(commandObjects.set(key, \"value\"));\n\n    Long touchExisting = exec(commandObjects.touch(key));\n    assertThat(touchExisting, equalTo(1L));\n\n    Long touchNonExistent = exec(commandObjects.touch(\"nonExistentKey\"));\n    assertThat(touchNonExistent, equalTo(0L));\n  }\n\n  @Test\n  public void testTouchBinary() {\n    byte[] key = \"touchKey\".getBytes();\n\n    exec(commandObjects.set(key, \"value\".getBytes()));\n\n    Long touchExisting = exec(commandObjects.touch(key));\n    assertThat(touchExisting, equalTo(1L));\n\n    Long touchNonExistent = exec(commandObjects.touch(\"nonExistentKey\".getBytes()));\n    assertThat(touchNonExistent, equalTo(0L));\n  }\n\n  @Test\n  public void testTouchMultiple() {\n    String key1 = \"touchMultiKey1\";\n    String key2 = \"touchMultiKey2\";\n    String key3 = \"nonExistentKey\";\n\n    exec(commandObjects.set(key1, \"value1\"));\n    exec(commandObjects.set(key2, \"value2\"));\n\n    Long touch = exec(commandObjects.touch(key1, key2, key3));\n    assertThat(touch, equalTo(2L));\n  }\n\n  @Test\n  public void testTouchMultipleBinary() {\n    byte[] key1 = \"touchMultiKey1\".getBytes();\n    byte[] key2 = \"touchMultiKey2\".getBytes();\n    byte[] key3 = \"nonExistentKey\".getBytes();\n\n    exec(commandObjects.set(key1, \"value1\".getBytes()));\n    exec(commandObjects.set(key2, \"value2\".getBytes()));\n\n    Long touch = exec(commandObjects.touch(key1, key2, key3));\n    assertThat(touch, equalTo(2L));\n  }\n\n  @Test\n  public void testSort() {\n    String listKey = \"sortList\";\n\n    exec(commandObjects.lpush(listKey, \"3\", \"1\", \"2\"));\n\n    List<String> sorted = exec(commandObjects.sort(listKey));\n    assertThat(sorted, contains(\"1\", \"2\", \"3\"));\n  }\n\n  @Test\n  public void testSortBinary() {\n    byte[] listKey = \"sortList\".getBytes();\n\n    exec(commandObjects.lpush(listKey, \"3\".getBytes(), \"1\".getBytes(), \"2\".getBytes()));\n\n    List<byte[]> sorted = exec(commandObjects.sort(listKey));\n    assertThat(sorted, contains(\"1\".getBytes(), \"2\".getBytes(), \"3\".getBytes()));\n  }\n\n  @Test\n  public void testSortWithSortingParams() {\n    String listKey = \"sortListParams\";\n\n    exec(commandObjects.lpush(listKey, \"item3\", \"item1\", \"item2\"));\n\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 2);\n\n    List<String> sorted = exec(commandObjects.sort(listKey, sortingParams));\n    assertThat(sorted, contains(\"item1\", \"item2\"));\n  }\n\n  @Test\n  public void testSortBinaryWithSortingParams() {\n    byte[] listKey = \"sortListParams\".getBytes();\n\n    exec(commandObjects.lpush(listKey, \"item3\".getBytes(), \"item1\".getBytes(), \"item2\".getBytes()));\n\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 2);\n\n    List<byte[]> sorted = exec(commandObjects.sort(listKey, sortingParams));\n    assertThat(sorted, contains(\"item1\".getBytes(), \"item2\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSortAndStore() {\n    String listKey = \"sortStoreList\";\n    String destinationKey = \"sortedList\";\n\n    exec(commandObjects.lpush(listKey, \"9\", \"3\", \"6\"));\n\n    Long sort = exec(commandObjects.sort(listKey, destinationKey));\n    assertThat(sort, equalTo(3L));\n\n    List<String> sorted = exec(commandObjects.lrange(destinationKey, 0, -1));\n    assertThat(sorted, contains(\"3\", \"6\", \"9\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSortAndStoreBinary() {\n    byte[] listKey = \"sortStoreList\".getBytes();\n    byte[] destinationKey = \"sortedList\".getBytes();\n\n    exec(commandObjects.lpush(listKey, \"9\".getBytes(), \"3\".getBytes(), \"6\".getBytes()));\n\n    Long sort = exec(commandObjects.sort(listKey, destinationKey));\n    assertThat(sort, equalTo(3L));\n\n    List<byte[]> sorted = exec(commandObjects.lrange(destinationKey, 0, -1));\n    assertThat(sorted, contains(\"3\".getBytes(), \"6\".getBytes(), \"9\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSortWithParamsAndStore() {\n    String listKey = \"sortParamsStoreList\";\n    String destinationKey = \"sortedParamsList\";\n\n    exec(commandObjects.lpush(listKey, \"item3\", \"item1\", \"item2\"));\n\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 2);\n\n    Long sort = exec(commandObjects.sort(listKey, sortingParams, destinationKey));\n    assertThat(sort, equalTo(2L));\n\n    List<String> sorted = exec(commandObjects.lrange(destinationKey, 0, -1));\n    assertThat(sorted, contains(\"item1\", \"item2\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSortWithParamsAndStoreBinary() {\n    byte[] listKey = \"sortParamsStoreList\".getBytes();\n    byte[] destinationKey = \"sortedParamsList\".getBytes();\n\n    exec(commandObjects.lpush(listKey, \"item3\".getBytes(), \"item1\".getBytes(), \"item2\".getBytes()));\n\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 2);\n\n    Long sort = exec(commandObjects.sort(listKey, sortingParams, destinationKey));\n    assertThat(sort, equalTo(2L));\n\n    List<byte[]> sorted = exec(commandObjects.lrange(destinationKey, 0, -1));\n    assertThat(sorted, contains(\"item1\".getBytes(), \"item2\".getBytes()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testSortReadonly() {\n    String listKey = \"readonlySortList\";\n\n    exec(commandObjects.lpush(listKey, \"3\", \"1\", \"2\"));\n\n    SortingParams sortingParams = new SortingParams().desc();\n\n    List<String> sorted = exec(commandObjects.sortReadonly(listKey, sortingParams));\n    assertThat(sorted, contains(\"3\", \"2\", \"1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testSortReadonlyBinary() {\n    byte[] listKey = \"readonlySortList\".getBytes();\n\n    exec(commandObjects.lpush(listKey, \"3\".getBytes(), \"1\".getBytes(), \"2\".getBytes()));\n\n    SortingParams sortingParams = new SortingParams().desc();\n\n    List<byte[]> sorted = exec(commandObjects.sortReadonly(listKey, sortingParams));\n    assertThat(sorted, contains(\"3\".getBytes(), \"2\".getBytes(), \"1\".getBytes()));\n  }\n\n  @Test\n  public void testDel() {\n    String key = \"delKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    String getBeforeDel = exec(commandObjects.get(key));\n    assertThat(getBeforeDel, equalTo(value));\n\n    Long del = exec(commandObjects.del(key));\n    assertThat(del, equalTo(1L));\n\n    String getAfterDel = exec(commandObjects.get(key));\n    assertThat(getAfterDel, nullValue());\n  }\n\n  @Test\n  public void testDelBinary() {\n    byte[] key = \"delKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    byte[] getBeforeDel = exec(commandObjects.get(key));\n    assertThat(getBeforeDel, equalTo(value));\n\n    Long del = exec(commandObjects.del(key));\n    assertThat(del, equalTo(1L));\n\n    byte[] getAfterDel = exec(commandObjects.get(key));\n    assertThat(getAfterDel, nullValue());\n  }\n\n  @Test\n  public void testDelMultiple() {\n    String key1 = \"key1\";\n    String key2 = \"key2\";\n\n    exec(commandObjects.set(key1, \"value\"));\n    exec(commandObjects.set(key2, \"value\"));\n\n    Long del = exec(commandObjects.del(key1, key2, \"nonExistingKey\"));\n    assertThat(del, equalTo(2L));\n\n    Long exists = exec(commandObjects.exists(key1, key2));\n    assertThat(exists, equalTo(0L));\n  }\n\n  @Test\n  public void testDelMultipleBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    exec(commandObjects.set(key1, \"value\".getBytes()));\n    exec(commandObjects.set(key2, \"value\".getBytes()));\n\n    Long del = exec(commandObjects.del(key1, key2, \"nonExistingKey\".getBytes()));\n    assertThat(del, equalTo(2L));\n\n    Long exists = exec(commandObjects.exists(key1, key2));\n    assertThat(exists, equalTo(0L));\n  }\n\n  @Test\n  public void testUnlink() {\n    String key = \"unlinkKey\";\n\n    exec(commandObjects.set(key, \"value\"));\n\n    Long unlink = exec(commandObjects.unlink(key));\n    assertThat(unlink, equalTo(1L));\n\n    Boolean exists = exec(commandObjects.exists(key));\n    assertThat(exists, equalTo(false));\n  }\n\n  @Test\n  public void testUnlinkBinary() {\n    byte[] key = \"unlinkKey\".getBytes();\n\n    exec(commandObjects.set(key, \"value\".getBytes()));\n\n    Long unlink = exec(commandObjects.unlink(key));\n    assertThat(unlink, equalTo(1L));\n\n    Boolean exists = exec(commandObjects.exists(key));\n    assertThat(exists, equalTo(false));\n  }\n\n  @Test\n  public void testUnlinkMultiple() {\n    String key1 = \"key1ToUnlink\";\n    String key2 = \"key2ToUnlink\";\n\n    exec(commandObjects.set(key1, \"value\"));\n    exec(commandObjects.set(key2, \"value\"));\n\n    Long unlink = exec(commandObjects.unlink(key1, key2, \"nonExistingKey\"));\n    assertThat(unlink, equalTo(2L));\n\n    Long exists = exec(commandObjects.exists(key1, key2));\n    assertThat(exists, equalTo(0L));\n  }\n\n  @Test\n  public void testUnlinkMultipleBinary() {\n    byte[] key1 = \"key1ToUnlink\".getBytes();\n    byte[] key2 = \"key2ToUnlink\".getBytes();\n\n    exec(commandObjects.set(key1, \"value\".getBytes()));\n    exec(commandObjects.set(key2, \"value\".getBytes()));\n\n    Long unlink = exec(commandObjects.unlink(key1, key2, \"nonExistingKey\".getBytes()));\n    assertThat(unlink, equalTo(2L));\n\n    Long exists = exec(commandObjects.exists(key1, key2));\n    assertThat(exists, equalTo(0L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testCopyWithStringKeys() {\n    String srcKey = \"sourceKey\";\n    String dstKey = \"destinationKey\";\n    String value = \"value\";\n    String otherValue = \"otherValue\";\n\n    exec(commandObjects.set(srcKey, value));\n\n    String initialValue = exec(commandObjects.get(srcKey));\n    assertThat(initialValue, equalTo(value));\n\n    String dstBeforeCopy = exec(commandObjects.get(dstKey));\n    assertThat(dstBeforeCopy, nullValue());\n\n    Boolean copy = exec(commandObjects.copy(srcKey, dstKey, false));\n    assertThat(copy, equalTo(true));\n\n    String dstAfterCopy = exec(commandObjects.get(dstKey));\n    assertThat(dstAfterCopy, equalTo(value));\n\n    exec(commandObjects.set(srcKey, otherValue));\n\n    Boolean copyFail = exec(commandObjects.copy(srcKey, dstKey, false));\n    assertThat(copyFail, equalTo(false));\n\n    String dstAfterFailedCopy = exec(commandObjects.get(dstKey));\n    assertThat(dstAfterFailedCopy, equalTo(value));\n\n    Boolean copyReplace = exec(commandObjects.copy(srcKey, dstKey, true));\n    assertThat(copyReplace, equalTo(true));\n\n    String dstAfterReplace = exec(commandObjects.get(dstKey));\n    assertThat(dstAfterReplace, equalTo(otherValue));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testCopyWithBinaryKeys() {\n    byte[] srcKey = \"sourceKey\".getBytes();\n    byte[] dstKey = \"destinationKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n    byte[] otherValue = \"otherValue\".getBytes();\n\n    exec(commandObjects.set(srcKey, value));\n\n    byte[] initialValue = exec(commandObjects.get(srcKey));\n    assertThat(initialValue, equalTo(value));\n\n    byte[] dstBeforeCopy = exec(commandObjects.get(dstKey));\n    assertThat(dstBeforeCopy, nullValue());\n\n    Boolean copy = exec(commandObjects.copy(srcKey, dstKey, false));\n    assertThat(copy, equalTo(true));\n\n    byte[] dstAfterCopy = exec(commandObjects.get(dstKey));\n    assertThat(dstAfterCopy, equalTo(value));\n\n    exec(commandObjects.set(srcKey, otherValue));\n\n    Boolean copyFail = exec(commandObjects.copy(srcKey, dstKey, false));\n    assertThat(copyFail, equalTo(false));\n\n    byte[] dstAfterFailedCopy = exec(commandObjects.get(dstKey));\n    assertThat(dstAfterFailedCopy, equalTo(value));\n\n    Boolean copyReplace = exec(commandObjects.copy(srcKey, dstKey, true));\n    assertThat(copyReplace, equalTo(true));\n\n    byte[] dstAfterReplace = exec(commandObjects.get(dstKey));\n    assertThat(dstAfterReplace, equalTo(otherValue));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testCopyToDb() {\n    String srcKey = \"sourceKey\";\n    String dstKey = \"destinationKey\";\n    int dstDB = 1;\n\n    exec(commandObjects.set(srcKey, \"initialValue\"));\n\n    Boolean existsAfterSet = exec(commandObjects.exists(srcKey));\n    assertThat(existsAfterSet, equalTo(true));\n\n    Boolean copy = exec(commandObjects.copy(srcKey, dstKey, dstDB, true));\n    assertThat(copy, equalTo(true));\n\n    assertKeyExists(dstDB, dstKey, \"initialValue\");\n\n    // Update source\n    exec(commandObjects.set(srcKey, \"newValue\"));\n\n    // Copy again without replace, it fails since dstKey already exists\n    Boolean secondCopy = exec(commandObjects.copy(srcKey, dstKey, dstDB, false));\n    assertThat(secondCopy, equalTo(false));\n\n    assertKeyExists(dstDB, dstKey, \"initialValue\");\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testCopyToDbBinary() {\n    String srcKey = \"sourceKey\";\n    String dstKey = \"destinationKey\";\n    int dstDB = 1;\n\n    exec(commandObjects.set(srcKey, \"initialValue\"));\n\n    Boolean existsAfterSet = exec(commandObjects.exists(srcKey));\n    assertThat(existsAfterSet, equalTo(true));\n\n    Boolean copy = exec(commandObjects.copy(\n        srcKey.getBytes(), dstKey.getBytes(), dstDB, true));\n    assertThat(copy, equalTo(true));\n\n    assertKeyExists(dstDB, dstKey, \"initialValue\");\n\n    // Update source\n    exec(commandObjects.set(srcKey, \"newValue\"));\n\n    // Copy again without replace, it will fail\n    Boolean secondCopy = exec(commandObjects.copy(srcKey.getBytes(), dstKey.getBytes(), dstDB, false));\n    assertThat(secondCopy, equalTo(false));\n\n    assertKeyExists(dstDB, dstKey, \"initialValue\");\n  }\n\n  private void assertKeyExists(int dstDb, String key, Object expectedValue) {\n    // Cheat and use Jedis, it gives us access to any db.\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort())) {\n      jedis.auth(endpoint.getPassword());\n      jedis.select(dstDb);\n      assertThat(jedis.get(key), equalTo(expectedValue));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testRenameWithStringKeys() {\n    String oldKey = \"oldKeyName\";\n    String newKey = \"newKeyName\";\n    String value = \"value\";\n\n    exec(commandObjects.set(oldKey, value));\n\n    String oldValue = exec(commandObjects.get(oldKey));\n    assertThat(oldValue, equalTo(value));\n\n    String newKeyBeforeRename = exec(commandObjects.get(newKey));\n    assertThat(newKeyBeforeRename, nullValue());\n\n    String rename = exec(commandObjects.rename(oldKey, newKey));\n    assertThat(rename, equalTo(\"OK\"));\n\n    String oldKeyAfterRename = exec(commandObjects.get(oldKey));\n    assertThat(oldKeyAfterRename, nullValue());\n\n    String newValue = exec(commandObjects.get(newKey));\n    assertThat(newValue, equalTo(value));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testRenameWithBinaryKeys() {\n    byte[] oldKey = \"oldKeyName\".getBytes();\n    byte[] newKey = \"newKeyName\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(oldKey, value));\n\n    byte[] oldValue = exec(commandObjects.get(oldKey));\n    assertThat(oldValue, equalTo(value));\n\n    byte[] newKeyBeforeRename = exec(commandObjects.get(newKey));\n    assertThat(newKeyBeforeRename, nullValue());\n\n    String rename = exec(commandObjects.rename(oldKey, newKey));\n    assertThat(rename, equalTo(\"OK\"));\n\n    byte[] oldKeyAfterRename = exec(commandObjects.get(oldKey));\n    assertThat(oldKeyAfterRename, nullValue());\n\n    byte[] newValue = exec(commandObjects.get(newKey));\n    assertThat(newValue, equalTo(value));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testRenamenx() {\n    String oldKey = \"oldKeyToRenameNX\";\n    String newKey = \"newKeyForRenameNX\";\n    String anotherKey = \"anotherKey\";\n    String value = \"value\";\n\n    exec(commandObjects.set(oldKey, value));\n    exec(commandObjects.set(anotherKey, value));\n\n    String newKeyBefore = exec(commandObjects.get(newKey));\n    assertThat(newKeyBefore, nullValue());\n\n    Long renamenx = exec(commandObjects.renamenx(oldKey, newKey));\n    assertThat(renamenx, equalTo(1L));\n\n    String newValue = exec(commandObjects.get(newKey));\n    assertThat(newValue, equalTo(value));\n\n    Long renamenxFail = exec(commandObjects.renamenx(anotherKey, newKey));\n    assertThat(renamenxFail, equalTo(0L));\n\n    String anotherKeyStillExists = exec(commandObjects.get(anotherKey));\n    assertThat(anotherKeyStillExists, equalTo(value));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testRenamenxBinary() {\n    byte[] oldKey = \"oldKeyToRenameNX\".getBytes();\n    byte[] newKey = \"newKeyForRenameNX\".getBytes();\n    byte[] anotherKey = \"anotherKey\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(oldKey, value));\n    exec(commandObjects.set(anotherKey, value));\n\n    byte[] newKeyBefore = exec(commandObjects.get(newKey));\n    assertThat(newKeyBefore, nullValue());\n\n    Long renamenx = exec(commandObjects.renamenx(oldKey, newKey));\n    assertThat(renamenx, equalTo(1L));\n\n    byte[] newValue = exec(commandObjects.get(newKey));\n    assertThat(newValue, equalTo(value));\n\n    Long renamenxFail = exec(commandObjects.renamenx(anotherKey, newKey));\n    assertThat(renamenxFail, equalTo(0L));\n\n    byte[] anotherKeyStillExists = exec(commandObjects.get(anotherKey));\n    assertThat(anotherKeyStillExists, equalTo(value));\n  }\n\n  @Test\n  public void testDbSize() {\n    Long initialSize = exec(commandObjects.dbSize());\n    assertThat(initialSize, greaterThanOrEqualTo(0L));\n\n    String key = \"testKey\";\n\n    exec(commandObjects.set(key, \"testValue\"));\n\n    Long newSize = exec(commandObjects.dbSize());\n    assertThat(newSize, equalTo(initialSize + 1));\n\n    exec(commandObjects.del(key));\n\n    Long finalSize = exec(commandObjects.dbSize());\n    assertThat(finalSize, equalTo(initialSize));\n  }\n\n  @Test\n  public void testKeysWithStringPattern() {\n    String pattern = \"testKey:*\";\n    String matchingKey1 = \"testKey:1\";\n    String matchingKey2 = \"testKey:2\";\n    String value = \"value\";\n\n    exec(commandObjects.set(matchingKey1, value));\n    exec(commandObjects.set(matchingKey2, value));\n    exec(commandObjects.set(\"otherKey\", value));\n\n    Set<String> keys = exec(commandObjects.keys(pattern));\n    assertThat(keys, containsInAnyOrder(matchingKey1, matchingKey2));\n\n    exec(commandObjects.del(matchingKey1, matchingKey2));\n\n    Set<String> keysAfterDeletion = exec(commandObjects.keys(pattern));\n    assertThat(keysAfterDeletion, empty());\n  }\n\n  @Test\n  public void testKeysWithBinaryPattern() {\n    byte[] pattern = \"testKey:*\".getBytes();\n    byte[] matchingKey1 = \"testKey:1\".getBytes();\n    byte[] matchingKey2 = \"testKey:2\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(matchingKey1, value));\n    exec(commandObjects.set(matchingKey2, value));\n    exec(commandObjects.set(\"otherKey\".getBytes(), value));\n\n    Set<byte[]> keys = exec(commandObjects.keys(pattern));\n    assertThat(keys, containsInAnyOrder(matchingKey1, matchingKey2));\n\n    exec(commandObjects.del(matchingKey1, matchingKey2));\n\n    Set<byte[]> keysAfterDeletion = exec(commandObjects.keys(pattern));\n    assertThat(keysAfterDeletion, empty());\n  }\n\n  @Test\n  public void testScan() {\n    String key1 = \"scanKey1\";\n    String key2 = \"scanKey2\";\n\n    exec(commandObjects.set(key1, \"value\"));\n    exec(commandObjects.set(key2, \"value\"));\n\n    Set<String> collectedKeys = new HashSet<>();\n\n    ScanResult<String> scanResult;\n    String nextCursor = \"0\";\n\n    do {\n      scanResult = exec(commandObjects.scan(nextCursor));\n      nextCursor = scanResult.getCursor();\n      collectedKeys.addAll(scanResult.getResult());\n    } while (!\"0\".equals(nextCursor));\n\n    assertThat(collectedKeys, hasItems(key1, key2));\n  }\n\n  @Test\n  public void testScanBinary() {\n    byte[] key1 = \"scanKey1\".getBytes();\n    byte[] key2 = \"scanKey2\".getBytes();\n\n    exec(commandObjects.set(key1, \"value\".getBytes()));\n    exec(commandObjects.set(key2, \"value\".getBytes()));\n\n    Set<byte[]> collectedKeys = new HashSet<>();\n\n    ScanResult<byte[]> scanResult;\n    byte[] cursor = \"0\".getBytes();\n\n    do {\n      scanResult = exec(commandObjects.scan(cursor));\n      cursor = scanResult.getCursorAsBytes();\n      collectedKeys.addAll(scanResult.getResult());\n    } while (!Arrays.equals(\"0\".getBytes(), cursor));\n\n    assertThat(collectedKeys, hasItems(key1, key2));\n  }\n\n  @Test\n  public void testScanWithParams() {\n    String matchingKey1 = \"user:123\";\n    String matchingKey2 = \"user:456\";\n    String nonMatchingKey = \"config:123\";\n\n    exec(commandObjects.set(matchingKey1, \"testValue\"));\n    exec(commandObjects.set(matchingKey2, \"testValue\"));\n    exec(commandObjects.set(nonMatchingKey, \"testValue\"));\n\n    ScanParams params = new ScanParams().match(\"user:*\").count(2);\n\n    Set<String> collectedKeys = new HashSet<>();\n\n    ScanResult<String> scanResult;\n    String cursor = \"0\";\n\n    do {\n      scanResult = exec(commandObjects.scan(cursor, params));\n      collectedKeys.addAll(scanResult.getResult());\n      cursor = scanResult.getCursor();\n    } while (!\"0\".equals(scanResult.getCursor()));\n\n    assertThat(collectedKeys, hasItems(matchingKey1, matchingKey2));\n    assertThat(collectedKeys, not(hasItem(nonMatchingKey)));\n  }\n\n  @Test\n  public void testScanWithParamsBinary() {\n    byte[] matchingKey1 = \"user:123\".getBytes();\n    byte[] matchingKey2 = \"user:456\".getBytes();\n    byte[] nonMatchingKey = \"config:123\".getBytes();\n\n    exec(commandObjects.set(matchingKey1, \"testValue\".getBytes()));\n    exec(commandObjects.set(matchingKey2, \"testValue\".getBytes()));\n    exec(commandObjects.set(nonMatchingKey, \"testValue\".getBytes()));\n\n    ScanParams params = new ScanParams().match(\"user:*\").count(2);\n\n    Set<byte[]> collectedKeys = new HashSet<>();\n\n    ScanResult<byte[]> scanResult;\n    byte[] cursor = \"0\".getBytes();\n\n    do {\n      scanResult = exec(commandObjects.scan(cursor, params));\n      collectedKeys.addAll(scanResult.getResult());\n      cursor = scanResult.getCursorAsBytes();\n    } while (!Arrays.equals(\"0\".getBytes(), cursor));\n\n    assertThat(collectedKeys, hasItems(matchingKey1, matchingKey2));\n    assertThat(collectedKeys, not(hasItem(nonMatchingKey)));\n  }\n\n  @Test\n  public void testScanWithParamsAndType() {\n    String stringKey = \"user:string:1\";\n    String listKey = \"user:list:1\";\n\n    exec(commandObjects.set(stringKey, \"value\"));\n    exec(commandObjects.rpush(listKey, \"value1\", \"value2\"));\n\n    ScanParams params = new ScanParams().match(\"user:*\");\n\n    Set<String> collectedKeys = new HashSet<>();\n\n    ScanResult<String> scanResult;\n    String cursor = \"0\";\n\n    do {\n      scanResult = exec(commandObjects.scan(cursor, params, \"string\"));\n      collectedKeys.addAll(scanResult.getResult());\n      cursor = scanResult.getCursor();\n    } while (!\"0\".equals(scanResult.getCursor()));\n\n    assertThat(collectedKeys, hasItem(stringKey));\n    assertThat(collectedKeys, not(hasItem(listKey)));\n  }\n\n  @Test\n  public void testScanWithParamsAndTypeBinary() {\n    byte[] stringKey = \"user:string:1\".getBytes();\n    byte[] listKey = \"user:list:1\".getBytes();\n\n    exec(commandObjects.set(stringKey, \"value\".getBytes()));\n    exec(commandObjects.rpush(listKey, \"value1\".getBytes(), \"value2\".getBytes()));\n\n    ScanParams params = new ScanParams().match(\"user:*\".getBytes());\n\n    Set<byte[]> collectedKeys = new HashSet<>();\n\n    ScanResult<byte[]> scanResult;\n    byte[] cursor = \"0\".getBytes();\n\n    do {\n      scanResult = exec(commandObjects.scan(cursor, params, \"string\".getBytes()));\n      collectedKeys.addAll(scanResult.getResult());\n      cursor = scanResult.getCursorAsBytes();\n    } while (!Arrays.equals(\"0\".getBytes(), cursor));\n\n    assertThat(collectedKeys, hasItem(stringKey));\n    assertThat(collectedKeys, not(hasItem(listKey)));\n  }\n\n  @Test\n  public void testRandomKey() {\n    String key1 = \"testKey1\";\n    String key2 = \"testKey2\";\n\n    exec(commandObjects.set(key1, \"value\"));\n    exec(commandObjects.set(key2, \"value\"));\n\n    String randomKey = exec(commandObjects.randomKey());\n\n    assertThat(randomKey, anyOf(equalTo(key1), equalTo(key2)));\n  }\n\n  @Test\n  public void testRandomBinaryKey() {\n    byte[] key1 = \"testKey1\".getBytes();\n    byte[] key2 = \"testKey2\".getBytes();\n\n    exec(commandObjects.set(key1, \"value\".getBytes()));\n    exec(commandObjects.set(key2, \"value\".getBytes()));\n\n    byte[] randomBinaryKey = exec(commandObjects.randomBinaryKey());\n\n    assertThat(randomBinaryKey, anyOf(equalTo(key1), equalTo(key2)));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsGeospatialCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=geo\">Geospatial</a> commands.\n */\npublic class CommandObjectsGeospatialCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  // Some coordinates for testing\n  public static final String CATANIA = \"Catania\";\n  public static final double CATANIA_LATITUDE = 37.502669;\n  public static final double CATANIA_LONGITUDE = 15.087269;\n\n  public static final String PALERMO = \"Palermo\";\n  public static final double PALERMO_LONGITUDE = 13.361389;\n  public static final double PALERMO_LATITUDE = 38.115556;\n\n  public static final String SYRACUSE = \"Syracuse\";\n  public static final double SYRACUSE_LONGITUDE = 15.293331;\n  public static final double SYRACUSE_LATITUDE = 37.075474;\n\n  public static final String AGRIGENTO = \"Agrigento\";\n  public static final double AGRIGENTO_LONGITUDE = 13.583333;\n  public static final double AGRIGENTO_LATITUDE = 37.316667;\n\n  public CommandObjectsGeospatialCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testGeoAddAndRadius() {\n    String key = \"locations\";\n\n    Map<String, GeoCoordinate> cataniaCoordinates = new HashMap<>();\n    cataniaCoordinates.put(CATANIA, new GeoCoordinate(CATANIA_LONGITUDE, CATANIA_LATITUDE));\n\n    Map<String, GeoCoordinate> syracuseCoordinates = new HashMap<>();\n    syracuseCoordinates.put(SYRACUSE, new GeoCoordinate(SYRACUSE_LONGITUDE, SYRACUSE_LATITUDE));\n\n    Long addPalermo = exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    assertThat(addPalermo, equalTo(1L));\n\n    List<GeoRadiusResponse> radiusFromPalermo = exec(commandObjects.georadius(\n        key, PALERMO_LONGITUDE, PALERMO_LATITUDE, 100, GeoUnit.KM));\n    assertThat(radiusFromPalermo.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(equalTo(PALERMO)));\n\n    Long addCatania = exec(commandObjects.geoadd(key, cataniaCoordinates));\n    assertThat(addCatania, equalTo(1L));\n\n    List<GeoRadiusResponse> radiusFromCatania = exec(commandObjects.georadius(\n        key, CATANIA_LONGITUDE, CATANIA_LATITUDE, 100, GeoUnit.KM));\n    assertThat(radiusFromCatania.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(equalTo(CATANIA)));\n\n    Long addSyracuse = exec(commandObjects.geoadd(key, GeoAddParams.geoAddParams().nx(), syracuseCoordinates));\n    assertThat(addSyracuse, equalTo(1L));\n\n    List<GeoRadiusResponse> radiusEverything = exec(commandObjects.georadius(\n        key, 15, 37, 200, GeoUnit.KM));\n    assertThat(radiusEverything.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(equalTo(CATANIA), equalTo(SYRACUSE), equalTo(PALERMO)));\n  }\n\n  @Test\n  public void testGeoAddAndRadiusBinary() {\n    byte[] key = \"locations\".getBytes();\n\n    Map<byte[], GeoCoordinate> cataniaCoordinates = new HashMap<>();\n    cataniaCoordinates.put(CATANIA.getBytes(), new GeoCoordinate(CATANIA_LONGITUDE, CATANIA_LATITUDE));\n\n    Map<byte[], GeoCoordinate> syracuseCoordinates = new HashMap<>();\n    syracuseCoordinates.put(SYRACUSE.getBytes(), new GeoCoordinate(SYRACUSE_LONGITUDE, SYRACUSE_LATITUDE));\n\n    Long addPalermo = exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO.getBytes()));\n    assertThat(addPalermo, equalTo(1L));\n\n    List<GeoRadiusResponse> radiusFromPalermo = exec(commandObjects.georadius(\n        key, PALERMO_LONGITUDE, PALERMO_LATITUDE, 100, GeoUnit.KM));\n    assertThat(radiusFromPalermo.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(equalTo(PALERMO.getBytes())));\n\n    Long addCatania = exec(commandObjects.geoadd(key, cataniaCoordinates));\n    assertThat(addCatania, equalTo(1L));\n\n    List<GeoRadiusResponse> radiusFromCatania = exec(commandObjects.georadius(\n        key, CATANIA_LONGITUDE, CATANIA_LATITUDE, 100, GeoUnit.KM));\n    assertThat(radiusFromCatania.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(equalTo(CATANIA.getBytes())));\n\n    Long addSyracuse = exec(commandObjects.geoadd(key, GeoAddParams.geoAddParams().nx(), syracuseCoordinates));\n    assertThat(addSyracuse, equalTo(1L));\n\n    List<GeoRadiusResponse> radiusEverything = exec(commandObjects.georadius(\n        key, 15, 37, 200, GeoUnit.KM));\n    assertThat(radiusEverything.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(equalTo(CATANIA.getBytes()), equalTo(PALERMO.getBytes()), equalTo(SYRACUSE.getBytes())));\n  }\n\n  @Test\n  public void testGeoDist() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    // Add locations to calculate distance\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n\n    Double distance = exec(commandObjects.geodist(key, CATANIA, PALERMO));\n    // This is in meters, we don't try to accurately assert it. We refer to it later.\n    assertThat(distance, notNullValue());\n\n    Double distanceWithUnit = exec(commandObjects.geodist(key, CATANIA, PALERMO, GeoUnit.KM));\n    assertThat(distanceWithUnit, closeTo(distance / 1000, 0.001));\n\n    Double binaryDistance = exec(commandObjects.geodist(binaryKey, CATANIA.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryDistance, closeTo(distance, 0.001));\n\n    Double binaryDistanceWithUnit = exec(commandObjects.geodist(binaryKey, CATANIA.getBytes(), PALERMO.getBytes(), GeoUnit.KM));\n    assertThat(binaryDistanceWithUnit, closeTo(distance / 1000, 0.001));\n  }\n\n  @Test\n  public void testGeoHash() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n\n    List<String> hashes = exec(commandObjects.geohash(key, CATANIA, PALERMO));\n    assertThat(hashes, contains(notNullValue(), notNullValue()));\n\n    List<byte[]> binaryHashes = exec(commandObjects.geohash(binaryKey, CATANIA.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryHashes, contains(hashes.get(0).getBytes(), hashes.get(1).getBytes()));\n  }\n\n  @Test\n  public void testGeoPos() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n\n    List<GeoCoordinate> positions = exec(commandObjects.geopos(key, CATANIA, PALERMO));\n    assertThat(positions.size(), equalTo(2));\n\n    assertThat(positions.get(0), notNullValue());\n    assertThat(positions.get(0).getLongitude(), closeTo(CATANIA_LONGITUDE, 0.001));\n    assertThat(positions.get(0).getLatitude(), closeTo(CATANIA_LATITUDE, 0.001));\n\n    assertThat(positions.get(1), notNullValue());\n    assertThat(positions.get(1).getLongitude(), closeTo(PALERMO_LONGITUDE, 0.001));\n    assertThat(positions.get(1).getLatitude(), closeTo(PALERMO_LATITUDE, 0.001));\n\n    List<GeoCoordinate> binaryPositions = exec(commandObjects.geopos(binaryKey, CATANIA.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryPositions.size(), equalTo(2));\n\n    assertThat(binaryPositions.get(0), notNullValue());\n    assertThat(binaryPositions.get(0).getLongitude(), closeTo(CATANIA_LONGITUDE, 0.001));\n    assertThat(binaryPositions.get(0).getLatitude(), closeTo(CATANIA_LATITUDE, 0.001));\n\n    assertThat(binaryPositions.get(1), notNullValue());\n    assertThat(binaryPositions.get(1).getLongitude(), closeTo(PALERMO_LONGITUDE, 0.001));\n    assertThat(binaryPositions.get(1).getLatitude(), closeTo(PALERMO_LATITUDE, 0.001));\n  }\n\n  @Test\n  public void testGeoRadius() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withCoord().withDist();\n\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n\n    List<GeoRadiusResponse> responses = exec(commandObjects.georadius(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM));\n\n    // we got distances, but no coordinates\n    assertThat(responses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA, PALERMO));\n    assertThat(responses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> responsesWithParam = exec(commandObjects.georadius(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM, param));\n\n    // we got distances, and coordinates\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA, PALERMO));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(CATANIA_LATITUDE, 0.001)));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(CATANIA_LONGITUDE, 0.001)));\n\n    List<GeoRadiusResponse> binaryResponses = exec(commandObjects.georadius(binaryKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM));\n\n    // distances, but no coordinates\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA, PALERMO));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> binaryResponsesWithParam = exec(commandObjects.georadius(binaryKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM, param));\n\n    // distances, and coordinates\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(CATANIA_LATITUDE, 0.001)));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(CATANIA_LONGITUDE, 0.001)));\n  }\n\n  @Test\n  public void testGeoRadiusReadonly() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withCoord().withDist();\n\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n\n    List<GeoRadiusResponse> responses = exec(commandObjects.georadiusReadonly(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM));\n\n    // we got distances, but no coordinates\n    assertThat(responses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA, PALERMO));\n    assertThat(responses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> responsesWithParam = exec(commandObjects.georadiusReadonly(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM, param));\n\n    // we got distances, and coordinates\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA, PALERMO));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(CATANIA_LATITUDE, 0.001)));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(CATANIA_LONGITUDE, 0.001)));\n\n    List<GeoRadiusResponse> binaryResponses = exec(commandObjects.georadiusReadonly(binaryKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM));\n\n    // distances, but no coordinates\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA, PALERMO));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> binaryResponsesWithParam = exec(commandObjects.georadiusReadonly(binaryKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, 200, GeoUnit.KM, param));\n\n    // distances, and coordinates\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(CATANIA.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(CATANIA_LATITUDE, 0.001)));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(CATANIA_LONGITUDE, 0.001)));\n  }\n\n  @Test\n  public void testGeoRadiusByMember() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withCoord().withDist();\n\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(key, AGRIGENTO_LONGITUDE, AGRIGENTO_LATITUDE, AGRIGENTO));\n\n    List<GeoRadiusResponse> responses = exec(commandObjects.georadiusByMember(key, AGRIGENTO, 100, GeoUnit.KM));\n\n    assertThat(responses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO, PALERMO));\n    assertThat(responses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> responsesWithParam = exec(commandObjects.georadiusByMember(key, AGRIGENTO, 100, GeoUnit.KM, param));\n\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO, PALERMO));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(AGRIGENTO_LATITUDE, 0.001)));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(AGRIGENTO_LONGITUDE, 0.001)));\n\n    List<GeoRadiusResponse> binaryResponses = exec(commandObjects.georadiusByMember(binaryKey, AGRIGENTO.getBytes(), 100, GeoUnit.KM));\n\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO, PALERMO));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> binaryResponsesWithParam = exec(commandObjects.georadiusByMember(binaryKey, AGRIGENTO.getBytes(), 100, GeoUnit.KM, param));\n\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(AGRIGENTO_LATITUDE, 0.001)));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(AGRIGENTO_LONGITUDE, 0.001)));\n  }\n\n  @Test\n  public void testGeoRadiusByMemberReadonly() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withCoord().withDist();\n\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(key, AGRIGENTO_LONGITUDE, AGRIGENTO_LATITUDE, AGRIGENTO));\n\n    List<GeoRadiusResponse> responses = exec(commandObjects.georadiusByMemberReadonly(key, AGRIGENTO, 100, GeoUnit.KM));\n\n    assertThat(responses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO, PALERMO));\n    assertThat(responses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> responsesWithParam = exec(commandObjects.georadiusByMemberReadonly(key, AGRIGENTO, 100, GeoUnit.KM, param));\n\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO, PALERMO));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(AGRIGENTO_LATITUDE, 0.001)));\n    assertThat(responsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(AGRIGENTO_LONGITUDE, 0.001)));\n\n    List<GeoRadiusResponse> binaryResponses = exec(commandObjects.georadiusByMemberReadonly(binaryKey, AGRIGENTO.getBytes(), 100, GeoUnit.KM));\n\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO, PALERMO));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponses.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(nullValue(), nullValue()));\n\n    List<GeoRadiusResponse> binaryResponsesWithParam = exec(commandObjects.georadiusByMemberReadonly(binaryKey, AGRIGENTO.getBytes(), 100, GeoUnit.KM, param));\n\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(AGRIGENTO.getBytes(), PALERMO.getBytes()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        containsInAnyOrder(notNullValue(), notNullValue()));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLatitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LATITUDE, 0.001), closeTo(AGRIGENTO_LATITUDE, 0.001)));\n    assertThat(binaryResponsesWithParam.stream().map(GeoRadiusResponse::getCoordinate).map(GeoCoordinate::getLongitude).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(PALERMO_LONGITUDE, 0.001), closeTo(AGRIGENTO_LONGITUDE, 0.001)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testGeoradiusStore() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    String destinationKey = \"result\";\n    String binaryDestinationKey = \"resultBinary\";\n\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().sortAscending();\n\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam().store(destinationKey);\n\n    Long store = exec(commandObjects.georadiusStore(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, 200, GeoUnit.KM, param, storeParam));\n    assertThat(store, equalTo(2L));\n\n    List<String> destination = exec(commandObjects.zrange(destinationKey, 0, -1));\n    assertThat(destination, containsInAnyOrder(PALERMO, CATANIA));\n\n    GeoRadiusStoreParam storeParamForBinary = GeoRadiusStoreParam.geoRadiusStoreParam().store(binaryDestinationKey);\n\n    Long storeBinary = exec(commandObjects.georadiusStore(binaryKey, PALERMO_LONGITUDE, PALERMO_LATITUDE, 200, GeoUnit.KM, param, storeParamForBinary));\n    assertThat(storeBinary, equalTo(2L));\n\n    destination = exec(commandObjects.zrange(binaryDestinationKey, 0, -1));\n    assertThat(destination, containsInAnyOrder(PALERMO, CATANIA));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testGeoradiusByMemberStore() {\n    String key = \"locations\";\n    byte[] binaryKey = key.getBytes();\n\n    String destinationKey = \"result\";\n    String binaryDestinationKey = \"resultBinary\";\n\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().sortAscending();\n\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam().store(destinationKey);\n\n    Long store = exec(commandObjects.georadiusByMemberStore(key, PALERMO, 200, GeoUnit.KM, param, storeParam));\n    assertThat(store, equalTo(2L));\n\n    List<String> storedResults = exec(commandObjects.zrange(destinationKey, 0, -1));\n    assertThat(storedResults, containsInAnyOrder(PALERMO, CATANIA));\n\n    GeoRadiusStoreParam storeParamForBinary = GeoRadiusStoreParam.geoRadiusStoreParam().store(binaryDestinationKey);\n\n    Long storeBinary = exec(commandObjects.georadiusByMemberStore(binaryKey, PALERMO.getBytes(), 200, GeoUnit.KM, param, storeParamForBinary));\n    assertThat(storeBinary, equalTo(2L));\n\n    storedResults = exec(commandObjects.zrange(binaryDestinationKey, 0, -1));\n    assertThat(storedResults, containsInAnyOrder(PALERMO, CATANIA));\n  }\n\n  @Test\n  public void testGeosearch() {\n    String key = \"locations\";\n\n    GeoCoordinate palermoCoord = new GeoCoordinate(PALERMO_LONGITUDE, PALERMO_LATITUDE);\n\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n\n    List<GeoRadiusResponse> resultsByMember = exec(commandObjects.geosearch(key, PALERMO, 200, GeoUnit.KM));\n\n    assertThat(resultsByMember.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO, CATANIA));\n\n    List<GeoRadiusResponse> resultsByCoord = exec(commandObjects.geosearch(key, palermoCoord, 200, GeoUnit.KM));\n\n    assertThat(resultsByCoord.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO, CATANIA));\n\n    List<GeoRadiusResponse> resultsByMemberBox = exec(commandObjects.geosearch(key, PALERMO, 200, 200, GeoUnit.KM));\n\n    assertThat(resultsByMemberBox.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO));\n\n    List<GeoRadiusResponse> resultsByCoordBox = exec(commandObjects.geosearch(key, palermoCoord, 200, 200, GeoUnit.KM));\n\n    assertThat(resultsByCoordBox.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO));\n\n    GeoSearchParam params = GeoSearchParam.geoSearchParam()\n        .byRadius(200, GeoUnit.KM).withCoord().withDist().fromMember(PALERMO);\n\n    List<GeoRadiusResponse> resultsWithParams = exec(commandObjects.geosearch(key, params));\n\n    assertThat(resultsWithParams.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO, CATANIA));\n\n    List<GeoRadiusResponse> resultsInvalidKey = exec(commandObjects.geosearch(\"invalidKey\", PALERMO, 100, GeoUnit.KM));\n\n    assertThat(resultsInvalidKey, empty());\n  }\n\n  @Test\n  public void testGeosearchBinary() {\n    byte[] key = \"locations\".getBytes();\n\n    GeoCoordinate palermoCoord = new GeoCoordinate(PALERMO_LONGITUDE, PALERMO_LATITUDE);\n\n    exec(commandObjects.geoadd(key, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO.getBytes()));\n    exec(commandObjects.geoadd(key, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA.getBytes()));\n\n    List<GeoRadiusResponse> resultsByMember = exec(commandObjects.geosearch(key, PALERMO.getBytes(), 200, GeoUnit.KM));\n\n    assertThat(resultsByMember.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO, CATANIA));\n\n    List<GeoRadiusResponse> resultsByCoord = exec(commandObjects.geosearch(key, palermoCoord, 200, GeoUnit.KM));\n\n    assertThat(resultsByCoord.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO, CATANIA));\n\n    List<GeoRadiusResponse> resultsByMemberBox = exec(commandObjects.geosearch(key, PALERMO.getBytes(), 200, 200, GeoUnit.KM));\n\n    assertThat(resultsByMemberBox.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO));\n\n    List<GeoRadiusResponse> resultsByCoordBox = exec(commandObjects.geosearch(key, palermoCoord, 200, 200, GeoUnit.KM));\n\n    assertThat(resultsByCoordBox.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO));\n\n    GeoSearchParam params = GeoSearchParam.geoSearchParam()\n        .byRadius(200, GeoUnit.KM).withCoord().withDist().fromMember(PALERMO);\n\n    List<GeoRadiusResponse> resultsWithParams = exec(commandObjects.geosearch(key, params));\n\n    assertThat(resultsWithParams.stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(PALERMO, CATANIA));\n\n    List<GeoRadiusResponse> resultsInvalidKey = exec(commandObjects.geosearch(\"invalidKey\".getBytes(), PALERMO.getBytes(), 100, GeoUnit.KM));\n\n    assertThat(resultsInvalidKey, empty());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testGeosearchStore() {\n    String srcKey = \"locations\";\n    String destKey = \"locationsStore\";\n\n    GeoCoordinate palermoCoord = new GeoCoordinate(PALERMO_LONGITUDE, PALERMO_LATITUDE);\n\n    exec(commandObjects.geoadd(srcKey, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(srcKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n\n    Long storeByMember = exec(commandObjects.geosearchStore(destKey, srcKey, PALERMO, 200, GeoUnit.KM));\n    assertThat(storeByMember, equalTo(2L));\n\n    List<String> storedResultsByMember = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByMember, containsInAnyOrder(PALERMO, CATANIA));\n\n    // Reset\n    exec(commandObjects.del(destKey));\n\n    Long storeByCoord = exec(commandObjects.geosearchStore(destKey, srcKey, palermoCoord, 200, GeoUnit.KM));\n    assertThat(storeByCoord, equalTo(2L));\n\n    List<String> storedResultsByCoord = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByCoord, containsInAnyOrder(PALERMO, CATANIA));\n\n    exec(commandObjects.del(destKey));\n\n    Long storeByMemberBox = exec(commandObjects.geosearchStore(destKey, srcKey, PALERMO, 200, 200, GeoUnit.KM));\n    assertThat(storeByMemberBox, equalTo(1L));\n\n    List<String> storedResultsByMemberBox = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByMemberBox, containsInAnyOrder(PALERMO));\n\n    exec(commandObjects.del(destKey));\n\n    Long storeByCoordBox = exec(commandObjects.geosearchStore(destKey, srcKey, palermoCoord, 200, 200, GeoUnit.KM));\n    assertThat(storeByCoordBox, equalTo(1L));\n\n    List<String> storedResultsByCoordBox = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByCoordBox, containsInAnyOrder(PALERMO));\n\n    exec(commandObjects.del(destKey));\n\n    GeoSearchParam params = GeoSearchParam.geoSearchParam()\n        .byRadius(200, GeoUnit.KM).fromMember(PALERMO);\n\n    Long storeWithParams = exec(commandObjects.geosearchStore(destKey, srcKey, params));\n    assertThat(storeWithParams, equalTo(2L));\n\n    List<String> storedResultsWithParams = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsWithParams, containsInAnyOrder(PALERMO, CATANIA));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testGeosearchStoreBinary() {\n    byte[] srcKey = \"locations\".getBytes();\n    byte[] destKey = \"locationsStore\".getBytes();\n\n    GeoCoordinate palermoCoord = new GeoCoordinate(PALERMO_LONGITUDE, PALERMO_LATITUDE);\n\n    exec(commandObjects.geoadd(srcKey, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO.getBytes()));\n    exec(commandObjects.geoadd(srcKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA.getBytes()));\n\n    Long storeByMember = exec(commandObjects.geosearchStore(destKey, srcKey, PALERMO.getBytes(), 200, GeoUnit.KM));\n    assertThat(storeByMember, equalTo(2L));\n\n    List<byte[]> storedResultsByMember = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByMember, containsInAnyOrder(PALERMO.getBytes(), CATANIA.getBytes()));\n\n    // Reset\n    exec(commandObjects.del(destKey));\n\n    Long storeByCoord = exec(commandObjects.geosearchStore(destKey, srcKey, palermoCoord, 200, GeoUnit.KM));\n    assertThat(storeByCoord, equalTo(2L));\n\n    List<byte[]> storedResultsByCoord = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByCoord, containsInAnyOrder(PALERMO.getBytes(), CATANIA.getBytes()));\n\n    exec(commandObjects.del(destKey));\n\n    Long storeByMemberBox = exec(commandObjects.geosearchStore(destKey, srcKey, PALERMO.getBytes(), 200, 200, GeoUnit.KM));\n    assertThat(storeByMemberBox, equalTo(1L));\n\n    List<byte[]> storedResultsByMemberBox = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByMemberBox, containsInAnyOrder(PALERMO.getBytes()));\n\n    exec(commandObjects.del(destKey));\n\n    Long storeByCoordBox = exec(commandObjects.geosearchStore(destKey, srcKey, palermoCoord, 200, 200, GeoUnit.KM));\n    assertThat(storeByCoordBox, equalTo(1L));\n\n    List<byte[]> storedResultsByCoordBox = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsByCoordBox, containsInAnyOrder(PALERMO.getBytes()));\n\n    exec(commandObjects.del(destKey));\n\n    GeoSearchParam params = GeoSearchParam.geoSearchParam()\n        .byRadius(200, GeoUnit.KM).fromMember(PALERMO);\n\n    Long storeWithParams = exec(commandObjects.geosearchStore(destKey, srcKey, params));\n    assertThat(storeWithParams, equalTo(2L));\n\n    List<byte[]> storedResultsWithParams = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(storedResultsWithParams, containsInAnyOrder(PALERMO.getBytes(), CATANIA.getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testGeosearchStoreStoreDist() {\n    String srcKey = \"locations\";\n    byte[] srcKeyBytes = srcKey.getBytes();\n\n    String destKey = \"resultKey\";\n    byte[] destKeyBytes = destKey.getBytes();\n\n    exec(commandObjects.geoadd(srcKey, PALERMO_LONGITUDE, PALERMO_LATITUDE, PALERMO));\n    exec(commandObjects.geoadd(srcKey, CATANIA_LONGITUDE, CATANIA_LATITUDE, CATANIA));\n    exec(commandObjects.geoadd(srcKey, SYRACUSE_LONGITUDE, SYRACUSE_LATITUDE, SYRACUSE));\n\n    GeoSearchParam params = new GeoSearchParam()\n        .byRadius(100, GeoUnit.KM).fromLonLat(15, 37);\n\n    Long store = exec(commandObjects.geosearchStoreStoreDist(destKey, srcKey, params));\n    assertThat(store, equalTo(2L));\n\n    List<String> dstContent = exec(commandObjects.zrange(destKey, 0, -1));\n    assertThat(dstContent, containsInAnyOrder(CATANIA, SYRACUSE));\n\n    exec(commandObjects.del(destKey));\n\n    Long storeWithBytes = exec(commandObjects.geosearchStoreStoreDist(destKeyBytes, srcKeyBytes, params));\n    assertThat(storeWithBytes, equalTo(2L));\n\n    List<byte[]> dstContentWithBytes = exec(commandObjects.zrange(destKeyBytes, 0, -1));\n    assertThat(dstContentWithBytes, containsInAnyOrder(CATANIA.getBytes(), SYRACUSE.getBytes()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsHashCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static java.util.Arrays.asList;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.both;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.everyItem;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.hamcrest.Matchers.nullValue;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\nimport static org.hamcrest.CoreMatchers.is;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=hash\">Hash</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsHashCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  private final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  private final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n\n  private final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  private final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  private final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n\n  public CommandObjectsHashCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testHashSetGet() {\n    String key = \"hashKey\";\n    String field = \"name\";\n    String value = \"John\";\n\n    String getInitial = exec(commandObjects.hget(key, field));\n    assertThat(getInitial, nullValue());\n\n    Long set = exec(commandObjects.hset(key, field, value));\n    assertThat(set, equalTo(1L));\n\n    String get = exec(commandObjects.hget(key, field));\n    assertThat(get, equalTo(value));\n  }\n\n  @Test\n  public void testHashSetGetBinary() {\n    byte[] key = \"hashKeyBytes\".getBytes();\n    byte[] field = \"field\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    byte[] getInitial = exec(commandObjects.hget(key, field));\n    assertThat(getInitial, nullValue());\n\n    Long set = exec(commandObjects.hset(key, field, value));\n    assertThat(set, equalTo(1L));\n\n    byte[] get = exec(commandObjects.hget(key, field));\n    assertThat(get, equalTo(value));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void testHgetex() {\n    String key = \"hashKey\";\n    String field = \"name\";\n    String value = \"John\";\n    long seconds = 20;\n\n    exec(commandObjects.hset(key, field, value));\n    List<String> fieldValues = exec(\n      commandObjects.hgetex(key, new HGetExParams().ex(seconds), field));\n    assertThat(fieldValues, is(Collections.singletonList(value)));\n\n    List<Long> ttlList = exec(commandObjects.httl(key, field));\n    Long ttl = ttlList.get(0);\n    assertThat(ttl, greaterThanOrEqualTo(seconds - 1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void testHgetexBinary() {\n    byte[] key = \"hashKeyBytes\".getBytes();\n    byte[] field = \"name\".getBytes();\n    byte[] value = \"John\".getBytes();\n    long seconds = 20;\n\n    exec(commandObjects.hset(key, field, value));\n    List<byte[]> get = exec(commandObjects.hgetex(key, new HGetExParams().ex(seconds), field));\n    assertByteArrayListEquals(get, Collections.singletonList(value));\n\n    List<Long> ttlList = exec(commandObjects.httl(key, field));\n    Long ttl = ttlList.get(0);\n    assertThat(ttl, greaterThanOrEqualTo(seconds - 1));\n    assertThat(ttl, greaterThanOrEqualTo(seconds - 1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void testHsetex() {\n    String key = \"hashKey\";\n    String field = \"name\";\n    String value = \"John\";\n    long seconds = 20;\n\n    Long set = exec(commandObjects.hsetex(key, new HSetExParams().ex(seconds), field, value));\n    assertThat(set, equalTo(1L));\n\n    String get = exec(commandObjects.hget(key, field));\n    assertThat(get, equalTo(value));\n\n    List<Long> ttlList = exec(commandObjects.httl(key, field));\n    Long ttl = ttlList.get(0);\n    assertThat(ttl, greaterThanOrEqualTo(seconds - 1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void testHsetexBinary() {\n    byte[] key = \"hashKeyBytes\".getBytes();\n    byte[] field = \"name\".getBytes();\n    byte[] value = \"John\".getBytes();\n    long seconds = 20;\n    Long set = exec(commandObjects.hsetex(key, new HSetExParams().ex(seconds), field, value));\n    assertThat(set, equalTo(1L));\n\n    byte[] get = exec(commandObjects.hget(key, field));\n    assertThat(get, equalTo(value));\n\n    List<Long> ttlList = exec(commandObjects.httl(key, field));\n    Long ttl = ttlList.get(0);\n    assertThat(ttl, greaterThanOrEqualTo(seconds - 1));\n    assertThat(ttl, greaterThanOrEqualTo(seconds - 1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void testHgetdel() {\n    String key = \"hashKey\";\n    String field = \"name\";\n    String value = \"John\";\n\n    exec(commandObjects.hset(key, field, value));\n\n    List<String> getDel = exec(commandObjects.hgetdel(key, field));\n    assertThat(getDel, is(Collections.singletonList(value)));\n\n    String getAfterDel = exec(commandObjects.hget(key, field));\n    assertThat(getAfterDel, nullValue());\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void testHgetdelBinary() {\n    byte[] key = \"hashKeyBytes\".getBytes();\n    byte[] field = \"field\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.hset(key, field, value));\n\n    List<byte[]> getDel = exec(commandObjects.hgetdel(key, field));\n    assertByteArrayListEquals(Collections.singletonList(value), getDel);\n\n    byte[] getAfterDel = exec(commandObjects.hget(key, field));\n    assertThat(getAfterDel, nullValue());\n  }\n\n  @Test\n  public void testHashBulkSet() {\n    String key = \"hashKey\";\n\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n\n    Long set = exec(commandObjects.hset(key, hash));\n    assertThat(set, equalTo((long) hash.size()));\n\n    List<String> mget = exec(commandObjects.hmget(key, \"field1\", \"field2\"));\n    assertThat(mget, contains(\"value1\", \"value2\"));\n  }\n\n  @Test\n  public void testHashBulkSetBinary() {\n    byte[] key = \"hashKey\".getBytes();\n\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n    hash.put(\"field2\".getBytes(), \"value2\".getBytes());\n\n    Long set = exec(commandObjects.hset(key, hash));\n    assertThat(set, equalTo((long) hash.size()));\n\n    List<byte[]> mget = exec(commandObjects.hmget(key, \"field1\".getBytes(), \"field2\".getBytes()));\n    assertThat(mget, contains(\"value1\".getBytes(), \"value2\".getBytes()));\n  }\n\n  @Test\n  public void testHashMsetMget() {\n    String key = \"bulkHashKey\";\n\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n\n    String mset = exec(commandObjects.hmset(key, hash));\n    assertThat(mset, equalTo(\"OK\"));\n\n    List<String> mget = exec(commandObjects.hmget(key, \"field1\", \"field2\"));\n    assertThat(mget, contains(\"value1\", \"value2\"));\n  }\n\n  @Test\n  public void testHashMsetMgetBinary() {\n    byte[] key = \"hashKey\".getBytes();\n\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n    hash.put(\"field2\".getBytes(), \"value2\".getBytes());\n\n    String mset = exec(commandObjects.hmset(key, hash));\n    assertThat(mset, equalTo(\"OK\"));\n\n    List<byte[]> mget = exec(commandObjects.hmget(key, \"field1\".getBytes(), \"field2\".getBytes()));\n    assertThat(mget, contains(\"value1\".getBytes(), \"value2\".getBytes()));\n  }\n\n  @Test\n  public void testHsetnx() {\n    String key = \"hashKey\";\n    String field = \"field\";\n    String value = \"value\";\n\n    String initialGet = exec(commandObjects.hget(key, field));\n    assertThat(initialGet, nullValue());\n\n    Long initialSet = exec(commandObjects.hsetnx(key, field, value));\n    assertThat(initialSet, equalTo(1L));\n\n    String get = exec(commandObjects.hget(key, field));\n    assertThat(get, equalTo(value));\n\n    Long secondSet = exec(commandObjects.hsetnx(key, field, \"newValue\"));\n    assertThat(secondSet, equalTo(0L));\n\n    String secondGet = exec(commandObjects.hget(key, field));\n    assertThat(secondGet, equalTo(value));\n  }\n\n  @Test\n  public void testHsetnxBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    byte[] initialGet = exec(commandObjects.hget(key, field));\n    assertThat(initialGet, nullValue());\n\n    Long set = exec(commandObjects.hsetnx(key, field, value));\n    assertThat(set, equalTo(1L));\n\n    byte[] get = exec(commandObjects.hget(key, field));\n    assertThat(get, equalTo(value));\n\n    Long secondSet = exec(commandObjects.hsetnx(key, field, \"newValue\".getBytes()));\n    assertThat(secondSet, equalTo(0L));\n\n    byte[] secondGet = exec(commandObjects.hget(key, field));\n    assertThat(secondGet, equalTo(value));\n  }\n\n  @Test\n  public void testHincrBy() {\n    String key = \"incrementHashKey\";\n    String field = \"incrementField\";\n\n    Long initialSet = exec(commandObjects.hset(key, field, \"0\"));\n    assertThat(initialSet, equalTo(1L));\n\n    String initialGet = exec(commandObjects.hget(key, field));\n    assertThat(initialGet, equalTo(\"0\"));\n\n    Long incrByLong = exec(commandObjects.hincrBy(key, field, 10L));\n    assertThat(incrByLong, equalTo(10L));\n\n    String getAfterIncrByLong = exec(commandObjects.hget(key, field));\n    assertThat(getAfterIncrByLong, equalTo(\"10\"));\n\n    Double incrByFloat = exec(commandObjects.hincrByFloat(key, field, 2.5));\n    assertThat(incrByFloat, equalTo(12.5));\n\n    String getAfterIncrByFloat = exec(commandObjects.hget(key, field));\n    assertThat(getAfterIncrByFloat, equalTo(\"12.5\"));\n  }\n\n  @Test\n  public void testHincrByBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] field = \"field\".getBytes();\n\n    Long initialSet = exec(commandObjects.hset(key, field, \"0\".getBytes()));\n    assertThat(initialSet, equalTo(1L));\n\n    byte[] initialGet = exec(commandObjects.hget(key, field));\n    assertThat(initialGet, equalTo(\"0\".getBytes()));\n\n    Long incrByLong = exec(commandObjects.hincrBy(key, field, 10L));\n    assertThat(incrByLong, equalTo(10L));\n\n    byte[] getAfterIncrByLong = exec(commandObjects.hget(key, field));\n    assertThat(getAfterIncrByLong, equalTo(\"10\".getBytes()));\n\n    Double incrByDouble = exec(commandObjects.hincrByFloat(key, field, 2.5));\n    assertThat(incrByDouble, equalTo(12.5));\n\n    byte[] getAfterIncrByDouble = exec(commandObjects.hget(key, field));\n    assertThat(getAfterIncrByDouble, equalTo(\"12.5\".getBytes()));\n  }\n\n  @Test\n  public void testHashExistsDel() {\n    String key = \"key\";\n    String field1 = \"field1\";\n    String field2 = \"field2\";\n    String value = \"value\";\n\n    exec(commandObjects.hset(key, field1, value));\n    exec(commandObjects.hset(key, field2, value));\n\n    Boolean exists = exec(commandObjects.hexists(key, field1));\n    assertThat(exists, equalTo(true));\n\n    Long len = exec(commandObjects.hlen(key));\n    assertThat(len, equalTo(2L));\n\n    Long del = exec(commandObjects.hdel(key, field1));\n    assertThat(del, equalTo(1L));\n\n    Boolean existsAfterDel = exec(commandObjects.hexists(key, field1));\n    assertThat(existsAfterDel, equalTo(false));\n\n    Long lenAfterDel = exec(commandObjects.hlen(key));\n    assertThat(lenAfterDel, equalTo(1L));\n  }\n\n  @Test\n  public void testHashExistsDelBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] field1 = \"field1\".getBytes();\n    byte[] field2 = \"field2\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.hset(key, field1, value));\n    exec(commandObjects.hset(key, field2, value));\n\n    Boolean exists = exec(commandObjects.hexists(key, field1));\n    assertThat(exists, equalTo(true));\n\n    Long len = exec(commandObjects.hlen(key));\n    assertThat(len, equalTo(2L));\n\n    Long del = exec(commandObjects.hdel(key, field1));\n    assertThat(del, equalTo(1L));\n\n    Boolean existsAfterDel = exec(commandObjects.hexists(key, field1));\n    assertThat(existsAfterDel, equalTo(false));\n\n    Long lenAfterDel = exec(commandObjects.hlen(key));\n    assertThat(lenAfterDel, equalTo(1L));\n  }\n\n  @Test\n  public void testHashKeysValsGetAll() {\n    String key = \"hashKey\";\n    byte[] keyBinary = key.getBytes();\n\n    String field1 = \"field1\";\n    String field2 = \"field2\";\n    String value1 = \"value1\";\n    String value2 = \"value2\";\n\n    exec(commandObjects.hset(key, field1, value1));\n    exec(commandObjects.hset(key, field2, value2));\n\n    Set<String> keys = exec(commandObjects.hkeys(key));\n    assertThat(keys, containsInAnyOrder(field1, field2));\n\n    List<String> values = exec(commandObjects.hvals(key));\n    assertThat(values, containsInAnyOrder(value1, value2));\n\n    Map<String, String> hash = exec(commandObjects.hgetAll(key));\n    assertThat(hash, allOf(\n        hasEntry(field1, value1),\n        hasEntry(field2, value2)));\n\n    // binary\n    Set<byte[]> keysBinary = exec(commandObjects.hkeys(keyBinary));\n    assertThat(keysBinary, containsInAnyOrder(field1.getBytes(), field2.getBytes()));\n\n    List<byte[]> valuesBinary = exec(commandObjects.hvals(keyBinary));\n    assertThat(valuesBinary, containsInAnyOrder(value1.getBytes(), value2.getBytes()));\n\n    Map<byte[], byte[]> hashBinary = exec(commandObjects.hgetAll(keyBinary));\n    assertThat(hashBinary, allOf(\n        hasEntry(field1.getBytes(), value1.getBytes()),\n        hasEntry(field2.getBytes(), value2.getBytes())));\n  }\n\n  @Test\n  public void testHashRandfield() {\n    String key = \"testHash\";\n    byte[] bkey = key.getBytes();\n\n    exec(commandObjects.hset(key, \"field1\", \"value1\"));\n    exec(commandObjects.hset(key, \"field2\", \"value2\"));\n\n    String singleField = exec(commandObjects.hrandfield(key));\n    assertThat(singleField, anyOf(equalTo(\"field1\"), equalTo(\"field2\")));\n\n    List<String> fields = exec(commandObjects.hrandfield(key, 2));\n    assertThat(fields, containsInAnyOrder(\"field1\", \"field2\"));\n\n    List<Map.Entry<String, String>> fieldsWithValues = exec(commandObjects.hrandfieldWithValues(key, 2));\n\n    assertThat(fieldsWithValues, hasSize(2));\n    fieldsWithValues.forEach(entry ->\n        assertThat(entry.getValue(), anyOf(equalTo(\"value1\"), equalTo(\"value2\"))));\n\n    // binary\n    byte[] singleFieldBinary = exec(commandObjects.hrandfield(bkey));\n    assertThat(singleFieldBinary, anyOf(equalTo(\"field1\".getBytes()), equalTo(\"field2\".getBytes())));\n\n    List<byte[]> fieldsBinary = exec(commandObjects.hrandfield(bkey, 2));\n    assertThat(fieldsBinary, containsInAnyOrder(\"field1\".getBytes(), \"field2\".getBytes()));\n\n    List<Map.Entry<byte[], byte[]>> fieldsWithValuesBinary = exec(commandObjects.hrandfieldWithValues(bkey, 2));\n\n    assertThat(fieldsWithValuesBinary, hasSize(2));\n    fieldsWithValuesBinary.forEach(entry ->\n        assertThat(entry.getValue(), anyOf(equalTo(\"value1\".getBytes()), equalTo(\"value2\".getBytes()))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void testHscan() {\n    String key = \"testHashScan\";\n    byte[] bkey = key.getBytes();\n\n    exec(commandObjects.hset(key, \"field1\", \"value1\"));\n    exec(commandObjects.hset(key, \"field2\", \"value2\"));\n\n    ScanParams params = new ScanParams().count(2);\n\n    ScanResult<Map.Entry<String, String>> scanResult = exec(commandObjects.hscan(key, ScanParams.SCAN_POINTER_START, params));\n\n    assertThat(scanResult.getResult(), hasSize(lessThanOrEqualTo(2)));\n\n    scanResult.getResult().forEach(entry ->\n        assertThat(entry.getKey(), anyOf(equalTo(\"field1\"), equalTo(\"field2\"))));\n    scanResult.getResult().forEach(entry ->\n        assertThat(entry.getValue(), anyOf(equalTo(\"value1\"), equalTo(\"value2\"))));\n\n    ScanResult<String> scanResultNoValues = exec(commandObjects.hscanNoValues(key, ScanParams.SCAN_POINTER_START, params));\n\n    assertThat(scanResultNoValues.getResult(), hasSize(lessThanOrEqualTo(2)));\n\n    assertThat(scanResultNoValues.getResult(),\n        everyItem(anyOf(equalTo(\"field1\"), equalTo(\"field2\"))));\n\n    // binary\n    ScanResult<Map.Entry<byte[], byte[]>> bscanResult = exec(commandObjects.hscan(bkey, ScanParams.SCAN_POINTER_START_BINARY, params));\n\n    assertThat(bscanResult.getResult(), hasSize(lessThanOrEqualTo(2)));\n\n    bscanResult.getResult().forEach(entry ->\n        assertThat(entry.getKey(), anyOf(equalTo(\"field1\".getBytes()), equalTo(\"field2\".getBytes()))));\n    bscanResult.getResult().forEach(entry ->\n        assertThat(entry.getValue(), anyOf(equalTo(\"value1\".getBytes()), equalTo(\"value2\".getBytes()))));\n\n    ScanResult<byte[]> bscanResultNoValues = exec(commandObjects.hscanNoValues(bkey, ScanParams.SCAN_POINTER_START_BINARY, params));\n\n    assertThat(bscanResultNoValues.getResult(), hasSize(lessThanOrEqualTo(2)));\n\n    assertThat(bscanResultNoValues.getResult(),\n        everyItem(anyOf(equalTo(\"field1\".getBytes()), equalTo(\"field2\".getBytes()))));\n  }\n\n  @Test\n  public void testHashStrlen() {\n    String key = \"testHashStrlen\";\n    byte[] bkey = key.getBytes();\n\n    exec(commandObjects.hset(key, \"field1\", \"value1\"));\n\n    Long strlen = exec(commandObjects.hstrlen(key, \"field1\"));\n    assertThat(strlen, equalTo(6L));\n\n    Long strlenNonExistingField = exec(commandObjects.hstrlen(key, \"nonExistingField\"));\n    assertThat(strlenNonExistingField, equalTo(0L));\n\n    // binary\n    Long strlenBinary = exec(commandObjects.hstrlen(bkey, \"field1\".getBytes()));\n    assertThat(strlenBinary, equalTo(6L));\n\n    Long strlenNonExistingFieldBinary = exec(commandObjects.hstrlen(bkey, \"nonExistingField\".getBytes()));\n    assertThat(strlenNonExistingFieldBinary, equalTo(0L));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAndHttl() {\n    long seconds1 = 20;\n    long seconds2 = 10;\n\n    exec(commandObjects.hset(\"foo\", \"bar\", \"car\"));\n    exec(commandObjects.hset(\"foo\", \"bare\", \"care\"));\n    assertThat(exec(commandObjects.hexpire(\"foo\", seconds1, \"bar\", \"bared\")), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(\"foo\", \"bared\", \"cared\"));\n    assertThat(exec(commandObjects.hexpire(\"foo\", seconds2, ExpiryOption.NX, \"bar\", \"bared\")), equalTo(asList(0L, 1L)));\n\n    assertThat(exec(commandObjects.httl(\"foo\", \"bar\", \"bare\", \"bared\")),\n        contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAndHttlBinary() {\n    long seconds1 = 20;\n    long seconds2 = 10;\n\n    exec(commandObjects.hset(bfoo, bbar1, bcar));\n    exec(commandObjects.hset(bfoo, bbar2, bcar));\n    assertThat(exec(commandObjects.hexpire(bfoo, seconds1, bbar1, bbar3)), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(bfoo, bbar3, bcar));\n    assertThat(exec(commandObjects.hexpire(bfoo, seconds2, ExpiryOption.NX, bbar1, bbar3)), equalTo(asList(0L, 1L)));\n\n    assertThat(exec(commandObjects.httl(bfoo, bbar1, bbar2, bbar3)),\n        contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAndHpttl() {\n    long millis1 = 20_000;\n    long millis2 = 10_000;\n\n    exec(commandObjects.hset(\"foo\", \"bar\", \"car\"));\n    assertThat(exec(commandObjects.hpexpire(\"foo\", millis1, \"bar\", \"bared\")), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(\"foo\", \"bared\", \"cared\"));\n    assertThat(exec(commandObjects.hpexpire(\"foo\", millis2, ExpiryOption.XX, \"bar\", \"bared\")), equalTo(asList(1L, 0L)));\n\n    assertThat(exec(commandObjects.hpttl(\"foo\", \"bar\", \"bare\", \"bared\")),\n        contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 1000)), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAndHpttlBinary() {\n    long millis1 = 20_000;\n    long millis2 = 10_000;\n\n    exec(commandObjects.hset(bfoo, bbar1, bcar));\n    assertThat(exec(commandObjects.hpexpire(bfoo, millis1, bbar1, bbar3)), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(bfoo, bbar3, bcar));\n    assertThat(exec(commandObjects.hpexpire(bfoo, millis2, ExpiryOption.XX, bbar1, bbar3)), equalTo(asList(1L, 0L)));\n\n    assertThat(exec(commandObjects.hpttl(bfoo, bbar1, bbar2, bbar3)),\n        contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 1000)), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAtAndExpireTime() {\n    long currSeconds = System.currentTimeMillis() / 1000;\n    long seconds1 = currSeconds + 20;\n    long seconds2 = currSeconds + 10;\n\n    exec(commandObjects.hset(\"foo\", \"bar\", \"car\"));\n    exec(commandObjects.hset(\"foo\", \"bare\", \"care\"));\n    assertThat(exec(commandObjects.hexpireAt(\"foo\", seconds1, \"bar\", \"bared\")), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(\"foo\", \"bared\", \"cared\"));\n    assertThat(exec(commandObjects.hexpireAt(\"foo\", seconds2, ExpiryOption.LT, \"bar\", \"bared\")), equalTo(asList(1L, 1L)));\n\n    assertThat(exec(commandObjects.hexpireTime(\"foo\", \"bar\", \"bare\", \"bared\")),\n        contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAtAndExpireTimeBinary() {\n    long currSeconds = System.currentTimeMillis() / 1000;\n    long seconds1 = currSeconds + 20;\n    long seconds2 = currSeconds + 10;\n\n    exec(commandObjects.hset(bfoo, bbar1, bcar));\n    exec(commandObjects.hset(bfoo, bbar2, bcar));\n    assertThat(exec(commandObjects.hexpireAt(bfoo, seconds1, bbar1, bbar3)), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(bfoo, bbar3, bcar));\n    assertThat(exec(commandObjects.hexpireAt(bfoo, seconds2, ExpiryOption.LT, bbar1, bbar3)), equalTo(asList(1L, 1L)));\n\n    assertThat(exec(commandObjects.hexpireTime(bfoo, bbar1, bbar2, bbar3)),\n        contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAtAndPexpireTime() {\n    long currMillis = System.currentTimeMillis();\n    long unixMillis = currMillis + 20_000;\n\n    exec(commandObjects.hset(\"foo\", \"bar\", \"car\"));\n    assertThat(exec(commandObjects.hpexpireAt(\"foo\", unixMillis - 100, \"bar\", \"bared\")), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(\"foo\", \"bared\", \"cared\"));\n    assertThat(exec(commandObjects.hpexpireAt(\"foo\", unixMillis, ExpiryOption.GT, \"bar\", \"bared\")), equalTo(asList(1L, 0L)));\n\n    assertThat(exec(commandObjects.hpexpireTime(\"foo\", \"bar\", \"bare\", \"bared\")),\n        contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAtAndPexpireTimeBinary() {\n    long currMillis = System.currentTimeMillis();\n    long unixMillis = currMillis + 20_000;\n\n    exec(commandObjects.hset(bfoo, bbar1, bcar));\n    assertThat(exec(commandObjects.hpexpireAt(bfoo, unixMillis - 100, bbar1, bbar3)), equalTo(asList(1L, -2L)));\n\n    exec(commandObjects.hset(bfoo, bbar3, bcar));\n    assertThat(exec(commandObjects.hpexpireAt(bfoo, unixMillis, ExpiryOption.GT, bbar1, bbar3)), equalTo(asList(1L, 0L)));\n\n    assertThat(exec(commandObjects.hpexpireTime(bfoo, bbar1, bbar2, bbar3)),\n        contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpersist() {\n    long seconds = 20;\n\n    exec(commandObjects.hset(\"foo\", \"bar\", \"car\"));\n    exec(commandObjects.hset(\"foo\", \"bare\", \"care\"));\n    assertThat(exec(commandObjects.hexpire(\"foo\", seconds, \"bar\", \"bared\")), equalTo(asList(1L, -2L)));\n\n    assertThat(exec(commandObjects.hpersist(\"foo\", \"bar\", \"bare\", \"bared\")), equalTo(asList(1L, -1L, -2L)));\n\n    assertThat(exec(commandObjects.httl(\"foo\", \"bar\", \"bare\", \"bared\")),\n        contains(equalTo(-1L), equalTo(-1L), equalTo(-2L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpersistBinary() {\n    long seconds = 20;\n\n    exec(commandObjects.hset(bfoo, bbar1, bcar));\n    exec(commandObjects.hset(bfoo, bbar2, bcar));\n    assertThat(exec(commandObjects.hexpire(bfoo, seconds, bbar1, bbar3)), equalTo(asList(1L, -2L)));\n\n    assertThat(exec(commandObjects.hpersist(bfoo, bbar1, bbar2, bbar3)), equalTo(asList(1L, -1L, -2L)));\n\n    assertThat(exec(commandObjects.httl(bfoo, bbar1, bbar2, bbar3)),\n        contains(equalTo(-1L), equalTo(-1L), equalTo(-2L)));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsHyperloglogCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=hyperloglog\">HyperLogLog</a> commands.\n */\npublic class CommandObjectsHyperloglogCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsHyperloglogCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testPfaddAndCount() {\n    String key = \"hyperloglogKey\";\n\n    Long add = exec(commandObjects.pfadd(key, \"element1\", \"element2\", \"element3\"));\n    assertThat(add, equalTo(1L));\n\n    Long count = exec(commandObjects.pfcount(key));\n    assertThat(count, greaterThanOrEqualTo(3L)); // approximate, expect at least 3\n\n    Long addNewElement = exec(commandObjects.pfadd(key, \"element4\"));\n    assertThat(addNewElement, equalTo(1L));\n\n    Long countWithNewElement = exec(commandObjects.pfcount(key));\n    assertThat(countWithNewElement, greaterThan(count));\n  }\n\n  @Test\n  public void testPfaddAndCountBinary() {\n    byte[] key = \"hyperloglogKey\".getBytes();\n\n    Long add = exec(commandObjects.pfadd(key, \"element1\".getBytes(), \"element2\".getBytes(), \"element3\".getBytes()));\n    assertThat(add, equalTo(1L));\n\n    Long count = exec(commandObjects.pfcount(key));\n    assertThat(count, greaterThanOrEqualTo(3L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testPfmerge() {\n    String key1 = \"hyperloglog1\";\n    String key2 = \"hyperloglog2\";\n\n    exec(commandObjects.pfadd(key1, \"elementA\", \"elementB\"));\n    exec(commandObjects.pfadd(key2, \"elementC\", \"elementD\"));\n\n    String destKey = \"mergedHyperloglog\";\n    byte[] destKeyBytes = \"mergedHyperloglogBytes\".getBytes();\n\n    String mergeResultWithString = exec(commandObjects.pfmerge(destKey, key1, key2));\n    assertThat(mergeResultWithString, equalTo(\"OK\"));\n\n    Long countAfterMergeWithString = exec(commandObjects.pfcount(destKey));\n    assertThat(countAfterMergeWithString, greaterThanOrEqualTo(4L));\n\n    // binary\n    String mergeResultWithBytes = exec(commandObjects.pfmerge(destKeyBytes, key1.getBytes(), key2.getBytes()));\n    assertThat(mergeResultWithBytes, equalTo(\"OK\"));\n\n    Long countAfterMergeWithBytes = exec(commandObjects.pfcount(destKeyBytes));\n    assertThat(countAfterMergeWithBytes, greaterThanOrEqualTo(4L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testPfcount() {\n    String key1 = \"hyperloglogCount1\";\n    String key2 = \"hyperloglogCount2\";\n\n    exec(commandObjects.pfadd(key1, \"element1\", \"element2\", \"element3\"));\n    exec(commandObjects.pfadd(key2, \"element4\", \"element5\", \"element6\"));\n\n    Long countForKey1 = exec(commandObjects.pfcount(key1));\n    assertThat(countForKey1, greaterThanOrEqualTo(3L));\n\n    Long countForBothKeys = exec(commandObjects.pfcount(key1, key2));\n    assertThat(countForBothKeys, greaterThanOrEqualTo(6L));\n\n    // binary\n    Long countForKey1Binary = exec(commandObjects.pfcount(key1.getBytes()));\n    assertThat(countForKey1Binary, greaterThanOrEqualTo(3L));\n\n    Long countForBothKeysBinary = exec(commandObjects.pfcount(key1.getBytes(), key2.getBytes()));\n    assertThat(countForBothKeysBinary, greaterThanOrEqualTo(6L));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.Path2;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=json\">JSON</a> commands.\n */\npublic class CommandObjectsJsonCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsJsonCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testJsonSetAndJsonGet() {\n    String key = \"jsonKey\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"John Doe\");\n    person.put(\"age\", 30);\n\n    String setRoot = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(getRoot, jsonEquals(new JSONArray().put(person)));\n\n    JSONObject details = new JSONObject();\n    details.put(\"city\", \"New York\");\n\n    String setDeep = exec(commandObjects.jsonSet(key, new Path2(\"$.details\"), details));\n    assertThat(setDeep, equalTo(\"OK\"));\n\n    Object getDeep = exec(commandObjects.jsonGet(key, new Path2(\"$.details\")));\n    assertThat(getDeep, jsonEquals(new JSONArray().put(details)));\n\n    Object getFull = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    person.put(\"details\", details);\n    assertThat(getFull, jsonEquals(new JSONArray().put(person)));\n  }\n\n  @Test\n  public void testJsonSetWithEscape() {\n    String key = \"jsonKey\";\n\n    Map<String, Object> book = new HashMap<>();\n    book.put(\"title\", \"Learning JSON\");\n\n    String setRoot = exec(commandObjects.jsonSetWithEscape(key, Path2.ROOT_PATH, book));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray().put(new JSONObject(book));\n    assertThat(getRoot, jsonEquals(expected));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonSetJsonGetOldPath() {\n    String key = \"jsonKey\";\n\n    Map<String, Object> book = new HashMap<>();\n    book.put(\"author\", \"Jane Doe\");\n    book.put(\"title\", \"Advanced JSON Techniques\");\n\n    String setRoot = exec(commandObjects.jsonSet(key, Path.ROOT_PATH, book));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path.ROOT_PATH));\n    assertThat(getRoot, instanceOf(Map.class));\n\n    @SuppressWarnings(\"unchecked\")\n    Map<String, Object> getRootMap = (Map<String, Object>) getRoot;\n    assertThat(getRootMap, hasEntry(\"author\", \"Jane Doe\"));\n    assertThat(getRootMap, hasEntry(\"title\", \"Advanced JSON Techniques\"));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonSetWithPlainString() {\n    String key = \"jsonKey\";\n    String jsonString = \"{\\\"name\\\":\\\"John\\\"}\";\n\n    String setRoot = exec(commandObjects.jsonSetWithPlainString(key, Path.ROOT_PATH, jsonString));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path.ROOT_PATH));\n    assertThat(getRoot, instanceOf(Map.class));\n\n    @SuppressWarnings(\"unchecked\")\n    Map<String, Object> getRootMap = (Map<String, Object>) getRoot;\n    assertThat(getRootMap, hasEntry(\"name\", \"John\"));\n  }\n\n  @Test\n  public void testJsonSetWithParams() {\n    String key = \"jsonKey\";\n\n    JSONObject book = new JSONObject();\n    book.put(\"author\", \"Jane Doe\");\n    book.put(\"title\", \"Advanced JSON Techniques\");\n\n    JsonSetParams params = new JsonSetParams().nx();\n\n    String setRoot = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, book, params));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray().put(book);\n    assertThat(getRoot, jsonEquals(expected));\n  }\n\n  @Test\n  public void testJsonSetWithEscapeAndParams() {\n    String key = \"jsonKey\";\n\n    Map<String, Object> book = new HashMap<>();\n    book.put(\"author\", \"John Smith\");\n    book.put(\"title\", \"JSON Escaping 101\");\n\n    JsonSetParams params = new JsonSetParams().nx();\n\n    String setRoot = exec(commandObjects.jsonSetWithEscape(key, Path2.ROOT_PATH, book, params));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray().put(new JSONObject(book));\n    assertThat(getRoot, jsonEquals(expected));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonSetOldPathWithParams() {\n    String key = \"jsonKey\";\n\n    Map<String, Object> user = new HashMap<>();\n    user.put(\"username\", \"johndoe\");\n    user.put(\"accountType\", \"premium\");\n\n    JsonSetParams params = new JsonSetParams().nx();\n\n    String setRoot = exec(commandObjects.jsonSet(key, Path.ROOT_PATH, user, params));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path.ROOT_PATH));\n    assertThat(getRoot, instanceOf(Map.class));\n\n    @SuppressWarnings(\"unchecked\")\n    Map<String, Object> readResultMap = (Map<String, Object>) getRoot;\n    assertThat(readResultMap, hasEntry(\"username\", \"johndoe\"));\n    assertThat(readResultMap, hasEntry(\"accountType\", \"premium\"));\n  }\n\n  @Test\n  public void testJsonMerge() {\n    String key = \"jsonKey\";\n\n    JSONObject initialUser = new JSONObject();\n    initialUser.put(\"name\", \"John Doe\");\n    initialUser.put(\"age\", 30);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, initialUser));\n\n    JSONObject mergeUser = new JSONObject();\n    mergeUser.put(\"occupation\", \"Software Developer\");\n    mergeUser.put(\"age\", 31); // Assuming we're updating the age as well\n\n    String mergeRoot = exec(commandObjects.jsonMerge(key, Path2.ROOT_PATH, mergeUser));\n    assertThat(mergeRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(getRoot, notNullValue());\n\n    JSONObject expectedUser = new JSONObject();\n    expectedUser.put(\"name\", \"John Doe\");\n    expectedUser.put(\"age\", 31);\n    expectedUser.put(\"occupation\", \"Software Developer\");\n\n    JSONArray expected = new JSONArray().put(expectedUser);\n    assertThat(getRoot, jsonEquals(expected));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonMergeOldPath() {\n    String key = \"jsonKey\";\n\n    Map<String, Object> initialUser = new HashMap<>();\n    initialUser.put(\"name\", \"Jane Doe\");\n\n    exec(commandObjects.jsonSet(key, Path.ROOT_PATH, initialUser));\n\n    Map<String, Object> mergeUser = new HashMap<>();\n    mergeUser.put(\"occupation\", \"Data Scientist\");\n    mergeUser.put(\"name\", \"Jane Smith\"); // update the name as well\n\n    String mergeRoot = exec(commandObjects.jsonMerge(key, Path.ROOT_PATH, mergeUser));\n    assertThat(mergeRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key, Path.ROOT_PATH));\n    assertThat(getRoot, instanceOf(Map.class));\n\n    @SuppressWarnings(\"unchecked\")\n    Map<String, Object> resultMap = (Map<String, Object>) getRoot;\n    assertThat(resultMap, hasEntry(\"name\", \"Jane Smith\"));\n    assertThat(resultMap, hasEntry(\"occupation\", \"Data Scientist\"));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonGenericObject() {\n    String key = \"user:1000\";\n\n    Person person = new Person();\n    person.setName(\"John Doe\");\n    person.setAge(30);\n\n    String setRoot = exec(commandObjects.jsonSet(key, Path.ROOT_PATH, person));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getRoot = exec(commandObjects.jsonGet(key));\n    assertThat(getRoot, instanceOf(Map.class));\n\n    @SuppressWarnings(\"unchecked\")\n    Map<String, Object> resultMap = (Map<String, Object>) getRoot;\n    assertThat(resultMap, hasEntry(\"name\", \"John Doe\"));\n    assertThat(resultMap, hasEntry(\"age\", 30.0));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonGetWithClass() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"user:2000\";\n\n    String jsonObject = \"{\\\"name\\\":\\\"Jane Doe\\\",\\\"age\\\":25}\";\n\n    exec(commandObjects.jsonSetWithPlainString(key, Path.ROOT_PATH, jsonObject));\n\n    Person getRoot = exec(commandObjects.jsonGet(key, Person.class));\n\n    assertThat(getRoot.getName(), equalTo(\"Jane Doe\"));\n    assertThat(getRoot.getAge(), equalTo(25));\n  }\n\n  @Test\n  public void testJsonMGet() {\n    String keyBob = \"user:bob\";\n    String keyCharlie = \"user:charlie\";\n\n    JSONObject bob = new JSONObject();\n    bob.put(\"name\", \"Bob\");\n    bob.put(\"age\", 30);\n\n    JSONObject charlie = new JSONObject();\n    charlie.put(\"name\", \"Charlie\");\n    charlie.put(\"age\", 25);\n\n    String setBobRoot = exec(commandObjects.jsonSet(keyBob, Path2.ROOT_PATH, bob));\n    assertThat(setBobRoot, equalTo(\"OK\"));\n\n    String setCharlieRoot = exec(commandObjects.jsonSet(keyCharlie, Path2.ROOT_PATH, charlie));\n    assertThat(setCharlieRoot, equalTo(\"OK\"));\n\n    List<JSONArray> getNames = exec(commandObjects.jsonMGet(Path2.of(\"name\"), keyBob, keyCharlie));\n    assertThat(getNames, contains(\n        jsonEquals(new JSONArray().put(\"Bob\")),\n        jsonEquals(new JSONArray().put(\"Charlie\"))\n    ));\n\n    List<JSONArray> getRoots = exec(commandObjects.jsonMGet(Path2.ROOT_PATH, keyBob, keyCharlie));\n    assertThat(getRoots, contains(\n        jsonEquals(new JSONArray().put(bob)),\n        jsonEquals(new JSONArray().put(charlie))\n    ));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonMGetOldPath() {\n    String keyBob = \"user:bob\";\n    String keyCharlie = \"user:charlie\";\n\n    JSONObject bob = new JSONObject();\n    bob.put(\"name\", \"Bob\");\n    bob.put(\"age\", 30);\n\n    JSONObject charlie = new JSONObject();\n    charlie.put(\"name\", \"Charlie\");\n    charlie.put(\"age\", 25);\n\n    String setBobRoot = exec(commandObjects.jsonSet(keyBob, Path2.ROOT_PATH, bob));\n    assertThat(setBobRoot, equalTo(\"OK\"));\n\n    String setCharlieRoot = exec(commandObjects.jsonSet(keyCharlie, Path2.ROOT_PATH, charlie));\n    assertThat(setCharlieRoot, equalTo(\"OK\"));\n\n    List<String> getNamesTyped = exec(commandObjects.jsonMGet(Path.of(\"name\"), String.class, keyBob, keyCharlie));\n    assertThat(getNamesTyped, contains(\"Bob\", \"Charlie\"));\n\n    List<Person> getPersonsTyped = exec(commandObjects.jsonMGet(Path.ROOT_PATH, Person.class, keyBob, keyCharlie));\n    assertThat(getPersonsTyped, contains(\n        new Person(\"Bob\", 30),\n        new Person(\"Charlie\", 25)\n    ));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonGetAsPlainString() {\n    String key = \"user:3000\";\n\n    Person person = new Person(\"John Smith\", 30);\n\n    exec(commandObjects.jsonSet(key, Path.ROOT_PATH, person));\n\n    String getName = exec(commandObjects.jsonGetAsPlainString(key, Path.of(\".name\")));\n    assertThat(getName, equalTo(\"\\\"John Smith\\\"\"));\n\n    String getRoot = exec(commandObjects.jsonGetAsPlainString(key, Path.ROOT_PATH));\n    assertThat(getRoot, jsonEquals(person));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonGetWithPathAndClass() {\n    String key = \"user:4000\";\n\n    String jsonObject = \"{\\\"person\\\":{\\\"name\\\":\\\"Alice Johnson\\\",\\\"age\\\":28}}\";\n\n    String setRoot = exec(commandObjects.jsonSetWithPlainString(key, Path.ROOT_PATH, jsonObject));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Person getPerson = exec(commandObjects.jsonGet(key, Person.class, Path.of(\".person\")));\n    assertThat(getPerson.getName(), equalTo(\"Alice Johnson\"));\n    assertThat(getPerson.getAge(), equalTo(28));\n  }\n\n  @Test\n  public void testJsonDel() {\n    String key = \"user:11000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long del = exec(commandObjects.jsonDel(key));\n    assertThat(del, equalTo(1L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(postCheck, nullValue());\n  }\n\n  @Test\n  public void testJsonDelPath() {\n    String key = \"user:11000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long delAge = exec(commandObjects.jsonDel(key, Path2.of(\".age\")));\n    assertThat(delAge, equalTo(1L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"name\", \"Gina\");\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonDelOldPath() {\n    String key = \"user:11000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long delAge = exec(commandObjects.jsonDel(key, Path.of(\".age\")));\n    assertThat(delAge, equalTo(1L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"name\", \"Gina\");\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonClear() {\n    String key = \"user:11000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long clear = exec(commandObjects.jsonClear(key));\n    assertThat(clear, equalTo(1L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray().put(new JSONObject());\n    assertThat(postCheck, jsonEquals(expected));\n  }\n\n  @Test\n  public void testJsonClearPath() {\n    String key = \"user:11000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n    person.put(\"occupations\", new JSONArray().put(\"Data Scientist\").put(\"Developer\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long clearOccupations = exec(commandObjects.jsonClear(key, Path2.of(\".occupations\")));\n    assertThat(clearOccupations, equalTo(1L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"name\", \"Gina\");\n    expected.put(\"age\", 29);\n    expected.put(\"occupations\", new JSONArray());\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonClearOldPath() {\n    String key = \"user:11000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n    person.put(\"occupations\", new JSONArray().put(\"Data Scientist\").put(\"Developer\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long clearOccupations = exec(commandObjects.jsonClear(key, Path.of(\".occupations\")));\n    assertThat(clearOccupations, equalTo(1L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"name\", \"Gina\");\n    expected.put(\"age\", 29);\n    expected.put(\"occupations\", new JSONArray());\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonToggle() {\n    String key = \"user:13000\";\n\n    JSONObject item = new JSONObject();\n    item.put(\"active\", true);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, item));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(item)));\n\n    List<Boolean> toggle = exec(commandObjects.jsonToggle(key, Path2.of(\".active\")));\n    assertThat(toggle, contains(false));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"active\", false);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonType() {\n    String key = \"jsonKey\";\n\n    JSONObject item = new JSONObject();\n    item.put(\"active\", true);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, item));\n\n    List<Class<?>> type = exec(commandObjects.jsonType(key, Path2.of(\".active\")));\n    assertThat(type, contains(boolean.class));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonTypeOldPath() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"jsonKey\";\n\n    JSONObject item = new JSONObject();\n    item.put(\"active\", true);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, item));\n\n    Class<?> type = exec(commandObjects.jsonType(key, Path.of(\".active\")));\n    assertThat(type, equalTo(boolean.class));\n  }\n\n  @Test\n  public void testJsonStrAppend() {\n    String key = \"user:1000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    List<Long> strAppend = exec(commandObjects.jsonStrAppend(key, Path2.of(\".name\"), \" Smith\"));\n    assertThat(strAppend, contains(10L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"name\", \"Gina Smith\");\n    expected.put(\"age\", 29);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonStrAppendOldPath() {\n    String key = \"user:1000\";\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"Gina\");\n    person.put(\"age\", 29);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, person));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(person)));\n\n    Long strAppend = exec(commandObjects.jsonStrAppend(key, Path.of(\".name\"), \" Smith\"));\n    assertThat(strAppend, equalTo(10L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"name\", \"Gina Smith\");\n    expected.put(\"age\", 29);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonStrAppendRootPath() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"user:1000\";\n\n    String setRoot = exec(commandObjects.jsonSetWithPlainString(key, Path.ROOT_PATH, \"\\\"John\\\"\"));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Object getBefore = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(getBefore, jsonEquals(new JSONArray().put(\"John\")));\n\n    Long strAppend = exec(commandObjects.jsonStrAppend(key, \" Doe\"));\n    assertThat(strAppend, equalTo(8L));\n\n    Object getAfter = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(getAfter, jsonEquals(new JSONArray().put(\"John Doe\")));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonStrLenRootPath() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"user:1001\";\n\n    String setRoot = exec(commandObjects.jsonSetWithPlainString(key, Path.ROOT_PATH, \"\\\"Hello World\\\"\"));\n    assertThat(setRoot, equalTo(\"OK\"));\n\n    Long strLen = exec(commandObjects.jsonStrLen(key));\n    assertThat(strLen, equalTo(11L)); // \"Hello World\" length\n  }\n\n  @Test\n  public void testJsonStrLen() {\n    String key = \"user:1002\";\n\n    JSONObject item = new JSONObject();\n    item.put(\"message\", \"Hello, Redis!\");\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, item));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    List<Long> strLenResponse = exec(commandObjects.jsonStrLen(key, Path2.of(\".message\")));\n    assertThat(strLenResponse, contains(13L)); // \"Hello, Redis!\" length\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonStrLenOldPath() {\n    String key = \"user:1003\";\n\n    JSONObject item = new JSONObject();\n    item.put(\"message\", \"Hello, Redis!\");\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, item));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    Long strLenResponse = exec(commandObjects.jsonStrLen(key, Path.of(\".message\")));\n    assertThat(strLenResponse, equalTo(13L)); // \"Hello, Redis!\" length\n  }\n\n  @Test\n  public void testJsonNumIncrBy() {\n    String key = \"user:12000\";\n\n    JSONObject item = new JSONObject();\n    item.put(\"balance\", 100);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, item));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(item)));\n\n    Object numIncrBy = exec(commandObjects.jsonNumIncrBy(key, Path2.of(\"$.balance\"), 50.0));\n    assertThat(numIncrBy, jsonEquals(new JSONArray().put(150.0)));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject();\n    expected.put(\"balance\", 150.0);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrAppendWithEscape() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"Elixir\")\n        .put(\"Swift\");\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    List<Long> arrAppend = exec(commandObjects.jsonArrAppendWithEscape(\n        key, Path2.ROOT_PATH, \"Kotlin\", \"TypeScript\"));\n    assertThat(arrAppend, contains(4L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(\"Elixir\")\n        .put(\"Swift\")\n        .put(\"Kotlin\")\n        .put(\"TypeScript\");\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrAppend() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"Java\")\n        .put(\"Python\");\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    JSONObject person = new JSONObject();\n    person.put(\"name\", \"John\");\n\n    List<Long> arrAppend = exec(commandObjects.jsonArrAppend(key, Path2.ROOT_PATH,\n        \"\\\"C++\\\"\", \"\\\"JavaScript\\\"\", person));\n    assertThat(arrAppend, contains(5L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(\"Java\")\n        .put(\"Python\")\n        .put(\"C++\")\n        .put(\"JavaScript\")\n        .put(person);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrAppendOldPath() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(new JSONArray()\n            .put(\"Java\")\n            .put(\"Python\"))\n        .put(1);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Person person = new Person(\"John\", 45);\n\n    Long arrAppend = exec(\n        commandObjects.jsonArrAppend(key, Path.of(\".[0]\"), \"Swift\", \"Go\", person));\n    assertThat(arrAppend, equalTo(5L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(new JSONArray()\n            .put(\"Java\")\n            .put(\"Python\")\n            .put(\"Swift\")\n            .put(\"Go\")\n            .put(new JSONObject()\n                .put(\"name\", \"John\")\n                .put(\"age\", 45)))\n        .put(1);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrIndex() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"Java\")\n        .put(\"Python\")\n        .put(\"Java\"); // duplicate\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    List<Long> arrIndex = exec(commandObjects.jsonArrIndex(key, Path2.ROOT_PATH, \"\\\"Java\\\"\"));\n    assertThat(arrIndex, contains(0L));\n\n    List<Long> arrIndexNotFound = exec(commandObjects.jsonArrIndex(key, Path2.ROOT_PATH, \"\\\"C++\\\"\"));\n    assertThat(arrIndexNotFound, contains(-1L));\n  }\n\n  @Test\n  public void testJsonArrIndexWithEscape() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"Java\")\n        .put(\"Python\")\n        .put(\"Java\"); // duplicate\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    List<Long> arrIndex = exec(commandObjects.jsonArrIndexWithEscape(key, Path2.ROOT_PATH, \"Java\"));\n    assertThat(arrIndex, contains(0L));\n\n    List<Long> arrIndexNotFound = exec(commandObjects.jsonArrIndexWithEscape(key, Path2.ROOT_PATH, \"Go\"));\n    assertThat(arrIndexNotFound, contains(-1L));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrIndexDeprecated() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(new JSONArray()\n            .put(\"Java\")\n            .put(\"Python\")\n            .put(\"Java\")); // duplicate\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Long arrIndex = exec(commandObjects.jsonArrIndex(key, Path.of(\".[0]\"), \"Java\"));\n    assertThat(arrIndex, equalTo(0L));\n\n    Long arrIndexNotFound = exec(commandObjects.jsonArrIndex(key, Path.of(\".[0]\"), \"Swift\"));\n    assertThat(arrIndexNotFound, equalTo(-1L));\n  }\n\n  @Test\n  public void testJsonArrInsert() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"Java\")\n        .put(\"Python\");\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    List<Long> arrInsert = exec(\n        commandObjects.jsonArrInsert(key, Path2.ROOT_PATH, 1, \"\\\"C++\\\"\"));\n    assertThat(arrInsert, contains(3L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(\"Java\")\n        .put(\"C++\")\n        .put(\"Python\");\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrInsertWithEscape() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"Java\")\n        .put(\"Python\");\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    List<Long> arrInsert = exec(commandObjects.jsonArrInsertWithEscape(key, Path2.ROOT_PATH, 1, \"Go\"));\n    assertThat(arrInsert, contains(3L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(\"Java\")\n        .put(\"Go\")\n        .put(\"Python\");\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrInsertOldPath() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(1)\n        .put(new JSONArray()\n            .put(\"Scala\")\n            .put(\"Kotlin\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Long arrInsert = exec(commandObjects.jsonArrInsert(key, Path.of(\".[1]\"), 1, \"Swift\"));\n    assertThat(arrInsert, equalTo(3L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(1)\n        .put(new JSONArray()\n            .put(\"Scala\")\n            .put(\"Swift\")\n            .put(\"Kotlin\"));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrPopRoot() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"apple\")\n        .put(\"banana\")\n        .put(\"cherry\");\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Object arrPop = exec(commandObjects.jsonArrPop(key));\n    assertThat(arrPop, equalTo(\"cherry\"));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(\"apple\")\n        .put(\"banana\");\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrPopWithPath2() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\")\n            .put(\"cherry\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    List<Object> arrPop = exec(commandObjects.jsonArrPop(key, Path2.of(\".fruits\")));\n    assertThat(arrPop, contains(\"cherry\"));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\"));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrPopOldPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\")\n            .put(\"cherry\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Object arrPop = exec(commandObjects.jsonArrPop(key, Path.of(\".fruits\")));\n    assertThat(arrPop, equalTo(\"cherry\"));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\"));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrPopRootWithType() {\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(1)\n        .put(2)\n        .put(3);\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Integer arrPop = exec(commandObjects.jsonArrPop(key, Integer.class));\n    assertThat(arrPop, equalTo(3));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONArray expected = new JSONArray()\n        .put(1)\n        .put(2);\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrPopWithOldPathAndType() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(20)\n            .put(30));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Integer arrPop = exec(commandObjects.jsonArrPop(key, Integer.class, Path.of(\".numbers\")));\n    assertThat(arrPop, equalTo(30));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(20));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrPopWithOldPathTypeAndIndex() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(20)\n            .put(30));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Integer arrPop = exec(commandObjects.jsonArrPop(key, Integer.class, Path.of(\".numbers\"), 1));\n    assertThat(arrPop, equalTo(20));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(30));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrPopWithPathAndIndex() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(20)\n            .put(30));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    List<Object> arrPop = exec(commandObjects.jsonArrPop(key, Path2.of(\".numbers\"), 1));\n    assertThat(arrPop, contains(20.0));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(30));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrPopOldPathAndIndex() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(20)\n            .put(30));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Object arrPop = exec(commandObjects.jsonArrPop(key, Path.of(\".numbers\"), 1));\n    assertThat(arrPop, equalTo(20.0));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"numbers\", new JSONArray()\n            .put(10)\n            .put(30));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  public void testJsonArrTrimWithPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\")\n            .put(\"cherry\")\n            .put(\"date\")\n            .put(\"fig\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    List<Long> arrTrim = exec(commandObjects.jsonArrTrim(key, Path2.of(\".fruits\"), 1, 3));\n    assertThat(arrTrim, contains(3L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"banana\")\n            .put(\"cherry\")\n            .put(\"date\"));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrTrimOldPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\")\n            .put(\"cherry\")\n            .put(\"date\")\n            .put(\"fig\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Object preCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n    assertThat(preCheck, jsonEquals(new JSONArray().put(data)));\n\n    Long arrTrim = exec(commandObjects.jsonArrTrim(key, Path.of(\".fruits\"), 1, 3));\n    assertThat(arrTrim, equalTo(3L));\n\n    Object postCheck = exec(commandObjects.jsonGet(key, Path2.ROOT_PATH));\n\n    JSONObject expected = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"banana\")\n            .put(\"cherry\")\n            .put(\"date\"));\n    assertThat(postCheck, jsonEquals(new JSONArray().put(expected)));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrLenRoot() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"json\";\n\n    JSONArray data = new JSONArray()\n        .put(\"apple\")\n        .put(\"banana\")\n        .put(\"cherry\")\n        .put(\"date\")\n        .put(\"fig\");\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Long arrLen = exec(commandObjects.jsonArrLen(key));\n    assertThat(arrLen, equalTo(5L));\n  }\n\n  @Test\n  public void testJsonArrLenWithPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\")\n            .put(\"cherry\")\n            .put(\"date\")\n            .put(\"fig\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    List<Long> arrLen = exec(commandObjects.jsonArrLen(key, Path2.of(\".fruits\")));\n    assertThat(arrLen, contains(5L));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonArrLenOldPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"fruits\", new JSONArray()\n            .put(\"apple\")\n            .put(\"banana\")\n            .put(\"cherry\")\n            .put(\"date\")\n            .put(\"fig\"));\n\n    exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n\n    Long arrLen = exec(commandObjects.jsonArrLen(key, Path.of(\".fruits\")));\n    assertThat(arrLen, equalTo(5L));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonObjLenRoot() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"json\";\n\n    JSONObject data = new JSONObject();\n    data.put(\"name\", \"John\");\n    data.put(\"age\", 30);\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    Long objLen = exec(commandObjects.jsonObjLen(key));\n    assertThat(objLen, equalTo(2L)); // 2 keys: \"name\" and \"age\"\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonObjLenOldPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject().put(\"user\",\n        new JSONObject()\n            .put(\"name\", \"John\")\n            .put(\"age\", 30));\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    Long objLen = exec(commandObjects.jsonObjLen(key, Path.of(\".user\")));\n    assertThat(objLen, equalTo(2L));\n  }\n\n  @Test\n  public void testJsonObjLenWithPath2() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject().put(\"user\",\n        new JSONObject()\n            .put(\"name\", \"John\")\n            .put(\"age\", 30));\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    List<Long> objLen = exec(commandObjects.jsonObjLen(key, Path2.of(\".user\")));\n    assertThat(objLen, contains(2L));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonObjKeysRoot() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    String key = \"json\";\n\n    JSONObject data = new JSONObject();\n    data.put(\"name\", \"John\");\n    data.put(\"age\", 30);\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    List<String> keys = exec(commandObjects.jsonObjKeys(key));\n    assertThat(keys, containsInAnyOrder(\"name\", \"age\"));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonObjKeysOldPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject().put(\"user\",\n        new JSONObject()\n            .put(\"name\", \"John\")\n            .put(\"age\", 30));\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    List<String> keys = exec(commandObjects.jsonObjKeys(key, Path.of(\".user\")));\n    assertThat(keys, containsInAnyOrder(\"name\", \"age\"));\n  }\n\n  @Test\n  public void testJsonObjKeysWithPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject().put(\"user\",\n        new JSONObject()\n            .put(\"name\", \"John\")\n            .put(\"age\", 30));\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    List<List<String>> keys = exec(commandObjects.jsonObjKeys(key, Path2.of(\".user\")));\n    assertThat(keys, contains(containsInAnyOrder(\"name\", \"age\")));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonDebugMemoryRoot() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n    String key = \"json\";\n\n    JSONObject data = new JSONObject()\n        .put(\"name\", \"John\")\n        .put(\"age\", 30);\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    Long memoryUsage = exec(commandObjects.jsonDebugMemory(key));\n    assertThat(memoryUsage, notNullValue());\n    assertThat(memoryUsage, greaterThan(0L));\n  }\n\n  @Test\n  @Deprecated\n  public void testJsonDebugMemoryOldPath() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject().put(\"user\",\n        new JSONObject()\n            .put(\"name\", \"John\")\n            .put(\"age\", 30));\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    Long memoryUsage = exec(commandObjects.jsonDebugMemory(key, Path.of(\".user\")));\n    assertThat(memoryUsage, notNullValue());\n    assertThat(memoryUsage, greaterThan(0L));\n  }\n\n  @Test\n  public void testJsonDebugMemoryWithPath2() {\n    String key = \"json\";\n\n    JSONObject data = new JSONObject().put(\"user\",\n        new JSONObject()\n            .put(\"name\", \"John\")\n            .put(\"age\", 30));\n\n    String setResponse = exec(commandObjects.jsonSet(key, Path2.ROOT_PATH, data));\n    assertThat(setResponse, equalTo(\"OK\"));\n\n    List<Long> memoryUsages = exec(commandObjects.jsonDebugMemory(key, Path2.of(\".user\")));\n    assertThat(memoryUsages, contains(greaterThan(0L)));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsListCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=list\">List</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsListCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsListCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testPushCommands() {\n    String key = \"list\";\n\n    Long rpush = exec(commandObjects.rpush(key, \"hello\", \"world\"));\n    assertThat(rpush, equalTo(2L));\n\n    Long lpush = exec(commandObjects.lpush(key, \"hello\", \"world\"));\n    assertThat(lpush, equalTo(4L));\n\n    List<String> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(\"world\", \"hello\", \"hello\", \"world\"));\n  }\n\n  @Test\n  public void testPushCommandsBinary() {\n    String keyStr = \"list\";\n    byte[] key = keyStr.getBytes();\n\n    Long rpush = exec(commandObjects.rpush(key, \"hello\".getBytes(), \"world\".getBytes()));\n    assertThat(rpush, equalTo(2L));\n\n    Long lpush = exec(commandObjects.lpush(key, \"hello\".getBytes(), \"world\".getBytes()));\n    assertThat(lpush, equalTo(4L));\n\n    List<String> lrange = exec(commandObjects.lrange(keyStr, 0, -1));\n    assertThat(lrange, contains(\"world\", \"hello\", \"hello\", \"world\"));\n  }\n\n  @Test\n  public void testLlen() {\n    String key = \"list\";\n\n    Long initialLength = exec(commandObjects.llen(key));\n    assertThat(initialLength, equalTo(0L));\n\n    exec(commandObjects.rpush(key, \"value\", \"value\"));\n\n    Long llen = exec(commandObjects.llen(key));\n    assertThat(llen, equalTo(2L));\n\n    Long llenBinary = exec(commandObjects.llen(key.getBytes()));\n    assertThat(llenBinary, equalTo(2L));\n  }\n\n  @Test\n  public void testLrange() {\n    String key = \"list\";\n    String value1 = \"first\";\n    String value2 = \"second\";\n    String value3 = \"third\";\n\n    exec(commandObjects.rpush(key, value1, value2, value3));\n\n    List<String> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(value1, value2, value3));\n\n    List<byte[]> lrangeBinary = exec(commandObjects.lrange(key.getBytes(), 0, -1));\n    assertThat(lrangeBinary, contains(value1.getBytes(), value2.getBytes(), value3.getBytes()));\n\n    List<String> partialRange = exec(commandObjects.lrange(key, 1, 2));\n    assertThat(partialRange, contains(value2, value3));\n\n    List<String> emptyRange = exec(commandObjects.lrange(key, 4, 5));\n    assertThat(emptyRange, empty());\n  }\n\n  @Test\n  public void testLtrim() {\n    String key = \"list\";\n\n    exec(commandObjects.rpush(key, \"one\", \"two\", \"three\", \"four\"));\n\n    String trim = exec(commandObjects.ltrim(key, 1, 2));\n    assertThat(trim, equalTo(\"OK\"));\n\n    List<String> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(\"two\", \"three\"));\n  }\n\n  @Test\n  public void testLtrimBinary() {\n    byte[] key = \"list\".getBytes();\n\n    exec(commandObjects.rpush(key, \"one\".getBytes(), \"two\".getBytes(), \"three\".getBytes(), \"four\".getBytes()));\n\n    String trim = exec(commandObjects.ltrim(key, 1, 2));\n    assertThat(trim, equalTo(\"OK\"));\n\n    List<byte[]> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(\"two\".getBytes(), \"three\".getBytes()));\n  }\n\n  @Test\n  public void testLindexCommands() {\n    String key = \"list\";\n\n    exec(commandObjects.rpush(key, \"alpha\", \"beta\", \"gamma\"));\n\n    String lindex = exec(commandObjects.lindex(key, 1));\n    assertThat(lindex, equalTo(\"beta\"));\n\n    byte[] lindexBinary = exec(commandObjects.lindex(key.getBytes(), 2));\n    assertThat(lindexBinary, equalTo(\"gamma\".getBytes()));\n\n    String lindexOufOfRange = exec(commandObjects.lindex(key, 5));\n    assertThat(lindexOufOfRange, nullValue());\n\n    byte[] lindexLastPositionBinary = exec(commandObjects.lindex(key.getBytes(), -1));\n    assertThat(lindexLastPositionBinary, equalTo(\"gamma\".getBytes()));\n  }\n\n  @Test\n  public void testLset() {\n    String key = \"list\";\n    String initialValue = \"initial\";\n    String updatedValue = \"updated\";\n\n    exec(commandObjects.rpush(key, initialValue));\n\n    String lindexBefore = exec(commandObjects.lindex(key, 0));\n    assertThat(lindexBefore, equalTo(initialValue));\n\n    String lset = exec(commandObjects.lset(key, 0, updatedValue));\n    assertThat(lset, equalTo(\"OK\"));\n\n    String lindexAfter = exec(commandObjects.lindex(key, 0));\n    assertThat(lindexAfter, equalTo(updatedValue));\n  }\n\n  @Test\n  public void testLsetBinary() {\n    byte[] keyBytes = \"list\".getBytes();\n    String initialValue = \"initial\";\n    String updatedValue = \"updated\";\n\n    exec(commandObjects.rpush(keyBytes, initialValue.getBytes()));\n\n    byte[] lindexBefore = exec(commandObjects.lindex(keyBytes, 0));\n    assertThat(lindexBefore, equalTo(initialValue.getBytes()));\n\n    String lset = exec(commandObjects.lset(keyBytes, 0, updatedValue.getBytes()));\n    assertThat(lset, equalTo(\"OK\"));\n\n    byte[] lindexAfter = exec(commandObjects.lindex(keyBytes, 0));\n    assertThat(lindexAfter, equalTo(updatedValue.getBytes()));\n  }\n\n  @Test\n  public void testLrem() {\n    String key = \"remList\";\n\n    exec(commandObjects.rpush(key, \"duplicate\", \"duplicate\", \"unique\"));\n\n    List<String> lrangeInitial = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrangeInitial, contains(\"duplicate\", \"duplicate\", \"unique\"));\n\n    Long lrem = exec(commandObjects.lrem(key, 1, \"duplicate\"));\n    assertThat(lrem, equalTo(1L));\n\n    List<String> lrangeAfterLremSingle = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrangeAfterLremSingle, contains(\"duplicate\", \"unique\"));\n\n    Long lremNonExistent = exec(commandObjects.lrem(key, 0, \"nonexistent\"));\n    assertThat(lremNonExistent, equalTo(0L));\n\n    List<String> lrangeAfterLremNonExistent = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrangeAfterLremNonExistent, contains(\"duplicate\", \"unique\"));\n  }\n\n  @Test\n  public void testLremBinary() {\n    byte[] keyBytes = \"remList\".getBytes();\n\n    exec(commandObjects.rpush(keyBytes, \"duplicate\".getBytes(), \"duplicate\".getBytes(), \"unique\".getBytes()));\n\n    List<byte[]> lrangeBefore = exec(commandObjects.lrange(keyBytes, 0, -1));\n    assertThat(lrangeBefore, contains(\"duplicate\".getBytes(), \"duplicate\".getBytes(), \"unique\".getBytes()));\n\n    Long lremMultiple = exec(commandObjects.lrem(keyBytes, 0, \"duplicate\".getBytes()));\n    assertThat(lremMultiple, equalTo(2L));\n\n    List<byte[]> lrangeAfter = exec(commandObjects.lrange(keyBytes, 0, -1));\n    assertThat(lrangeAfter, contains(\"unique\".getBytes()));\n  }\n\n  @Test\n  public void testPopCommands() {\n    String key = \"popList\";\n\n    exec(commandObjects.rpush(key,\n        \"first\", \"second\", \"third\", \"first\", \"second\", \"third\"));\n\n    String lpop = exec(commandObjects.lpop(key));\n    assertThat(lpop, equalTo(\"first\"));\n\n    String rpop = exec(commandObjects.rpop(key));\n    assertThat(rpop, equalTo(\"third\"));\n\n    List<String> lpopMultiple = exec(commandObjects.lpop(key, 2));\n    assertThat(lpopMultiple, contains(\"second\", \"third\"));\n\n    List<String> rpopMultiple = exec(commandObjects.rpop(key, 2));\n    assertThat(rpopMultiple, contains(\"second\", \"first\"));\n  }\n\n  @Test\n  public void testPopCommandsBinary() {\n    byte[] key = \"popList\".getBytes();\n\n    exec(commandObjects.rpush(key,\n        \"first\".getBytes(), \"second\".getBytes(), \"third\".getBytes(),\n        \"first\".getBytes(), \"second\".getBytes(), \"third\".getBytes()));\n\n    byte[] lpop = exec(commandObjects.lpop(key));\n    assertThat(lpop, equalTo(\"first\".getBytes()));\n\n    byte[] rpop = exec(commandObjects.rpop(key));\n    assertThat(rpop, equalTo(\"third\".getBytes()));\n\n    List<byte[]> lpopMultiple = exec(commandObjects.lpop(key, 2));\n    assertThat(lpopMultiple, contains(\"second\".getBytes(), \"third\".getBytes()));\n\n    List<byte[]> rpopMultiple = exec(commandObjects.rpop(key, 2));\n    assertThat(rpopMultiple, contains(\"second\".getBytes(), \"first\".getBytes()));\n  }\n\n  @Test\n  public void testLpos() {\n    String key = \"list\";\n    String value = \"target\";\n    String nonExistentValue = \"ghost\";\n\n    exec(commandObjects.rpush(key, \"start\", value, \"middle\", value, \"end\"));\n\n    Long lposFirst = exec(commandObjects.lpos(key, value));\n    assertThat(lposFirst, equalTo(1L));\n\n    Long lposFirstBinary = exec(commandObjects.lpos(key.getBytes(), value.getBytes()));\n    assertThat(lposFirstBinary, equalTo(1L));\n\n    LPosParams params = LPosParams.lPosParams().rank(-1);\n    Long lposLast = exec(commandObjects.lpos(key, value, params));\n    assertThat(lposLast, equalTo(3L));\n\n    Long lposLastBinary = exec(commandObjects.lpos(key.getBytes(), value.getBytes(), params));\n    assertThat(lposLastBinary, equalTo(3L));\n\n    List<Long> lposMultiple = exec(commandObjects.lpos(key, value, params, 2));\n    assertThat(lposMultiple, contains(3L, 1L));\n\n    List<Long> lposMultipleBinary = exec(commandObjects.lpos(key.getBytes(), value.getBytes(), params, 2));\n    assertThat(lposMultipleBinary, contains(3L, 1L));\n\n    Long lposNonExistent = exec(commandObjects.lpos(key, nonExistentValue));\n    assertThat(lposNonExistent, nullValue());\n\n    Long lposNonExistentBinary = exec(commandObjects.lpos(key.getBytes(), nonExistentValue.getBytes()));\n    assertThat(lposNonExistentBinary, nullValue());\n  }\n\n  @Test\n  public void testLinsert() {\n    String key = \"insertList\";\n    String pivot = \"pivot\";\n    String valueBefore = \"beforePivot\";\n    String valueAfter = \"afterPivot\";\n\n    exec(commandObjects.rpush(key, pivot));\n\n    Long linsertBefore = exec(commandObjects.linsert(key, ListPosition.BEFORE, pivot, valueBefore));\n    assertThat(linsertBefore, equalTo(2L));\n\n    Long linsertAfter = exec(commandObjects.linsert(key, ListPosition.AFTER, pivot, valueAfter));\n    assertThat(linsertAfter, equalTo(3L));\n\n    List<String> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(valueBefore, pivot, valueAfter));\n  }\n\n  @Test\n  public void testLinsertBinary() {\n    byte[] key = \"insertList\".getBytes();\n    byte[] pivot = \"pivot\".getBytes();\n    byte[] valueBefore = \"valueBefore\".getBytes();\n    byte[] valueAfter = \"valueAfter\".getBytes();\n\n    exec(commandObjects.rpush(key, pivot));\n\n    Long linsertBefore = exec(commandObjects.linsert(key, ListPosition.BEFORE, pivot, valueBefore));\n    assertThat(linsertBefore, equalTo(2L));\n\n    Long linsertAfter = exec(commandObjects.linsert(key, ListPosition.AFTER, pivot, valueAfter));\n    assertThat(linsertAfter, equalTo(3L));\n\n    List<byte[]> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(valueBefore, pivot, valueAfter));\n  }\n\n  @Test\n  public void testPushxCommands() {\n    String key = \"pushxList\";\n    String value1 = \"first\";\n    String value2 = \"second\";\n\n    Long lpushxInitial = exec(commandObjects.lpushx(key, value1));\n    assertThat(lpushxInitial, equalTo(0L));\n\n    Long rpushxInitial = exec(commandObjects.rpushx(key, value1));\n    assertThat(rpushxInitial, equalTo(0L));\n\n    Boolean exists = exec(commandObjects.exists(key));\n    assertThat(exists, equalTo(false));\n\n    exec(commandObjects.lpush(key, \"init\"));\n\n    Long lpushx = exec(commandObjects.lpushx(key, value1, value2));\n    assertThat(lpushx, equalTo(3L)); // new size returned\n\n    Long rpushx = exec(commandObjects.rpushx(key, value1, value2));\n    assertThat(rpushx, equalTo(5L));\n\n    List<String> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(value2, value1, \"init\", value1, value2));\n  }\n\n  @Test\n  public void testPushxCommandsBinary() {\n    byte[] key = \"pushxList\".getBytes();\n    byte[] value1 = \"first\".getBytes();\n    byte[] value2 = \"second\".getBytes();\n\n    Long lpushxInitial = exec(commandObjects.lpushx(key, value1));\n    assertThat(lpushxInitial, equalTo(0L));\n\n    Long rpushxInitial = exec(commandObjects.rpushx(key, value1));\n    assertThat(rpushxInitial, equalTo(0L));\n\n    Boolean exists = exec(commandObjects.exists(key));\n    assertThat(exists, equalTo(false));\n\n    exec(commandObjects.lpush(key, \"init\".getBytes()));\n\n    Long lpushx = exec(commandObjects.lpushx(key, value1, value2));\n    assertThat(lpushx, equalTo(3L));\n\n    Long rpushx = exec(commandObjects.rpushx(key, value1, value2));\n    assertThat(rpushx, equalTo(5L));\n\n    List<byte[]> lrange = exec(commandObjects.lrange(key, 0, -1));\n    assertThat(lrange, contains(value2, value1, \"init\".getBytes(), value1, value2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBlpop() {\n    String key1 = \"list1\";\n    String key2 = \"list2\";\n    String value1 = \"value1\";\n    String value2 = \"value2\";\n\n    exec(commandObjects.lpush(key1, value1));\n\n    List<String> blpop = exec(commandObjects.blpop(1, key1));\n    assertThat(blpop, contains(key1, value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    List<String> blpopMultiple = exec(commandObjects.blpop(1, key1, key2));\n    assertThat(blpopMultiple, anyOf(contains(key1, value1), contains(key2, value2)));\n\n    exec(commandObjects.lpush(key1, value1));\n\n    KeyValue<String, String> blpopDoubleTimeout = exec(commandObjects.blpop(1.0, key1));\n    assertThat(blpopDoubleTimeout.getKey(), equalTo(key1));\n    assertThat(blpopDoubleTimeout.getValue(), equalTo(value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    KeyValue<String, String> blpopDoubleTimeoutMultiple = exec(commandObjects.blpop(1.0, key1, key2));\n    assertThat(blpopDoubleTimeoutMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(blpopDoubleTimeoutMultiple.getValue(), anyOf(equalTo(value1), equalTo(value2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBlpopBinary() {\n    byte[] key1 = \"list1\".getBytes();\n    byte[] key2 = \"list2\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    exec(commandObjects.lpush(key1, value1));\n\n    List<byte[]> blpop = exec(commandObjects.blpop(1, key1));\n    assertThat(blpop.get(0), equalTo(key1));\n    assertThat(blpop.get(1), equalTo(value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    List<byte[]> blpopMultiple = exec(commandObjects.blpop(1, key1, key2));\n    assertThat(blpopMultiple, anyOf(contains(key1, value1), contains(key2, value2)));\n\n    exec(commandObjects.lpush(key1, value1));\n\n    KeyValue<byte[], byte[]> blpopDoubleTimeout = exec(commandObjects.blpop(1.0, key1));\n    assertThat(blpopDoubleTimeout.getKey(), equalTo(key1));\n    assertThat(blpopDoubleTimeout.getValue(), equalTo(value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    KeyValue<byte[], byte[]> blpopDoubleTimeoutMultiple = exec(commandObjects.blpop(1.0, key1, key2));\n    assertThat(blpopDoubleTimeoutMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(blpopDoubleTimeoutMultiple.getValue(), anyOf(equalTo(value1), equalTo(value2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBrpop() {\n    String key1 = \"list1\";\n    String key2 = \"list2\";\n    String value1 = \"value1\";\n    String value2 = \"value2\";\n\n    exec(commandObjects.lpush(key1, value1));\n\n    List<String> brpop = exec(commandObjects.brpop(1, key1));\n    assertThat(brpop, contains(key1, value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    List<String> brpopMultiple = exec(commandObjects.brpop(1, key1, key2));\n    assertThat(brpopMultiple, anyOf(contains(key1, value1), contains(key2, value2)));\n\n    exec(commandObjects.lpush(key1, value1));\n\n    KeyValue<String, String> brpopDoubleTimeout = exec(commandObjects.brpop(1.0, key1));\n    assertThat(brpopDoubleTimeout.getKey(), equalTo(key1));\n    assertThat(brpopDoubleTimeout.getValue(), equalTo(value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    KeyValue<String, String> brpopDoubleTimeoutMultiple = exec(commandObjects.brpop(1.0, key1, key2));\n    assertThat(brpopDoubleTimeoutMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(brpopDoubleTimeoutMultiple.getValue(), anyOf(equalTo(value1), equalTo(value2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBrpopBinary() {\n    byte[] key1 = \"list1\".getBytes();\n    byte[] key2 = \"list2\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    exec(commandObjects.lpush(key1, value1));\n\n    List<byte[]> brpop = exec(commandObjects.brpop(1, key1));\n    assertThat(brpop.get(0), equalTo(key1));\n    assertThat(brpop.get(1), equalTo(value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    List<byte[]> brpopMultiple = exec(commandObjects.brpop(1, key1, key2));\n    assertThat(brpopMultiple, anyOf(contains(key1, value1), contains(key2, value2)));\n\n    exec(commandObjects.lpush(key1, value1));\n\n    KeyValue<byte[], byte[]> brpopDoubleTimeout = exec(commandObjects.brpop(1.0, key1));\n    assertThat(brpopDoubleTimeout.getKey(), equalTo(key1));\n    assertThat(brpopDoubleTimeout.getValue(), equalTo(value1));\n\n    exec(commandObjects.lpush(key1, value1));\n    exec(commandObjects.lpush(key2, value2));\n\n    KeyValue<byte[], byte[]> brpopDoubleTimeoutMultiple = exec(commandObjects.brpop(1.0, key1, key2));\n    assertThat(brpopDoubleTimeoutMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(brpopDoubleTimeoutMultiple.getValue(), anyOf(equalTo(value1), equalTo(value2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testRpoplpushAndBrpoplpush() {\n    String srcKey = \"sourceList\";\n    String dstKey = \"destinationList\";\n    String value1 = \"value1\";\n    String value2 = \"value2\";\n\n    String noResult = exec(commandObjects.rpoplpush(srcKey, dstKey));\n    assertThat(noResult, nullValue());\n\n    exec(commandObjects.lpush(srcKey, value1));\n\n    String result = exec(commandObjects.rpoplpush(srcKey, dstKey));\n    assertThat(result, equalTo(value1));\n\n    List<String> dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(value1));\n\n    exec(commandObjects.lpush(srcKey, value2));\n\n    String bResult = exec(commandObjects.brpoplpush(srcKey, dstKey, 1));\n    assertThat(bResult, equalTo(value2));\n\n    dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(value2, value1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testRpoplpushAndBrpoplpushBinary() {\n    byte[] srcKey = \"sourceList\".getBytes();\n    byte[] dstKey = \"destinationList\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    exec(commandObjects.lpush(srcKey, value1));\n\n    byte[] result = exec(commandObjects.rpoplpush(srcKey, dstKey));\n    assertThat(result, equalTo(value1));\n\n    List<byte[]> dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(equalTo(value1)));\n\n    exec(commandObjects.lpush(srcKey, value2));\n\n    byte[] bResult = exec(commandObjects.brpoplpush(srcKey, dstKey, 1));\n    assertThat(bResult, equalTo(value2));\n\n    dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(equalTo(value2), equalTo(value1)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testLmoveAndBlmove() {\n    String srcKey = \"sourceList\";\n    String dstKey = \"destinationList\";\n    String value1 = \"value1\";\n    String value2 = \"value2\";\n\n    exec(commandObjects.lpush(srcKey, value1));\n\n    String result = exec(commandObjects.lmove(srcKey, dstKey, ListDirection.LEFT, ListDirection.RIGHT));\n    assertThat(result, equalTo(value1));\n\n    List<String> dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(value1));\n\n    exec(commandObjects.lpush(srcKey, value2));\n\n    String bResult = exec(commandObjects.blmove(srcKey, dstKey, ListDirection.LEFT, ListDirection.LEFT, 1.0));\n    assertThat(bResult, equalTo(value2));\n\n    dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(value2, value1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testLmoveAndBlmoveBinary() {\n    byte[] srcKey = \"sourceList\".getBytes();\n    byte[] dstKey = \"destinationList\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    exec(commandObjects.lpush(srcKey, value1));\n\n    byte[] result = exec(commandObjects.lmove(srcKey, dstKey, ListDirection.LEFT, ListDirection.RIGHT));\n    assertThat(result, equalTo(value1));\n\n    List<byte[]> dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList.get(0), equalTo(value1));\n\n    exec(commandObjects.lpush(srcKey, value2));\n\n    byte[] bResult = exec(commandObjects.blmove(srcKey, dstKey, ListDirection.LEFT, ListDirection.LEFT, 1.0));\n    assertThat(bResult, equalTo(value2));\n\n    dstList = exec(commandObjects.lrange(dstKey, 0, -1));\n    assertThat(dstList, contains(equalTo(value2), equalTo(value1)));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testLmpopAndBlmpop() {\n    String key1 = \"list1\";\n    String key2 = \"list2\";\n    String value1 = \"value1\";\n    String value2 = \"value2\";\n\n    exec(commandObjects.lpush(key1, value1, value1, value1, value1, value1, value1));\n    exec(commandObjects.lpush(key2, value2, value2, value2, value2, value2, value2));\n\n    KeyValue<String, List<String>> lmpop = exec(commandObjects.lmpop(ListDirection.LEFT, key1, key2));\n    assertThat(lmpop.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(lmpop.getValue(), anyOf(contains(value1), contains(value2)));\n\n    KeyValue<String, List<String>> lmpopMultiple = exec(commandObjects.lmpop(ListDirection.LEFT, 2, key1, key2));\n    assertThat(lmpopMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(lmpopMultiple.getValue(), anyOf(contains(value1, value1), contains(value2, value2)));\n\n    KeyValue<String, List<String>> blmpop = exec(commandObjects.blmpop(1.0, ListDirection.LEFT, key1, key2));\n    assertThat(blmpop.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(blmpop.getValue(), anyOf(contains(value1), contains(value2)));\n\n    KeyValue<String, List<String>> blmpopMultiple = exec(commandObjects.blmpop(1.0, ListDirection.LEFT, 2, key1, key2));\n    assertThat(blmpopMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(blmpopMultiple.getValue(), anyOf(contains(value1, value1), contains(value2, value2)));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testLmpopAndBlmpopBinary() {\n    byte[] key1 = \"list1\".getBytes();\n    byte[] key2 = \"list2\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    exec(commandObjects.lpush(key1, value1, value1, value1, value1, value1, value1));\n    exec(commandObjects.lpush(key2, value2, value2, value2, value2, value2, value2));\n\n    KeyValue<byte[], List<byte[]>> lmpop = exec(commandObjects.lmpop(ListDirection.LEFT, key1, key2));\n    assertThat(lmpop.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(lmpop.getValue(), anyOf(contains(equalTo(value1)), contains(equalTo(value2))));\n\n    KeyValue<byte[], List<byte[]>> lmpopMultiple = exec(commandObjects.lmpop(ListDirection.LEFT, 2, key1, key2));\n    assertThat(lmpopMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(lmpopMultiple.getValue(), anyOf(contains(equalTo(value1), equalTo(value1)), contains(equalTo(value2), equalTo(value2))));\n\n    KeyValue<byte[], List<byte[]>> blmpop = exec(commandObjects.blmpop(1.0, ListDirection.LEFT, key1, key2));\n    assertThat(blmpop.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(blmpop.getValue(), anyOf(contains(equalTo(value1)), contains(equalTo(value2))));\n\n    KeyValue<byte[], List<byte[]>> blmpopMultiple = exec(commandObjects.blmpop(1.0, ListDirection.LEFT, 2, key1, key2));\n    assertThat(blmpopMultiple.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(blmpopMultiple.getValue(), anyOf(contains(equalTo(value1), equalTo(value1)), contains(equalTo(value2), equalTo(value2))));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsModulesTestBase.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.util.EnvCondition;\n\n/**\n * Base class for tests that need a Redis Stack server.\n */\npublic abstract class CommandObjectsModulesTestBase extends CommandObjectsTestBase {\n\n  @RegisterExtension\n  static EnvCondition envCondition = new EnvCondition();\n\n  public CommandObjectsModulesTestBase(RedisProtocol protocol) {\n    super(protocol, Endpoints.getRedisEndpoint(\"modules-docker\"));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsScriptingCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasKey;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.utils.RedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=scripting\">Scripting</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsScriptingCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsScriptingCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() {\n    super.setUp();\n    if (RedisVersionUtil.getRedisVersion(endpoint).isGreaterThanOrEqualTo(RedisVersion.V7_0_0)) {\n      assertThat(exec(commandObjects.functionFlush(FlushMode.SYNC)), equalTo(\"OK\"));\n    }\n  }\n\n  @Test\n  public void testEvalWithOnlyScript() {\n    String set = exec(commandObjects.set(\"foo\", \"bar\"));\n    assertThat(set, equalTo(\"OK\"));\n\n    String script = \"return redis.call('get', 'foo')\";\n\n    Object eval = exec(commandObjects.eval(script));\n    assertThat(eval, equalTo(\"bar\"));\n\n    Object evalBinary = exec(commandObjects.eval(script.getBytes()));\n    assertThat(evalBinary, equalTo(\"bar\".getBytes()));\n\n    // eval with incorrect script\n    assertThrows(JedisException.class,\n        () -> exec(commandObjects.eval(\"return x\")));\n  }\n\n  @Test\n  public void testEvalWithScriptAndSampleKey() {\n    String set = exec(commandObjects.set(\"foo\", \"bar\"));\n    assertThat(set, equalTo(\"OK\"));\n\n    String script = \"return redis.call('get', 'foo');\";\n\n    Object eval = exec(commandObjects.eval(script, \"sampleKey\"));\n    assertThat(eval, equalTo(\"bar\"));\n\n    Object evalBinary = exec(commandObjects.eval(script.getBytes(), \"sampleKey\".getBytes()));\n    assertThat(evalBinary, equalTo(\"bar\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvalWithScriptKeyCountAndParams() {\n    exec(commandObjects.set(\"key1\", \"value1\"));\n    exec(commandObjects.set(\"key2\", \"value2\"));\n\n    // Script to get values of two keys and compare them\n    String script = \"if redis.call('get', KEYS[1]) == ARGV[1] and redis.call('get', KEYS[2]) == ARGV[2] then return 'true' else return 'false' end\";\n\n    Object evalTrue = exec(commandObjects.eval(\n        script, 2, \"key1\", \"key2\", \"value1\", \"value2\"));\n\n    assertThat(evalTrue, equalTo(\"true\"));\n\n    Object evalTrueBinary = exec(commandObjects.eval(\n        script.getBytes(), 2, \"key1\".getBytes(), \"key2\".getBytes(), \"value1\".getBytes(), \"value2\".getBytes()));\n\n    assertThat(evalTrueBinary, equalTo(\"true\".getBytes()));\n\n    Object evalFalse = exec(commandObjects.eval(\n        script, 2, \"key1\", \"key2\", \"value1\", \"value3\"));\n\n    assertThat(evalFalse, equalTo(\"false\"));\n\n    Object evalFalseBinary = exec(commandObjects.eval(\n        script.getBytes(), 2, \"key1\".getBytes(), \"key2\".getBytes(), \"value1\".getBytes(), \"value3\".getBytes()));\n\n    assertThat(evalFalseBinary, equalTo(\"false\".getBytes()));\n\n    // Incorrect number of keys specified\n    assertThrows(JedisException.class,\n        () -> exec(commandObjects.eval(script, 1, \"key1\", \"value1\", \"value2\")));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvalWithScriptKeysAndArgsList() {\n    exec(commandObjects.hset(\"fruits\", \"apples\", \"5\"));\n    exec(commandObjects.hset(\"fruits\", \"bananas\", \"3\"));\n    exec(commandObjects.hset(\"fruits\", \"oranges\", \"4\"));\n\n    // Script to sum the values for the fruits provided as args. The hash name is provided as key.\n    // The sum is written to a string value whose name is also provided as keys.\n    String script = \"local sum = 0\\n\" +\n        \"for i, fruitKey in ipairs(ARGV) do\\n\" +\n        \"    local value = redis.call('HGET', KEYS[1], fruitKey)\\n\" +\n        \"    if value then\\n\" +\n        \"        sum = sum + tonumber(value)\\n\" +\n        \"    end\\n\" +\n        \"end\\n\" +\n        \"redis.call('SET', KEYS[2], sum)\\n\" +\n        \"return sum\";\n\n    String initialTotal = exec(commandObjects.get(\"total\"));\n    assertThat(initialTotal, nullValue());\n\n    Object eval = exec(commandObjects.eval(script,\n        Arrays.asList(\"fruits\", \"total\"), Arrays.asList(\"apples\", \"bananas\", \"oranges\")));\n\n    assertThat(eval, equalTo(12L));\n\n    String totalAfterEval = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterEval, equalTo(\"12\"));\n\n    // reset\n    assertThat(exec(commandObjects.del(\"total\")), equalTo(1L));\n\n    // binary\n    String initialTotalBinary = exec(commandObjects.get(\"total\"));\n    assertThat(initialTotalBinary, nullValue());\n\n    Object evalBinary = exec(commandObjects.eval(script.getBytes(),\n        Arrays.asList(\"fruits\".getBytes(), \"total\".getBytes()), Arrays.asList(\"apples\".getBytes(), \"bananas\".getBytes(), \"oranges\".getBytes())));\n\n    assertThat(evalBinary, equalTo(12L));\n\n    String totalAfterEvalBinary = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterEvalBinary, equalTo(\"12\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvalReadonlyWithScriptKeysAndArgsList() {\n    exec(commandObjects.set(\"readonlyKey1\", \"readonlyValue1\"));\n    exec(commandObjects.set(\"readonlyKey2\", \"readonlyValue2\"));\n\n    // Script to retrieve values for provided keys, concatenates\n    String script = \"return redis.call('get', KEYS[1]) .. redis.call('get', KEYS[2])\";\n\n    Object eval = exec(commandObjects.evalReadonly(\n        script, Arrays.asList(\"readonlyKey1\", \"readonlyKey2\"), Collections.emptyList()));\n\n    assertThat(eval, equalTo(\"readonlyValue1readonlyValue2\"));\n\n    Object evalBinary = exec(commandObjects.evalReadonly(\n        script.getBytes(), Arrays.asList(\"readonlyKey1\".getBytes(), \"readonlyKey2\".getBytes()), Collections.emptyList()));\n\n    assertThat(evalBinary, equalTo(\"readonlyValue1readonlyValue2\".getBytes()));\n  }\n\n  @Test\n  public void testEvalshaWithSha1() {\n    String script = \"return 42\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    Object eval = exec(commandObjects.evalsha(sha1));\n    assertThat(eval, equalTo(42L));\n\n    Object evalBinary = exec(commandObjects.evalsha(sha1.getBytes()));\n    assertThat(evalBinary, equalTo(42L));\n\n    // incorrect SHA1 hash\n    assertThrows(JedisException.class,\n        () -> exec(commandObjects.evalsha(\"incorrectSha1\")));\n  }\n\n  @Test\n  public void testEvalshaWithSha1AndSampleKey() {\n    String script = \"return redis.call('get', 'foo')\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    exec(commandObjects.set(\"foo\", \"bar\"));\n\n    Object eval = exec(commandObjects.evalsha(sha1, \"sampleKey\"));\n\n    assertThat(eval, equalTo(\"bar\"));\n\n    Object evalBinary = exec(commandObjects.evalsha(sha1.getBytes(), \"sampleKey\".getBytes()));\n\n    assertThat(evalBinary, equalTo(\"bar\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvalWithScriptKeyCountAndParamsSha() {\n    exec(commandObjects.set(\"key1\", \"value1\"));\n    exec(commandObjects.set(\"key2\", \"value2\"));\n\n    // Script to get values of two keys and compare them with expected values\n    String script = \"if redis.call('get', KEYS[1]) == ARGV[1] and redis.call('get', KEYS[2]) == ARGV[2] then return 'true' else return 'false' end\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    Object evalTrue = exec(commandObjects.evalsha(\n        sha1, 2, \"key1\", \"key2\", \"value1\", \"value2\"));\n\n    assertThat(evalTrue, equalTo(\"true\"));\n\n    Object evalTrueBinary = exec(commandObjects.evalsha(\n        sha1.getBytes(), 2, \"key1\".getBytes(), \"key2\".getBytes(), \"value1\".getBytes(), \"value2\".getBytes()));\n\n    assertThat(evalTrueBinary, equalTo(\"true\".getBytes()));\n\n    Object evalFalse = exec(commandObjects.evalsha(\n        sha1, 2, \"key1\", \"key2\", \"value1\", \"value3\"));\n\n    assertThat(evalFalse, equalTo(\"false\"));\n\n    Object evalFalseBinary = exec(commandObjects.evalsha(\n        sha1.getBytes(), 2, \"key1\".getBytes(), \"key2\".getBytes(), \"value1\".getBytes(), \"value3\".getBytes()));\n\n    assertThat(evalFalseBinary, equalTo(\"false\".getBytes()));\n\n    // Incorrect number of keys\n    assertThrows(JedisException.class,\n        () -> exec(commandObjects.evalsha(sha1, 1, \"key1\", \"value1\", \"value2\")));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvalWithScriptKeysAndArgsListSha() {\n    exec(commandObjects.hset(\"fruits\", \"apples\", \"5\"));\n    exec(commandObjects.hset(\"fruits\", \"bananas\", \"3\"));\n    exec(commandObjects.hset(\"fruits\", \"oranges\", \"4\"));\n\n    // Sums the values for given fruits, stores the result, and returns it\n    String script = \"local sum = 0\\n\" +\n        \"for i, fruitKey in ipairs(ARGV) do\\n\" +\n        \"    local value = redis.call('HGET', KEYS[1], fruitKey)\\n\" +\n        \"    if value then\\n\" +\n        \"        sum = sum + tonumber(value)\\n\" +\n        \"    end\\n\" +\n        \"end\\n\" +\n        \"redis.call('SET', KEYS[2], sum)\\n\" +\n        \"return sum\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    String initialTotal = exec(commandObjects.get(\"total\"));\n    assertThat(initialTotal, nullValue());\n\n    Object eval = exec(commandObjects.evalsha(\n        sha1, Arrays.asList(\"fruits\", \"total\"), Arrays.asList(\"apples\", \"bananas\", \"oranges\")));\n\n    assertThat(eval, equalTo(12L));\n\n    String totalAfterEval = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterEval, equalTo(\"12\"));\n\n    // reset\n    assertThat(exec(commandObjects.del(\"total\")), equalTo(1L));\n\n    // binary\n    String initialTotalBinary = exec(commandObjects.get(\"total\"));\n    assertThat(initialTotalBinary, nullValue());\n\n    Object evalBinary = exec(commandObjects.evalsha(\n        sha1.getBytes(),\n        Arrays.asList(\"fruits\".getBytes(), \"total\".getBytes()),\n        Arrays.asList(\"apples\".getBytes(), \"bananas\".getBytes(), \"oranges\".getBytes())));\n\n    assertThat(evalBinary, equalTo(12L));\n\n    String totalAfterEvalBinary = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterEvalBinary, equalTo(\"12\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvalReadonlyWithScriptKeysAndArgsListSha() {\n    exec(commandObjects.set(\"readonlyKey1\", \"readonlyValue1\"));\n    exec(commandObjects.set(\"readonlyKey2\", \"readonlyValue2\"));\n\n    // Script to retrieve values for provided keys, concatenated\n    String script = \"return redis.call('get', KEYS[1]) .. redis.call('get', KEYS[2])\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    Object eval = exec(commandObjects.evalshaReadonly(\n        sha1,\n        Arrays.asList(\"readonlyKey1\", \"readonlyKey2\"),\n        Collections.emptyList()));\n\n    assertThat(eval, equalTo(\"readonlyValue1readonlyValue2\"));\n\n    Object evalBinary = exec(commandObjects.evalshaReadonly(\n        sha1.getBytes(),\n        Arrays.asList(\"readonlyKey1\".getBytes(), \"readonlyKey2\".getBytes()),\n        Collections.emptyList()));\n\n    assertThat(evalBinary, equalTo(\"readonlyValue1readonlyValue2\".getBytes()));\n  }\n\n  @Test\n  public void testScriptExists() {\n    String script = \"return 'test script'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    List<Boolean> exists = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n\n    assertThat(exists, contains(true));\n\n    // Load another script to test with multiple SHA1 hashes\n    String anotherScript = \"return 'another test script'\";\n    String anotherSha1 = exec(commandObjects.scriptLoad(anotherScript));\n    assertThat(anotherSha1, notNullValue());\n\n    String nonExistingSha1 = \"nonexistentsha1\";\n\n    List<Boolean> existsMultiple = exec(commandObjects.scriptExists(\n        \"sampleKey\", sha1, anotherSha1, nonExistingSha1));\n\n    assertThat(existsMultiple, contains(true, true, false));\n\n    List<Boolean> existsMultipleBinary = exec(commandObjects.scriptExists(\n        \"sampleKey\".getBytes(), sha1.getBytes(), anotherSha1.getBytes(), nonExistingSha1.getBytes()));\n\n    assertThat(existsMultipleBinary, contains(true, true, false));\n  }\n\n  @Test\n  public void testScriptLoadAndRun() {\n    String script = \"return 'Hello, Redis!'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    Object scriptResponse1 = exec(commandObjects.evalsha(sha1));\n    assertThat(scriptResponse1, equalTo(\"Hello, Redis!\"));\n  }\n\n  @Test\n  public void testScriptLoadAndRunSampleKey() {\n    String anotherScript = \"return redis.call('get', 'testKey')\";\n\n    String sampleKey = \"testKey\";\n    exec(commandObjects.set(sampleKey, \"sampleValue\")); // Set a value for the sampleKey\n\n    String anotherSha1 = exec(commandObjects.scriptLoad(anotherScript, sampleKey));\n    assertThat(anotherSha1, notNullValue());\n\n    Object scriptResponse2 = exec(commandObjects.evalsha(anotherSha1, sampleKey));\n    assertThat(scriptResponse2, equalTo(\"sampleValue\"));\n  }\n\n  @Test\n  public void testScriptLoadAndRunSampleKeyBinary() {\n    String anotherScript = \"return redis.call('get', 'testKey')\";\n\n    String sampleKey = \"testKey\";\n    exec(commandObjects.set(sampleKey, \"sampleValue\")); // Set a value for the sampleKey\n\n    byte[] anotherSha1 = exec(commandObjects.scriptLoad(anotherScript.getBytes(), sampleKey.getBytes()));\n    assertThat(anotherSha1, notNullValue());\n\n    Object scriptResponse2 = exec(commandObjects.evalsha(anotherSha1, sampleKey.getBytes()));\n    assertThat(scriptResponse2, equalTo(\"sampleValue\".getBytes()));\n  }\n\n  @Test\n  public void testScriptFlush() {\n    String script = \"return 'test script flush'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    List<Boolean> existsBefore = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsBefore, contains(true));\n\n    String flush = exec(commandObjects.scriptFlush());\n    assertThat(flush, equalTo(\"OK\"));\n\n    List<Boolean> existsAfter = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsAfter, contains(false));\n  }\n\n  @Test\n  public void testScriptFlushSampleKeyAndMode() {\n    String script = \"return 'test script flush'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    List<Boolean> existsBefore = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsBefore, contains(true));\n\n    String flush = exec(commandObjects.scriptFlush(\"anyKey\", FlushMode.SYNC));\n    assertThat(flush, equalTo(\"OK\"));\n\n    List<Boolean> existsAfter = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsAfter, contains(false));\n  }\n\n  @Test\n  public void testScriptFlushSampleKey() {\n    String script = \"return 'test script flush'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    List<Boolean> existsBefore = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsBefore, contains(true));\n\n    String flush = exec(commandObjects.scriptFlush(\"anyKey\"));\n    assertThat(flush, equalTo(\"OK\"));\n\n    List<Boolean> existsAfter = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsAfter, contains(false));\n  }\n\n  @Test\n  public void testScriptFlushBinary() {\n    String script = \"return 'test script flush'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    List<Boolean> existsBefore = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsBefore, contains(true));\n\n    String flush = exec(commandObjects.scriptFlush(\"anyKey\".getBytes()));\n    assertThat(flush, equalTo(\"OK\"));\n\n    List<Boolean> existsAfter = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsAfter, contains(false));\n  }\n\n  @Test\n  public void testScriptFlushSampleKeyAndModeBinary() {\n    String script = \"return 'test script flush'\";\n    String sha1 = exec(commandObjects.scriptLoad(script));\n    assertThat(sha1, notNullValue());\n\n    List<Boolean> existsBefore = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsBefore, contains(true));\n\n    String flush = exec(commandObjects.scriptFlush(\"anyKey\".getBytes(), FlushMode.SYNC));\n    assertThat(flush, equalTo(\"OK\"));\n\n    List<Boolean> existsAfter = exec(commandObjects.scriptExists(Collections.singletonList(sha1)));\n    assertThat(existsAfter, contains(false));\n  }\n\n  @Test\n  public void testScriptKill() {\n    JedisException e = assertThrows(JedisException.class,\n        () -> exec(commandObjects.scriptKill()));\n    assertThat(e.getMessage(), containsString(\"No scripts in execution right now.\"));\n\n    e = assertThrows(JedisException.class,\n        () -> exec(commandObjects.scriptKill(\"anyKey\")));\n    assertThat(e.getMessage(), containsString(\"No scripts in execution right now.\"));\n\n    e = assertThrows(JedisException.class,\n        () -> exec(commandObjects.scriptKill(\"anyKey\".getBytes())));\n    assertThat(e.getMessage(), containsString(\"No scripts in execution right now.\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSumValuesFunction() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args)\\n\" +\n        \"local sum = 0\\n\" +\n        \"for _, key in ipairs(keys) do\\n\" +\n        \"local val = redis.call('GET', key)\\n\" +\n        \"if val then sum = sum + tonumber(val) end\\n\" +\n        \"end\\n\" +\n        \"redis.call('SET', 'total', sum)\\n\" +\n        \"return sum\\n\" +\n        \"end)\";\n    String functionLoad = exec(commandObjects.functionLoad(luaScript));\n    assertThat(functionLoad, equalTo(\"mylib\"));\n\n    exec(commandObjects.set(\"key1\", \"10\"));\n    exec(commandObjects.set(\"key2\", \"20\"));\n    exec(commandObjects.set(\"key3\", \"30\"));\n\n    String initialTotal = exec(commandObjects.get(\"total\"));\n    assertThat(initialTotal, nullValue());\n\n    Object fcall = exec(commandObjects.fcall(\n        \"sumValues\",\n        Arrays.asList(\"key1\", \"key2\", \"key3\"),\n        new ArrayList<>()));\n\n    assertThat(fcall, equalTo(60L));\n\n    String totalAfterFcall = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterFcall, equalTo(\"60\"));\n\n    // reset\n    exec(commandObjects.del(\"total\"));\n\n    String totalAfterRest = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterRest, nullValue());\n\n    Object fcallBinary = exec(commandObjects.fcall(\n        \"sumValues\".getBytes(),\n        Arrays.asList(\"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes()),\n        new ArrayList<>()));\n\n    assertThat(fcallBinary, equalTo(60L));\n\n    String totalAfterFcallBinary = exec(commandObjects.get(\"total\"));\n    assertThat(totalAfterFcallBinary, equalTo(\"60\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSumValuesFunctionReadonly() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function{function_name='sumValues', callback=function(keys, args)\\n\" +\n        \"local sum = 0\\n\" +\n        \"for _, key in ipairs(keys) do\\n\" +\n        \"local val = redis.call('GET', key)\\n\" +\n        \"if val then sum = sum + tonumber(val) end\\n\" +\n        \"end\\n\" +\n        \"return sum\\n\" +\n        \"end, flags={'no-writes'}}\";\n    String functionLoad = exec(commandObjects.functionLoad(luaScript));\n    assertThat(functionLoad, equalTo(\"mylib\"));\n\n    exec(commandObjects.set(\"key1\", \"10\"));\n    exec(commandObjects.set(\"key2\", \"20\"));\n    exec(commandObjects.set(\"key3\", \"30\"));\n\n    Object fcall = exec(commandObjects.fcallReadonly(\n        \"sumValues\",\n        Arrays.asList(\"key1\", \"key2\", \"key3\"),\n        new ArrayList<>()));\n\n    assertThat(fcall, equalTo(60L));\n\n    Object fcallBinary = exec(commandObjects.fcallReadonly(\n        \"sumValues\".getBytes(),\n        Arrays.asList(\"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes()),\n        new ArrayList<>()));\n\n    assertThat(fcallBinary, equalTo(60L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDeletion() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args) return 42 end)\";\n    exec(commandObjects.functionLoad(luaScript));\n\n    String libraryName = \"mylib\";\n\n    List<LibraryInfo> listResponse = exec(commandObjects.functionList());\n\n    assertThat(listResponse, hasSize(1));\n    assertThat(listResponse.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listResponse.get(0).getFunctions(), hasSize(1));\n    assertThat(listResponse.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n\n    String delete = exec(commandObjects.functionDelete(libraryName));\n    assertThat(delete, equalTo(\"OK\"));\n\n    listResponse = exec(commandObjects.functionList());\n    assertThat(listResponse, empty());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDeletionBinary() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args) return 42 end)\";\n    exec(commandObjects.functionLoad(luaScript));\n\n    String libraryName = \"mylib\";\n\n    List<LibraryInfo> listResponse = exec(commandObjects.functionList());\n\n    assertThat(listResponse, hasSize(1));\n    assertThat(listResponse.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listResponse.get(0).getFunctions(), hasSize(1));\n    assertThat(listResponse.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n\n    String deleteBinary = exec(commandObjects.functionDelete(libraryName.getBytes()));\n    assertThat(deleteBinary, equalTo(\"OK\"));\n\n    listResponse = exec(commandObjects.functionList());\n    assertThat(listResponse, empty());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionListing() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args) return 42 end)\";\n    exec(commandObjects.functionLoad(luaScript));\n\n    String libraryName = \"mylib\";\n\n    List<LibraryInfo> list = exec(commandObjects.functionList());\n\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n    assertThat(list.get(0).getLibraryCode(), nullValue());\n\n    List<Object> listBinary = exec(commandObjects.functionListBinary());\n\n    assertThat(listBinary, hasSize(1));\n\n    List<LibraryInfo> listLibrary = exec(commandObjects.functionList(libraryName));\n\n    assertThat(listLibrary, hasSize(1));\n    assertThat(listLibrary.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listLibrary.get(0).getFunctions(), hasSize(1));\n    assertThat(listLibrary.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n    assertThat(listLibrary.get(0).getLibraryCode(), nullValue());\n\n    List<Object> listLibraryBinary = exec(commandObjects.functionList(libraryName.getBytes()));\n\n    assertThat(listLibraryBinary, hasSize(1));\n\n    List<LibraryInfo> listWithCode = exec(commandObjects.functionListWithCode());\n\n    assertThat(listWithCode, hasSize(1));\n    assertThat(listWithCode.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listWithCode.get(0).getFunctions(), hasSize(1));\n    assertThat(listWithCode.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n    assertThat(listWithCode.get(0).getLibraryCode(), notNullValue());\n\n    List<Object> listWithCodeBinary = exec(commandObjects.functionListWithCodeBinary());\n\n    assertThat(listWithCodeBinary, hasSize(1));\n\n    List<LibraryInfo> listWithCodeLibrary = exec(commandObjects.functionListWithCode(libraryName));\n\n    assertThat(listWithCodeLibrary, hasSize(1));\n    assertThat(listWithCodeLibrary.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listWithCodeLibrary.get(0).getFunctions(), hasSize(1));\n    assertThat(listWithCodeLibrary.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n    assertThat(listWithCodeLibrary.get(0).getLibraryCode(), notNullValue());\n\n    List<Object> listWithCodeLibraryBinary = exec(commandObjects.functionListWithCode(libraryName.getBytes()));\n\n    assertThat(listWithCodeLibraryBinary, hasSize(1));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionReload() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('dummy', function(keys, args) return 42 end)\";\n    String loadResult = exec(commandObjects.functionLoad(luaScript));\n    assertThat(loadResult, equalTo(\"mylib\"));\n\n    Object result = exec(commandObjects.fcall(\n        \"dummy\".getBytes(), new ArrayList<>(), new ArrayList<>()));\n    assertThat(result, equalTo(42L));\n\n    String luaScriptChanged = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('dummy', function(keys, args) return 52 end)\";\n    String replaceResult = exec(commandObjects.functionLoadReplace(luaScriptChanged));\n    assertThat(replaceResult, equalTo(\"mylib\"));\n\n    Object resultAfter = exec(commandObjects.fcall(\n        \"dummy\".getBytes(), new ArrayList<>(), new ArrayList<>()));\n    assertThat(resultAfter, equalTo(52L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionReloadBinary() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('dummy', function(keys, args) return 42 end)\";\n    String loadResult = exec(commandObjects.functionLoad(luaScript.getBytes()));\n    assertThat(loadResult, equalTo(\"mylib\"));\n\n    Object result = exec(commandObjects.fcall((\n        \"dummy\").getBytes(), new ArrayList<>(), new ArrayList<>()));\n    assertThat(result, equalTo(42L));\n\n    String luaScriptChanged = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('dummy', function(keys, args) return 52 end)\";\n    String replaceResult = exec(commandObjects.functionLoadReplace(luaScriptChanged.getBytes()));\n    assertThat(replaceResult, equalTo(\"mylib\"));\n\n    Object resultAfter = exec(commandObjects.fcall(\n        \"dummy\".getBytes(), new ArrayList<>(), new ArrayList<>()));\n    assertThat(resultAfter, equalTo(52L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionStats() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('dummy', function(keys, args) return 42 end)\";\n    String loadResult = exec(commandObjects.functionLoad(luaScript));\n    assertThat(loadResult, equalTo(\"mylib\"));\n\n    for (int i = 0; i < 5; i++) {\n      Object result = exec(commandObjects.fcall(\n          \"dummy\".getBytes(), new ArrayList<>(), new ArrayList<>()));\n      assertThat(result, equalTo(42L));\n    }\n\n    FunctionStats stats = exec(commandObjects.functionStats());\n\n    assertThat(stats, notNullValue());\n    assertThat(stats.getEngines(), hasKey(\"LUA\"));\n    Map<String, Object> luaStats = stats.getEngines().get(\"LUA\");\n    assertThat(luaStats, hasEntry(\"libraries_count\", 1L));\n    assertThat(luaStats, hasEntry(\"functions_count\", 1L));\n\n    Object statsBinary = exec(commandObjects.functionStatsBinary());\n\n    assertThat(statsBinary, notNullValue());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDumpFlushRestore() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args) return 42 end)\";\n    exec(commandObjects.functionLoad(luaScript));\n\n    String libraryName = \"mylib\";\n\n    List<LibraryInfo> list = exec(commandObjects.functionList());\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n\n    byte[] dump = exec(commandObjects.functionDump());\n    assertThat(dump, notNullValue());\n\n    String flush = exec(commandObjects.functionFlush());\n    assertThat(flush, equalTo(\"OK\"));\n\n    list = exec(commandObjects.functionList());\n    assertThat(list, empty());\n\n    String restore = exec(commandObjects.functionRestore(dump));\n    assertThat(restore, equalTo(\"OK\"));\n\n    list = exec(commandObjects.functionList());\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDumpFlushRestoreWithPolicy() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args) return 42 end)\";\n    exec(commandObjects.functionLoad(luaScript));\n\n    String libraryName = \"mylib\";\n\n    List<LibraryInfo> list = exec(commandObjects.functionList());\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n\n    byte[] dump = exec(commandObjects.functionDump());\n    assertThat(dump, notNullValue());\n\n    String flush = exec(commandObjects.functionFlush());\n    assertThat(flush, equalTo(\"OK\"));\n\n    list = exec(commandObjects.functionList());\n    assertThat(list, empty());\n\n    String restore = exec(commandObjects.functionRestore(dump, FunctionRestorePolicy.REPLACE));\n    assertThat(restore, equalTo(\"OK\"));\n\n    list = exec(commandObjects.functionList());\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionFlushWithMode() {\n    String luaScript = \"#!lua name=mylib\\n\" +\n        \"redis.register_function('sumValues', function(keys, args) return 42 end)\";\n    exec(commandObjects.functionLoad(luaScript));\n\n    String libraryName = \"mylib\";\n\n    List<LibraryInfo> list = exec(commandObjects.functionList());\n\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", \"sumValues\"));\n\n    String flush = exec(commandObjects.functionFlush(FlushMode.SYNC));\n    assertThat(flush, equalTo(\"OK\"));\n\n    list = exec(commandObjects.functionList());\n    assertThat(list, empty());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionKill() {\n    JedisException e = assertThrows(JedisException.class,\n        () -> exec(commandObjects.functionKill()));\n    assertThat(e.getMessage(), containsString(\"No scripts in execution right now\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsSearchAndQueryCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anEmptyMap;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.emptyOrNullString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.equalToIgnoringCase;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasKey;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.SortingOrder;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.search.Document;\nimport redis.clients.jedis.search.FTCreateParams;\nimport redis.clients.jedis.search.FTSearchParams;\nimport redis.clients.jedis.search.FTSpellCheckParams;\nimport redis.clients.jedis.search.IndexDataType;\nimport redis.clients.jedis.search.IndexDefinition;\nimport redis.clients.jedis.search.IndexOptions;\nimport redis.clients.jedis.search.Query;\nimport redis.clients.jedis.search.Schema;\nimport redis.clients.jedis.search.SearchResult;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.aggr.Reducers;\nimport redis.clients.jedis.search.schemafields.NumericField;\nimport redis.clients.jedis.search.schemafields.SchemaField;\nimport redis.clients.jedis.search.schemafields.TagField;\nimport redis.clients.jedis.search.schemafields.TextField;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=search\">Search and query</a> commands.\n */\npublic class CommandObjectsSearchAndQueryCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsSearchAndQueryCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testFtSearchHash() {\n    String indexName = \"booksIndex\";\n\n    IndexDefinition indexDefinition =\n        new IndexDefinition(IndexDefinition.Type.HASH).setPrefixes(\"books:\");\n\n    IndexOptions indexOptions = IndexOptions.defaultOptions().setDefinition(indexDefinition);\n\n    Schema schema = new Schema()\n        .addField(new Schema.Field(\"title\", Schema.FieldType.TEXT))\n        .addField(new Schema.Field(\"price\", Schema.FieldType.NUMERIC));\n\n    String create = exec(commandObjects.ftCreate(indexName, indexOptions, schema));\n    assertThat(create, equalTo(\"OK\"));\n\n    // Set individual fields.\n    String book1000 = \"books:1000\";\n\n    Long hset = exec(commandObjects.hsetObject(book1000, \"title\", \"Redis in Action\"));\n    assertThat(hset, equalTo(1L));\n\n    hset = exec(commandObjects.hsetObject(book1000, \"price\", 17.99));\n    assertThat(hset, equalTo(1L));\n\n    hset = exec(commandObjects.hsetObject(book1000, \"author\", \"John Doe\"));\n    assertThat(hset, equalTo(1L));\n\n    // Set multiple fields.\n    Map<String, Object> hash = new HashMap<>();\n    hash.put(\"title\", \"Redis Essentials\");\n    hash.put(\"price\", 19.99);\n    hash.put(\"author\", \"Jane Doe\");\n    String book1200 = \"books:1200\";\n\n    Long hsetMultiple = exec(commandObjects.hsetObject(book1200, hash));\n    assertThat(hsetMultiple, equalTo(3L));\n\n    // Text search.\n    SearchResult search = exec(commandObjects.ftSearch(indexName, \"Action\"));\n\n    assertThat(search.getTotalResults(), equalTo(1L));\n    assertThat(search.getDocuments(), hasSize(1));\n\n    Document document = search.getDocuments().get(0);\n    assertThat(document.getId(), equalTo(book1000));\n    assertThat(document.get(\"title\"), equalTo(\"Redis in Action\"));\n    assertThat(document.get(\"price\"), equalTo(\"17.99\"));\n    assertThat(document.get(\"author\"), equalTo(\"John Doe\"));\n\n    // Price range search.\n    SearchResult searchByPrice = exec(commandObjects.ftSearch(indexName, \"@price:[19 +inf]\"));\n\n    assertThat(searchByPrice.getTotalResults(), equalTo(1L));\n    assertThat(searchByPrice.getDocuments(), hasSize(1));\n\n    Document documentByPrice = searchByPrice.getDocuments().get(0);\n    assertThat(documentByPrice.getId(), equalTo(book1200));\n    assertThat(documentByPrice.get(\"title\"), equalTo(\"Redis Essentials\"));\n    assertThat(documentByPrice.get(\"price\"), equalTo(\"19.99\"));\n    assertThat(documentByPrice.get(\"author\"), equalTo(\"Jane Doe\"));\n\n    // Price range search with sorting.\n    FTSearchParams ftSearchParams = new FTSearchParams().sortBy(\"price\", SortingOrder.DESC);\n    SearchResult searchByPriceWithParams = exec(commandObjects.ftSearch(indexName, \"@price:[10 20]\", ftSearchParams));\n\n    assertThat(searchByPriceWithParams.getTotalResults(), equalTo(2L));\n    assertThat(searchByPriceWithParams.getDocuments(), hasSize(2));\n    assertThat(searchByPriceWithParams.getDocuments().stream().map(Document::getId).collect(Collectors.toList()),\n        contains(book1200, book1000));\n\n    Query query = new Query()\n        .addFilter(new Query.NumericFilter(\"price\", 19.0, 20.0))\n        .returnFields(\"price\", \"title\");\n    SearchResult searchByPriceWithQuery = exec(commandObjects.ftSearch(indexName, query));\n\n    assertThat(searchByPriceWithQuery.getTotalResults(), equalTo(1L));\n    assertThat(searchByPriceWithQuery.getDocuments(), hasSize(1));\n\n    Document documentByPriceWithQuery = searchByPriceWithQuery.getDocuments().get(0);\n    assertThat(documentByPriceWithQuery.getId(), equalTo(book1200));\n    assertThat(documentByPriceWithQuery.get(\"title\"), equalTo(\"Redis Essentials\"));\n    assertThat(documentByPriceWithQuery.get(\"price\"), equalTo(\"19.99\"));\n    assertThat(documentByPriceWithQuery.get(\"author\"), nullValue());\n  }\n\n  @Test\n  public void testFtSearchJson() {\n    String indexName = \"testIndex\";\n\n    IndexDefinition indexDefinition = new IndexDefinition(IndexDefinition.Type.JSON)\n        .setPrefixes(\"books:\");\n\n    IndexOptions indexOptions = IndexOptions.defaultOptions().setDefinition(indexDefinition);\n\n    Schema schema = new Schema()\n        .addField(new Schema.Field(\"$.title\", Schema.FieldType.TEXT))\n        .addField(new Schema.Field(\"$.price\", Schema.FieldType.NUMERIC));\n\n    String create = exec(commandObjects.ftCreate(indexName, indexOptions, schema));\n    assertThat(create, equalTo(\"OK\"));\n\n    Map<String, Object> hash = new HashMap<>();\n    hash.put(\"title\", \"Redis in Action\");\n    hash.put(\"price\", 17.99);\n    hash.put(\"author\", \"John Doe\");\n\n    String jsonSet = exec(commandObjects.jsonSet(\"books:1000\", Path2.ROOT_PATH, new JSONObject(hash)));\n    assertThat(jsonSet, equalTo(\"OK\"));\n\n    Map<String, Object> hash2 = new HashMap<>();\n    hash2.put(\"title\", \"Redis Essentials\");\n    hash2.put(\"price\", 19.99);\n    hash2.put(\"author\", \"Jane Doe\");\n\n    String jsonSet2 = exec(commandObjects.jsonSet(\"books:1200\", Path2.ROOT_PATH, new JSONObject(hash2)));\n    assertThat(jsonSet2, equalTo(\"OK\"));\n\n    SearchResult searchResult = exec(commandObjects.ftSearch(indexName, \"Action\"));\n\n    assertThat(searchResult.getTotalResults(), equalTo(1L));\n    assertThat(searchResult.getDocuments(), hasSize(1));\n\n    Document document = searchResult.getDocuments().get(0);\n    assertThat(document.getId(), equalTo(\"books:1000\"));\n    assertThat(document.get(\"$\"), equalTo(\"{\\\"title\\\":\\\"Redis in Action\\\",\\\"price\\\":17.99,\\\"author\\\":\\\"John Doe\\\"}\"));\n  }\n\n  @Test\n  public void testFtCreateWithParams() {\n    String indexName = \"booksIndex\";\n\n    SchemaField[] schema = {\n        TextField.of(\"$.title\").as(\"title\"),\n        NumericField.of(\"$.price\").as(\"price\")\n    };\n\n    FTCreateParams createParams = FTCreateParams.createParams()\n        .on(IndexDataType.JSON)\n        .addPrefix(\"books:\");\n\n    String createResult = exec(commandObjects.ftCreate(indexName, createParams, Arrays.asList(schema)));\n    assertThat(createResult, equalTo(\"OK\"));\n\n    JSONObject bookRedisInAction = new JSONObject();\n    bookRedisInAction.put(\"title\", \"Redis in Action\");\n    bookRedisInAction.put(\"price\", 17.99);\n    bookRedisInAction.put(\"author\", \"John Doe\");\n\n    String jsonSet = exec(commandObjects.jsonSet(\"books:1000\", Path2.ROOT_PATH, bookRedisInAction));\n    assertThat(jsonSet, equalTo(\"OK\"));\n\n    JSONObject bookRedisEssentials = new JSONObject();\n    bookRedisEssentials.put(\"title\", \"Redis Essentials\");\n    bookRedisEssentials.put(\"price\", 19.99);\n    bookRedisEssentials.put(\"author\", \"Jane Doe\");\n\n    String jsonSet2 = exec(commandObjects.jsonSet(\"books:1200\", Path2.ROOT_PATH, bookRedisEssentials));\n    assertThat(jsonSet2, equalTo(\"OK\"));\n\n    SearchResult searchResult = exec(commandObjects.ftSearch(indexName, \"Action\"));\n\n    assertThat(searchResult.getTotalResults(), equalTo(1L));\n    assertThat(searchResult.getDocuments(), hasSize(1));\n\n    Document document = searchResult.getDocuments().get(0);\n    assertThat(document.getId(), equalTo(\"books:1000\"));\n\n    Object documentRoot = document.get(\"$\");\n    assertThat(documentRoot, instanceOf(String.class)); // Unparsed!\n    assertThat(documentRoot, jsonEquals(bookRedisInAction));\n  }\n\n  @Test\n  public void testFtAlterWithParams() throws InterruptedException {\n    String indexName = \"booksIndex\";\n\n    List<SchemaField> schema = new ArrayList<>();\n    schema.add(TextField.of(\"$.title\").as(\"title\"));\n    schema.add(NumericField.of(\"$.price\").as(\"price\"));\n\n    FTCreateParams createParams = FTCreateParams.createParams()\n        .on(IndexDataType.JSON)\n        .addPrefix(\"books:\");\n\n    String createResult = exec(commandObjects.ftCreate(indexName, createParams, schema));\n    assertThat(createResult, equalTo(\"OK\"));\n\n    JSONObject bookRedisInAction = new JSONObject();\n    bookRedisInAction.put(\"title\", \"Redis in Action\");\n    bookRedisInAction.put(\"price\", 17.99);\n    bookRedisInAction.put(\"author\", \"John Doe\");\n\n    String jsonSet = exec(commandObjects.jsonSet(\"books:1000\", Path2.ROOT_PATH, bookRedisInAction));\n    assertThat(jsonSet, equalTo(\"OK\"));\n\n    JSONObject bookRedisEssentials = new JSONObject();\n    bookRedisEssentials.put(\"title\", \"Redis Essentials\");\n    bookRedisEssentials.put(\"price\", 19.99);\n    bookRedisEssentials.put(\"author\", \"Jane Doe\");\n\n    String jsonSet2 = exec(commandObjects.jsonSet(\"books:1200\", Path2.ROOT_PATH, bookRedisEssentials));\n    assertThat(jsonSet2, equalTo(\"OK\"));\n\n    SearchResult searchNotInIndex = exec(commandObjects.ftSearch(indexName, \"John\"));\n\n    assertThat(searchNotInIndex.getTotalResults(), equalTo(0L));\n    assertThat(searchNotInIndex.getDocuments(), empty());\n\n    List<SchemaField> schemaExtension = new ArrayList<>();\n    schemaExtension.add(TextField.of(\"$.author\").as(\"author\"));\n\n    String alter = exec(commandObjects.ftAlter(indexName, schemaExtension));\n    assertThat(alter, equalTo(\"OK\"));\n\n    Thread.sleep(300); // wait for index to be updated\n\n    SearchResult searchInIndex = exec(commandObjects.ftSearch(indexName, \"John\"));\n\n    assertThat(searchInIndex.getTotalResults(), equalTo(1L));\n    assertThat(searchInIndex.getDocuments(), hasSize(1));\n\n    Document document = searchInIndex.getDocuments().get(0);\n    assertThat(document.getId(), equalTo(\"books:1000\"));\n\n    Object documentRoot = document.get(\"$\");\n    assertThat(documentRoot, instanceOf(String.class)); // Unparsed!\n    assertThat(documentRoot, jsonEquals(bookRedisInAction));\n  }\n\n  @Test\n  public void testFtExplain() {\n    String indexName = \"booksIndex\";\n\n    IndexDefinition indexDefinition = new IndexDefinition(IndexDefinition.Type.HASH).setPrefixes(\"books:\");\n\n    IndexOptions indexOptions = IndexOptions.defaultOptions().setDefinition(indexDefinition);\n\n    Schema schema = new Schema()\n        .addField(new Schema.Field(\"title\", Schema.FieldType.TEXT))\n        .addField(new Schema.Field(\"price\", Schema.FieldType.NUMERIC));\n\n    String createResult = exec(commandObjects.ftCreate(indexName, indexOptions, schema));\n    assertThat(createResult, equalTo(\"OK\"));\n\n    // Add a book to the index\n    String bookId = \"books:123\";\n\n    Map<String, Object> bookFields = new HashMap<>();\n    bookFields.put(\"title\", \"Redis for Dummies\");\n    bookFields.put(\"price\", 29.99);\n\n    Long hsetResult = exec(commandObjects.hsetObject(bookId, bookFields));\n    assertThat(hsetResult, equalTo(2L));\n\n    Query query = new Query(\"Redis\").returnFields(\"title\", \"price\");\n\n    String explanation = exec(commandObjects.ftExplain(indexName, query));\n    assertThat(explanation, not(emptyOrNullString()));\n\n    List<String> explanationCli = exec(commandObjects.ftExplainCLI(indexName, query));\n    assertThat(explanationCli, not(empty()));\n  }\n\n  @Test\n  public void testFtAggregate() {\n    String indexName = \"booksIndex\";\n\n    IndexDefinition indexDefinition = new IndexDefinition(IndexDefinition.Type.HASH).setPrefixes(\"books:\");\n\n    IndexOptions indexOptions = IndexOptions.defaultOptions().setDefinition(indexDefinition);\n\n    Schema schema = new Schema()\n        .addField(new Schema.Field(\"title\", Schema.FieldType.TEXT))\n        .addField(new Schema.Field(\"price\", Schema.FieldType.NUMERIC))\n        .addField(new Schema.Field(\"genre\", Schema.FieldType.TAG));\n\n    String createResult = exec(commandObjects.ftCreate(indexName, indexOptions, schema));\n    assertThat(createResult, equalTo(\"OK\"));\n\n    // Add books to the index\n    Map<String, Object> book1Fields = new HashMap<>();\n    book1Fields.put(\"title\", \"Redis for Dummies\");\n    book1Fields.put(\"price\", 20.99);\n    book1Fields.put(\"genre\", \"Technology\");\n\n    String book1Id = \"books:101\";\n\n    exec(commandObjects.hsetObject(book1Id, book1Fields));\n\n    Map<String, Object> book2Fields = new HashMap<>();\n    book2Fields.put(\"title\", \"Advanced Redis\");\n    book2Fields.put(\"price\", 25.99);\n    book2Fields.put(\"genre\", \"Technology\");\n\n    String book2Id = \"books:102\";\n\n    exec(commandObjects.hsetObject(book2Id, book2Fields));\n\n    // Aggregation: average price of books in the 'Technology' genre\n    AggregationBuilder aggr = new AggregationBuilder()\n        .groupBy(\"@genre\", Reducers.avg(\"@price\").as(\"avgPrice\"))\n        .filter(\"@genre=='Technology'\");\n\n    AggregationResult aggregationResult = exec(commandObjects.ftAggregate(indexName, aggr));\n\n    assertThat(aggregationResult, notNullValue());\n    assertThat(aggregationResult.getResults(), hasSize(1));\n\n    Map<String, Object> result = aggregationResult.getResults().get(0);\n    assertThat(result, hasEntry(\"genre\", \"Technology\"));\n    assertThat(result, hasEntry(\"avgPrice\", \"23.49\"));\n  }\n\n  @Test\n  public void testSpellCheck() {\n    // Add some terms to an index\n    String indexName = \"techArticles\";\n\n    List<SchemaField> schemaFields = Collections.singletonList(TextField.of(\"$.technology\"));\n    exec(commandObjects.ftCreate(indexName, FTCreateParams.createParams().on(IndexDataType.JSON), schemaFields));\n\n    exec(commandObjects.jsonSet(\"articles:02\", Path2.ROOT_PATH, new JSONObject().put(\"technology\", \"Flutter\")));\n    exec(commandObjects.jsonSet(\"articles:03\", Path2.ROOT_PATH, new JSONObject().put(\"technology\", \"Rust\")));\n    exec(commandObjects.jsonSet(\"articles:04\", Path2.ROOT_PATH, new JSONObject().put(\"technology\", \"Angular\")));\n\n    SearchResult searchInIndex = exec(commandObjects.ftSearch(indexName, \"Flutter\"));\n    assertThat(searchInIndex.getTotalResults(), equalTo(1L));\n\n    String query = \"Fluter JavaScrit Pyhton Rust\";\n\n    // Spellcheck based on index only\n    Map<String, Map<String, Double>> indexOnly = exec(commandObjects.ftSpellCheck(indexName, query));\n    assertThat(indexOnly.get(\"fluter\"), hasKey(equalToIgnoringCase(\"Flutter\")));\n    assertThat(indexOnly.get(\"javascrit\"), anEmptyMap());\n    assertThat(indexOnly.get(\"pyhton\"), anEmptyMap());\n\n    // Add more terms to a dictionary\n    String dictionary = \"techDict\";\n\n    Long addResult = exec(commandObjects.ftDictAdd(dictionary, \"JavaScript\", \"Python\"));\n    assertThat(addResult, equalTo(2L));\n\n    // Spellcheck based on index and dictionary\n    FTSpellCheckParams paramsWithDict = new FTSpellCheckParams().includeTerm(dictionary);\n\n    Map<String, Map<String, Double>> indexAndDictionary = exec(commandObjects.ftSpellCheck(indexName, query, paramsWithDict));\n    assertThat(indexAndDictionary.get(\"fluter\"), hasKey(equalToIgnoringCase(\"Flutter\")));\n    assertThat(indexAndDictionary.get(\"javascrit\"), hasKey(\"JavaScript\"));\n    assertThat(indexAndDictionary.get(\"pyhton\"), anEmptyMap());\n\n    // Increase Levenshtein distance, to allow for misspelled letter\n    FTSpellCheckParams paramsWithDictAndDist = new FTSpellCheckParams().includeTerm(dictionary).distance(2);\n\n    Map<String, Map<String, Double>> indexAndDictionaryWithDist = exec(commandObjects.ftSpellCheck(indexName, query, paramsWithDictAndDist));\n    assertThat(indexAndDictionaryWithDist.get(\"fluter\"), hasKey(equalToIgnoringCase(\"Flutter\")));\n    assertThat(indexAndDictionaryWithDist.get(\"javascrit\"), hasKey(\"JavaScript\"));\n    assertThat(indexAndDictionaryWithDist.get(\"pyhton\"), hasKey(\"Python\"));\n  }\n\n  @Test\n  public void testFtDictAddDelAndDump() {\n    String dictionary = \"programmingLanguages\";\n\n    Long addResult = exec(commandObjects.ftDictAdd(dictionary, \"Java\", \"Python\", \"JavaScript\", \"Rust\"));\n    assertThat(addResult, equalTo(4L));\n\n    Set<String> dumpResultAfterAdd = exec(commandObjects.ftDictDump(dictionary));\n    assertThat(dumpResultAfterAdd, containsInAnyOrder(\"Java\", \"Python\", \"JavaScript\", \"Rust\"));\n\n    Long delResult = exec(commandObjects.ftDictDel(dictionary, \"Rust\"));\n    assertThat(delResult, equalTo(1L));\n\n    Set<String> dumpResultAfterDel = exec(commandObjects.ftDictDump(dictionary));\n    assertThat(dumpResultAfterDel, containsInAnyOrder(\"Java\", \"Python\", \"JavaScript\"));\n  }\n\n  @Test\n  public void testFtDictAddDelAndDumpWithSampleKeys() {\n    String index = \"index\"; // not used actually, but needed for the command\n\n    String dictionary = \"programmingLanguages\";\n\n    Long addResult = exec(commandObjects.ftDictAddBySampleKey(index, dictionary, \"Java\", \"Python\", \"JavaScript\", \"Rust\"));\n    assertThat(addResult, equalTo(4L));\n\n    Set<String> dumpResultAfterAdd = exec(commandObjects.ftDictDumpBySampleKey(index, dictionary));\n    assertThat(dumpResultAfterAdd, containsInAnyOrder(\"Java\", \"Python\", \"JavaScript\", \"Rust\"));\n\n    Long delResult = exec(commandObjects.ftDictDelBySampleKey(index, dictionary, \"Rust\"));\n    assertThat(delResult, equalTo(1L));\n\n    Set<String> dumpResultAfterDel = exec(commandObjects.ftDictDumpBySampleKey(index, dictionary));\n    assertThat(dumpResultAfterDel, containsInAnyOrder(\"Java\", \"Python\", \"JavaScript\"));\n  }\n\n  @Test\n  public void testFtTags() {\n    String indexName = \"booksIndex\";\n\n    SchemaField[] schema = {\n        TextField.of(\"$.title\"),\n        TagField.of(\"$.genre\").as(\"genre\").separator(',')\n    };\n\n    FTCreateParams createParams = FTCreateParams.createParams()\n        .on(IndexDataType.JSON)\n        .addPrefix(\"books:\");\n\n    String createResult = exec(commandObjects.ftCreate(indexName, createParams, Arrays.asList(schema)));\n    assertThat(createResult, equalTo(\"OK\"));\n\n    JSONObject bookDune = new JSONObject();\n    bookDune.put(\"title\", \"Dune\");\n    bookDune.put(\"genre\", \"Science Fiction, Fantasy, Adventure\");\n\n    String jsonSet = exec(commandObjects.jsonSet(\"books:1000\", Path2.ROOT_PATH, bookDune));\n    assertThat(jsonSet, equalTo(\"OK\"));\n\n    JSONObject bookTheFoundation = new JSONObject();\n    bookTheFoundation.put(\"title\", \"The Foundation\");\n    bookTheFoundation.put(\"genre\", \"Technical, Novel, Essential\");\n\n    String jsonSet2 = exec(commandObjects.jsonSet(\"books:1200\", Path2.ROOT_PATH, bookTheFoundation));\n    assertThat(jsonSet2, equalTo(\"OK\"));\n\n    Set<String> tagVals = exec(commandObjects.ftTagVals(indexName, \"genre\"));\n    assertThat(tagVals, containsInAnyOrder(\n        \"science fiction\", \"fantasy\", \"adventure\", \"technical\", \"novel\", \"essential\"));\n\n    SearchResult searchSimple = exec(commandObjects.ftSearch(indexName, \"Fantasy\"));\n\n    assertThat(searchSimple.getTotalResults(), equalTo(0L));\n    assertThat(searchSimple.getDocuments(), empty());\n\n    SearchResult searchSpecialSyntax = exec(commandObjects.ftSearch(indexName, \"@genre:{ fantasy }\"));\n\n    assertThat(searchSpecialSyntax.getTotalResults(), equalTo(1L));\n    assertThat(searchSpecialSyntax.getDocuments(), hasSize(1));\n\n    Document document = searchSpecialSyntax.getDocuments().get(0);\n    assertThat(document.getId(), equalTo(\"books:1000\"));\n\n    Object documentRoot = document.get(\"$\");\n    assertThat(documentRoot, instanceOf(String.class)); // Unparsed!\n    assertThat(documentRoot, jsonEquals(bookDune));\n  }\n\n  @Test\n  public void testFtInfo() {\n    String indexName = \"booksIndex\";\n\n    SchemaField[] schema = {\n        TextField.of(\"$.title\"),\n        TagField.of(\"$.genre\").as(\"genre\").separator(',')\n    };\n\n    FTCreateParams createParams = FTCreateParams.createParams()\n        .on(IndexDataType.JSON)\n        .addPrefix(\"books:\");\n\n    String createResult = exec(commandObjects.ftCreate(indexName, createParams, Arrays.asList(schema)));\n    assertThat(createResult, equalTo(\"OK\"));\n\n    JSONObject bookDune = new JSONObject();\n    bookDune.put(\"title\", \"Dune\");\n    bookDune.put(\"genre\", \"Science Fiction, Fantasy, Adventure\");\n\n    String jsonSet = exec(commandObjects.jsonSet(\"books:1000\", Path2.ROOT_PATH, bookDune));\n    assertThat(jsonSet, equalTo(\"OK\"));\n\n    JSONObject bookTheFoundation = new JSONObject();\n    bookTheFoundation.put(\"title\", \"The Foundation\");\n    bookTheFoundation.put(\"genre\", \"Technical, Novel, Essential\");\n\n    String jsonSet2 = exec(commandObjects.jsonSet(\"books:1200\", Path2.ROOT_PATH, bookTheFoundation));\n    assertThat(jsonSet2, equalTo(\"OK\"));\n\n    Map<String, Object> infoResult = exec(commandObjects.ftInfo(indexName));\n    assertThat(infoResult, hasEntry(\"index_name\", indexName));\n  }\n\n  @Test\n  public void testFtSugAddAndGet() {\n    String key = \"autocomplete\";\n\n    // Round 1: single suggestion with weight 2.0\n    Long sugAdd1 = exec(commandObjects.ftSugAdd(key, \"Redis\", 2.0));\n    assertThat(sugAdd1, equalTo(1L));\n\n    List<String> suggestionsOneOption = exec(commandObjects.ftSugGet(key, \"Re\"));\n    assertThat(suggestionsOneOption, contains(\"Redis\"));\n\n    List<Tuple> suggestionsWithScoresOneOption = exec(commandObjects.ftSugGetWithScores(key, \"Re\"));\n    assertThat(suggestionsWithScoresOneOption, contains(\n        new Tuple(\"Redis\", 1.0)));\n\n    // Round 2: two suggestions with weights 2.0 and 1.0\n    Long sugAdd2 = exec(commandObjects.ftSugAdd(key, \"Redux\", 1.0));\n    assertThat(sugAdd2, equalTo(2L));\n\n    List<String> suggestionsTwoOptions = exec(commandObjects.ftSugGet(key, \"Re\"));\n    assertThat(suggestionsTwoOptions, contains(\"Redis\", \"Redux\"));\n\n    List<Tuple> suggestionsWithScoresTwoOptions = exec(commandObjects.ftSugGetWithScores(key, \"Re\"));\n    assertThat(suggestionsWithScoresTwoOptions, contains(\n        new Tuple(\"Redis\", 1.0),\n        new Tuple(\"Redux\", 0.5)));\n\n    // Round 2: same two suggestions with weights 2.0 and 3.0\n    Long sugAddIncr = exec(commandObjects.ftSugAddIncr(key, \"Redux\", 2.0));\n    assertThat(sugAddIncr, equalTo(2L));\n\n    List<String> suggestionsAfterScoreChange = exec(commandObjects.ftSugGet(key, \"Re\"));\n    assertThat(suggestionsAfterScoreChange, contains(\"Redux\", \"Redis\"));\n\n    List<Tuple> suggestionsWithScoresAfterChange = exec(commandObjects.ftSugGetWithScores(key, \"Re\"));\n    assertThat(suggestionsWithScoresAfterChange, contains(\n        new Tuple(\"Redux\", 1.5),\n        new Tuple(\"Redis\", 1.0)));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsServerManagementCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.RedisProtocol;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=server\">Server management</a> commands.\n */\npublic class CommandObjectsServerManagementCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsServerManagementCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testSlowLogReset() {\n    String reset = exec(commandObjects.slowlogReset());\n    assertThat(reset, equalTo(\"OK\"));\n  }\n\n  @Test\n  public void testMemoryUsage() {\n    String key = \"key\";\n    int samples = 5;\n\n    exec(commandObjects.set(key, \"value\"));\n\n    CommandObject<Long> memoryUsage = commandObjects.memoryUsage(key);\n    assertThat(exec(memoryUsage), greaterThan(0L));\n\n    CommandObject<Long> memoryUsageWithSamples = commandObjects.memoryUsage(key, samples);\n    assertThat(exec(memoryUsageWithSamples), greaterThan(0L));\n\n    CommandObject<Long> memoryUsageBinary = commandObjects.memoryUsage(key.getBytes());\n    assertThat(exec(memoryUsageBinary), greaterThan(0L));\n\n    CommandObject<Long> memoryUsageBinaryWithSamples = commandObjects.memoryUsage(key.getBytes(), samples);\n    assertThat(exec(memoryUsageBinaryWithSamples), greaterThan(0L));\n  }\n\n  @Test\n  public void testObjectRefcount() {\n    String key = \"refcountKey\";\n\n    exec(commandObjects.set(key, \"value\"));\n\n    Long refcount = exec(commandObjects.objectRefcount(key));\n\n    assertThat(refcount, greaterThanOrEqualTo(1L));\n\n    Long refcountBinary = exec(commandObjects.objectRefcount(key.getBytes()));\n\n    assertThat(refcountBinary, greaterThanOrEqualTo(1L));\n  }\n\n  @Test\n  public void testObjectEncoding() {\n    exec(commandObjects.lpush(\"lst\", \"Hello, Redis!\"));\n\n    String encoding = exec(commandObjects.objectEncoding(\"lst\"));\n\n    assertThat(encoding, containsString(\"list\"));\n\n    byte[] encodingBinary = exec(commandObjects.objectEncoding(\"lst\".getBytes()));\n\n    assertThat(new String(encodingBinary), containsString(\"list\"));\n  }\n\n  @Test\n  public void testObjectIdletime() throws InterruptedException {\n    String key = \"idleTestString\";\n    String value = \"Idle value test\";\n\n    exec(commandObjects.set(key, value));\n\n    // A small delay to simulate idle time\n    Thread.sleep(1000);\n\n    Long idleTime = exec(commandObjects.objectIdletime(key));\n    assertThat(idleTime, greaterThan(0L));\n\n    Long idleTimeBinary = exec(commandObjects.objectIdletime(key.getBytes()));\n    assertThat(idleTimeBinary, greaterThan(0L));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.everyItem;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.hamcrest.Matchers.not;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=set\">Set</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsSetCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testSetCommands() {\n    String key = \"testSet\";\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    String member3 = \"member3\";\n\n    Long sadd = exec(commandObjects.sadd(key, member1, member2, member3));\n    assertThat(sadd, equalTo(3L));\n\n    Set<String> members = exec(commandObjects.smembers(key));\n    assertThat(members, containsInAnyOrder(member1, member2, member3));\n\n    Long srem = exec(commandObjects.srem(key, member1));\n    assertThat(srem, equalTo(1L));\n\n    Set<String> membersAfterSrem = exec(commandObjects.smembers(key));\n    assertThat(membersAfterSrem, containsInAnyOrder(member2, member3));\n  }\n\n  @Test\n  public void testSetCommandsBinary() {\n    byte[] key = \"testSetB\".getBytes();\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n    byte[] member3 = \"member3\".getBytes();\n\n    Long sadd = exec(commandObjects.sadd(key, member1, member2, member3));\n    assertThat(sadd, equalTo(3L));\n\n    Set<byte[]> members = exec(commandObjects.smembers(key));\n    assertThat(members, containsInAnyOrder(member1, member2, member3));\n\n    Long srem = exec(commandObjects.srem(key, member1));\n    assertThat(srem, equalTo(1L));\n\n    Set<byte[]> membersAfterSrem = exec(commandObjects.smembers(key));\n    assertThat(membersAfterSrem, containsInAnyOrder(member2, member3));\n  }\n\n  @Test\n  public void testSpop() {\n    String key = \"testSetPop\";\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    String member3 = \"member3\";\n\n    Long sadd = exec(commandObjects.sadd(key, member1, member2, member3));\n    assertThat(sadd, equalTo(3L));\n\n    String spop = exec(commandObjects.spop(key));\n    assertThat(spop, anyOf(equalTo(member1), equalTo(member2), equalTo(member3)));\n\n    Set<String> spopMultiple = exec(commandObjects.spop(key, 2));\n    assertThat(spopMultiple, hasSize(2));\n    assertThat(spopMultiple, everyItem(anyOf(equalTo(member1), equalTo(member2), equalTo(member3))));\n    assertThat(spopMultiple, not(contains(spop)));\n  }\n\n  @Test\n  public void testSpopBinary() {\n    byte[] bkey = \"testSetPopB\".getBytes();\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n    byte[] member3 = \"member3\".getBytes();\n\n    Long sadd = exec(commandObjects.sadd(bkey, member1, member2, member3));\n    assertThat(sadd, equalTo(3L));\n\n    byte[] spop = exec(commandObjects.spop(bkey));\n    assertThat(spop, anyOf(equalTo(member1), equalTo(member2), equalTo(member3)));\n\n    Set<byte[]> spopMultiple = exec(commandObjects.spop(bkey, 2));\n    assertThat(spopMultiple, hasSize(2));\n    assertThat(spopMultiple, everyItem(anyOf(equalTo(member1), equalTo(member2), equalTo(member3))));\n    assertThat(spopMultiple, not(contains(spop)));\n  }\n\n  @Test\n  public void testSetMembershipCommands() {\n    String key = \"testSetMembership\";\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n\n    exec(commandObjects.sadd(key, member1, member2));\n\n    Long scard = exec(commandObjects.scard(key));\n    assertThat(scard, equalTo(2L));\n\n    Long scardBinary = exec(commandObjects.scard(key.getBytes()));\n    assertThat(scardBinary, equalTo(2L));\n\n    Boolean isMember = exec(commandObjects.sismember(key, member1));\n    assertThat(isMember, equalTo(true));\n\n    Boolean isMemberBinary = exec(commandObjects.sismember(key.getBytes(), member1.getBytes()));\n    assertThat(isMemberBinary, equalTo(true));\n\n    List<Boolean> mIsMember = exec(commandObjects.smismember(key, member1, \"nonMember\"));\n    assertThat(mIsMember, contains(true, false));\n\n    List<Boolean> mIsMemberBinary = exec(commandObjects.smismember(key.getBytes(), member1.getBytes(), \"nonMember\".getBytes()));\n    assertThat(mIsMemberBinary, contains(true, false));\n  }\n\n  @Test\n  public void testSrandmemberCommands() {\n    String key = \"testSetRandomMember\";\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    String member3 = \"member3\";\n\n    exec(commandObjects.sadd(key, member1, member2, member3));\n\n    String randomMember = exec(commandObjects.srandmember(key));\n    assertThat(randomMember, anyOf(equalTo(member1), equalTo(member2), equalTo(member3)));\n\n    byte[] randomMemberBinary = exec(commandObjects.srandmember(key.getBytes()));\n    assertThat(new String(randomMemberBinary), anyOf(equalTo(member1), equalTo(member2), equalTo(member3)));\n\n    List<String> randomMembers = exec(commandObjects.srandmember(key, 2));\n    assertThat(randomMembers, hasSize(2));\n    assertThat(randomMembers, everyItem(anyOf(equalTo(member1), equalTo(member2), equalTo(member3))));\n    assertThat(randomMembers, not(contains(randomMember)));\n\n    List<byte[]> randomMembersBinary = exec(commandObjects.srandmember(key.getBytes(), 2));\n    assertThat(randomMembersBinary, hasSize(2));\n    assertThat(randomMembersBinary, everyItem(anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes()), equalTo(member3.getBytes()))));\n    assertThat(randomMembersBinary, not(contains(randomMemberBinary)));\n  }\n\n  @Test\n  public void testSscanCommands() {\n    String key = \"testSetScan\";\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    String member3 = \"member3\";\n\n    exec(commandObjects.sadd(key, member1, member2, member3));\n\n    ScanParams params = new ScanParams().count(2);\n\n    ScanResult<String> scan = exec(commandObjects.sscan(key, ScanParams.SCAN_POINTER_START, params));\n\n    assertThat(scan.getResult(), hasSize(lessThanOrEqualTo(3)));\n    assertThat(scan.getResult(), everyItem(anyOf(equalTo(member1), equalTo(member2), equalTo(member3))));\n\n    ScanResult<byte[]> scanBinary = exec(commandObjects.sscan(key.getBytes(), ScanParams.SCAN_POINTER_START_BINARY, params));\n\n    assertThat(scanBinary.getResult(), hasSize(lessThanOrEqualTo(3)));\n    assertThat(scanBinary.getResult(), everyItem(anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes()), equalTo(member3.getBytes()))));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSdiff() {\n    String key1 = \"testSet1\";\n    String key2 = \"testSet2\";\n\n    exec(commandObjects.sadd(key1, \"member1\", \"member2\", \"member3\"));\n    exec(commandObjects.sadd(key2, \"member2\", \"member3\", \"member4\"));\n\n    Set<String> diff = exec(commandObjects.sdiff(key1, key2));\n    assertThat(diff, contains(\"member1\"));\n\n    Set<byte[]> diffBinary = exec(commandObjects.sdiff(key1.getBytes(), key2.getBytes()));\n    assertThat(diffBinary, contains(\"member1\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSdiffstore() {\n    String key1 = \"testSet1\";\n    String key2 = \"testSet2\";\n    String dstKey = \"testSetDiff\";\n\n    exec(commandObjects.sadd(key1, \"member1\", \"member2\", \"member3\"));\n    exec(commandObjects.sadd(key2, \"member2\", \"member3\", \"member4\"));\n\n    Long diffStore = exec(commandObjects.sdiffstore(dstKey, key1, key2));\n    assertThat(diffStore, equalTo(1L));\n\n    Set<String> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, contains(\"member1\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSdiffstoreBinary() {\n    byte[] key1 = \"testSet1\".getBytes();\n    byte[] key2 = \"testSet2\".getBytes();\n    byte[] dstKey = \"testSetDiff\".getBytes();\n\n    exec(commandObjects.sadd(key1, \"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes()));\n    exec(commandObjects.sadd(key2, \"member2\".getBytes(), \"member3\".getBytes(), \"member4\".getBytes()));\n\n    Long diffStore = exec(commandObjects.sdiffstore(dstKey, key1, key2));\n    assertThat(diffStore, equalTo(1L));\n\n    Set<byte[]> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, contains(\"member1\".getBytes()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSinterAndSinterCard() {\n    String key1 = \"testSetInter1\";\n    String key2 = \"testSetInter2\";\n\n    exec(commandObjects.sadd(key1, \"member1\", \"member2\", \"member3\"));\n    exec(commandObjects.sadd(key2, \"member2\", \"member3\", \"member4\"));\n\n    Set<String> inter = exec(commandObjects.sinter(key1, key2));\n    assertThat(inter, containsInAnyOrder(\"member2\", \"member3\"));\n\n    Set<byte[]> interBinary = exec(commandObjects.sinter(key1.getBytes(), key2.getBytes()));\n    assertThat(interBinary, containsInAnyOrder(\"member2\".getBytes(), \"member3\".getBytes()));\n\n    Long interCard = exec(commandObjects.sintercard(key1, key2));\n    assertThat(interCard, equalTo(2L));\n\n    Long interCardBinary = exec(commandObjects.sintercard(key1.getBytes(), key2.getBytes()));\n    assertThat(interCardBinary, equalTo(2L));\n\n    Long interCardLimited = exec(commandObjects.sintercard(1, key1, key2));\n    assertThat(interCardLimited, equalTo(1L));\n\n    Long interCardLimitedBinary = exec(commandObjects.sintercard(1, key1.getBytes(), key2.getBytes()));\n    assertThat(interCardLimitedBinary, equalTo(1L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSinterstore() {\n    String key1 = \"testSetInter1\";\n    String key2 = \"testSetInter2\";\n    String dstKey = \"testSetInterResult\";\n\n    exec(commandObjects.sadd(key1, \"member1\", \"member2\", \"member3\"));\n    exec(commandObjects.sadd(key2, \"member2\", \"member3\", \"member4\"));\n\n    Long interStore = exec(commandObjects.sinterstore(dstKey, key1, key2));\n    assertThat(interStore, equalTo(2L));\n\n    Set<String> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, containsInAnyOrder(\"member2\", \"member3\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSinterstoreBinary() {\n    byte[] key1 = \"testSetInter1B\".getBytes();\n    byte[] key2 = \"testSetInter2B\".getBytes();\n    byte[] dstKey = \"testSetInterResultB\".getBytes();\n\n    exec(commandObjects.sadd(key1, \"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes()));\n    exec(commandObjects.sadd(key2, \"member2\".getBytes(), \"member3\".getBytes(), \"member4\".getBytes()));\n\n    Long interStore = exec(commandObjects.sinterstore(dstKey, key1, key2));\n    assertThat(interStore, equalTo(2L));\n\n    Set<byte[]> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, containsInAnyOrder(\"member2\".getBytes(), \"member3\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSunion() {\n    String key1 = \"testSetUnion1\";\n    String key2 = \"testSetUnion2\";\n\n    exec(commandObjects.sadd(key1, \"member1\", \"member2\", \"member3\"));\n    exec(commandObjects.sadd(key2, \"member3\", \"member4\", \"member5\"));\n\n    Set<String> unionResult = exec(commandObjects.sunion(key1, key2));\n\n    assertThat(unionResult, containsInAnyOrder(\n        \"member1\", \"member2\", \"member3\", \"member4\", \"member5\"));\n\n    Set<byte[]> bunionResult = exec(commandObjects.sunion(key1.getBytes(), key2.getBytes()));\n\n    assertThat(bunionResult, containsInAnyOrder(\n        \"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes(), \"member4\".getBytes(), \"member5\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSunionstore() {\n    String key1 = \"testSetUnion1\";\n    String key2 = \"testSetUnion2\";\n    String dstKey = \"testSetUnionResult\";\n\n    exec(commandObjects.sadd(key1, \"member1\", \"member2\", \"member3\"));\n    exec(commandObjects.sadd(key2, \"member3\", \"member4\", \"member5\"));\n\n    Long unionStore = exec(commandObjects.sunionstore(dstKey, key1, key2));\n\n    assertThat(unionStore, equalTo(5L));\n\n    Set<String> dstSet = exec(commandObjects.smembers(dstKey));\n\n    assertThat(dstSet, containsInAnyOrder(\n        \"member1\", \"member2\", \"member3\", \"member4\", \"member5\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSunionstoreBinary() {\n    byte[] key1 = \"testSetUnion1\".getBytes();\n    byte[] key2 = \"testSetUnion2\".getBytes();\n    byte[] dstKey = \"testSetUnionResult\".getBytes();\n\n    exec(commandObjects.sadd(key1, \"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes()));\n    exec(commandObjects.sadd(key2, \"member3\".getBytes(), \"member4\".getBytes(), \"member5\".getBytes()));\n\n    Long unionStore = exec(commandObjects.sunionstore(dstKey, key1, key2));\n    assertThat(unionStore, equalTo(5L));\n\n    Set<byte[]> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, containsInAnyOrder(\n        \"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes(), \"member4\".getBytes(), \"member5\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSmove() {\n    String srcKey = \"testSetSrc\";\n    String dstKey = \"testSetDst\";\n    String member = \"memberToMove\";\n\n    exec(commandObjects.sadd(srcKey, member));\n\n    Long smove = exec(commandObjects.smove(srcKey, dstKey, member));\n    assertThat(smove, equalTo(1L));\n\n    Set<String> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, contains(member));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSmoveBinary() {\n    byte[] srcKey = \"testSetSrc\".getBytes();\n    byte[] dstKey = \"testSetDst\".getBytes();\n    byte[] member = \"memberToMove\".getBytes();\n\n    exec(commandObjects.sadd(srcKey, member));\n\n    Long smove = exec(commandObjects.smove(srcKey, dstKey, member));\n    assertThat(smove, equalTo(1L));\n\n    Set<byte[]> dstSet = exec(commandObjects.smembers(dstKey));\n    assertThat(dstSet, contains(member));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsSortedSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.either;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.notNullValue;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\nimport redis.clients.jedis.params.ZParams;\nimport redis.clients.jedis.params.ZRangeParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=sorted-set\">Sorted set</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsSortedSetCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsSortedSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testZaddAndZcard() {\n    String key = \"zset\";\n    String member = \"member1\";\n    double score = 1.0;\n\n    Map<String, Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member2\", 2.0);\n    scoreMembers.put(\"member3\", 3.0);\n\n    ZAddParams params = ZAddParams.zAddParams().nx();\n\n    Long zadd = exec(commandObjects.zadd(key, score, member));\n    assertThat(zadd, equalTo(1L));\n\n    Long zaddParams = exec(commandObjects.zadd(key, score, member, params));\n    assertThat(zaddParams, equalTo(0L));\n\n    Long zaddMultiple = exec(commandObjects.zadd(key, scoreMembers));\n    assertThat(zaddMultiple, equalTo(2L));\n\n    Long zaddMultipleParams = exec(commandObjects.zadd(key, scoreMembers, params));\n    assertThat(zaddMultipleParams, equalTo(0L));\n\n    Long zcard = exec(commandObjects.zcard(key));\n    assertThat(zcard, equalTo(3L));\n  }\n\n  @Test\n  public void testZaddAndZcardBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    double score = 1.0;\n\n    Map<byte[], Double> binaryScoreMembers = new HashMap<>();\n    binaryScoreMembers.put(\"member2\".getBytes(), 2.0);\n    binaryScoreMembers.put(\"member3\".getBytes(), 3.0);\n\n    ZAddParams params = ZAddParams.zAddParams().nx();\n\n    Long zadd = exec(commandObjects.zadd(key, score, member));\n    assertThat(zadd, equalTo(1L));\n\n    Long zaddParams = exec(commandObjects.zadd(key, score, member, params));\n    assertThat(zaddParams, equalTo(0L));\n\n    Long zaddMultiple = exec(commandObjects.zadd(key, binaryScoreMembers));\n    assertThat(zaddMultiple, equalTo(2L));\n\n    Long zaddMultipleParams = exec(commandObjects.zadd(key, binaryScoreMembers, params));\n    assertThat(zaddMultipleParams, equalTo(0L));\n\n    Long zcard = exec(commandObjects.zcard(key));\n    assertThat(zcard, equalTo(3L));\n  }\n\n  @Test\n  public void testZIncrAndZincrBy() {\n    String key = \"zset\";\n    String member = \"member\";\n    double initialScore = 1.0;\n    double increment = 2.0;\n\n    ZAddParams zAddParams = ZAddParams.zAddParams().xx();\n\n    ZIncrByParams zIncrByParams = ZIncrByParams.zIncrByParams().xx();\n\n    Long zadd = exec(commandObjects.zadd(key, initialScore, member));\n    assertThat(zadd, equalTo(1L));\n\n    Double zaddIncr = exec(commandObjects.zaddIncr(key, increment, member, zAddParams));\n    assertThat(zaddIncr, closeTo(initialScore + increment, 0.001));\n\n    Double zscoreAfterZaddincr = exec(commandObjects.zscore(key, member));\n    assertThat(zscoreAfterZaddincr, closeTo(initialScore + increment, 0.001));\n\n    Double zincrBy = exec(commandObjects.zincrby(key, increment, member));\n    assertThat(zincrBy, closeTo(initialScore + increment * 2, 0.001));\n\n    Double zscoreAfterZincrBy = exec(commandObjects.zscore(key, member));\n    assertThat(zscoreAfterZincrBy, closeTo(initialScore + increment * 2, 0.001));\n\n    Double zincrByParams = exec(commandObjects.zincrby(key, increment, member, zIncrByParams));\n    assertThat(zincrByParams, closeTo(initialScore + increment * 3, 0.001));\n\n    Double zscoreAfterZincrByParams = exec(commandObjects.zscore(key, member));\n    assertThat(zscoreAfterZincrByParams, closeTo(initialScore + increment * 3, 0.001));\n  }\n\n  @Test\n  public void testZIncrAndZincrByBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member\".getBytes();\n    double initialScore = 1.0;\n    double increment = 2.0;\n\n    ZAddParams zAddParams = ZAddParams.zAddParams().xx();\n\n    ZIncrByParams zIncrByParams = ZIncrByParams.zIncrByParams().xx();\n\n    Long zadd = exec(commandObjects.zadd(key, initialScore, member));\n    assertThat(zadd, equalTo(1L));\n\n    Double zaddIncr = exec(commandObjects.zaddIncr(key, increment, member, zAddParams));\n    assertThat(zaddIncr, closeTo(initialScore + increment, 0.001));\n\n    Double zscoreAfterZaddIncr = exec(commandObjects.zscore(key, member));\n    assertThat(zscoreAfterZaddIncr, closeTo(initialScore + increment, 0.001));\n\n    Double zincrBy = exec(commandObjects.zincrby(key, increment, member));\n    assertThat(zincrBy, closeTo(initialScore + increment * 2, 0.001));\n\n    Double zscoreAfterZincrBy = exec(commandObjects.zscore(key, member));\n    assertThat(zscoreAfterZincrBy, closeTo(initialScore + increment * 2, 0.001));\n\n    Double zincrByParams = exec(commandObjects.zincrby(key, increment, member, zIncrByParams));\n    assertThat(zincrByParams, closeTo(initialScore + increment * 3, 0.001));\n\n    Double zscoreAfterZincrByParams = exec(commandObjects.zscore(key, member));\n    assertThat(zscoreAfterZincrByParams, closeTo(initialScore + increment * 3, 0.001));\n  }\n\n  @Test\n  public void testZrem() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<String> zrangeBefore = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(zrangeBefore, containsInAnyOrder(member1, member2));\n\n    Long removedCount = exec(commandObjects.zrem(key, member1));\n    assertThat(removedCount, equalTo(1L));\n\n    List<String> zrangeAfter = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(zrangeAfter, containsInAnyOrder(member2));\n  }\n\n  @Test\n  public void testZremBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member1 = \"one\".getBytes();\n    byte[] member2 = \"two\".getBytes();\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<byte[]> zrangeBefore = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(zrangeBefore, containsInAnyOrder(member1, member2));\n\n    Long removedCount = exec(commandObjects.zrem(key, member1));\n    assertThat(removedCount, equalTo(1L));\n\n    List<byte[]> zrangeAfter = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(zrangeAfter, containsInAnyOrder(member2));\n  }\n\n  @Test\n  public void testZrandmember() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    String randomMember = exec(commandObjects.zrandmember(key));\n\n    assertThat(randomMember, anyOf(equalTo(member1), equalTo(member2)));\n\n    byte[] randomMemberBinary = exec(commandObjects.zrandmember(key.getBytes()));\n\n    assertThat(randomMemberBinary, anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes())));\n\n    List<String> randomMembers = exec(commandObjects.zrandmember(key, 2));\n\n    assertThat(randomMembers, containsInAnyOrder(member1, member2));\n\n    List<byte[]> randomMembersBinary = exec(commandObjects.zrandmember(key.getBytes(), 2));\n\n    assertThat(randomMembersBinary.get(0), anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes())));\n    assertThat(randomMembersBinary.get(1), anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes())));\n  }\n\n  @Test\n  public void testZrandmemberWithScores() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<Tuple> randomMembersWithScores = exec(commandObjects.zrandmemberWithScores(key, 2));\n\n    assertThat(randomMembersWithScores, hasSize(2));\n    assertThat(randomMembersWithScores, containsInAnyOrder(new Tuple(member1, score1), new Tuple(member2, score2)));\n\n    List<Tuple> randomMembersWithScoresBinary = exec(commandObjects.zrandmemberWithScores(key.getBytes(), 2));\n\n    assertThat(randomMembersWithScoresBinary, hasSize(2));\n\n    assertThat(randomMembersWithScoresBinary.get(0).getBinaryElement(), anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes())));\n    assertThat(randomMembersWithScoresBinary.get(0).getScore(), anyOf(equalTo(score1), equalTo(score2)));\n\n    assertThat(randomMembersWithScoresBinary.get(1).getBinaryElement(), anyOf(equalTo(member1.getBytes()), equalTo(member2.getBytes())));\n    assertThat(randomMembersWithScoresBinary.get(1).getScore(), anyOf(equalTo(score1), equalTo(score2)));\n  }\n\n  @Test\n  public void testZscore() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    double score1 = 1.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n\n    Double score = exec(commandObjects.zscore(key, member1));\n    assertThat(score, equalTo(score1));\n\n    Double scoreBinary = exec(commandObjects.zscore(key.getBytes(), member1.getBytes()));\n    assertThat(scoreBinary, equalTo(score1));\n  }\n\n  @Test\n  public void testZmscore() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<Double> scores = exec(commandObjects.zmscore(key, member1, member2));\n    assertThat(scores, contains(score1, score2));\n\n    List<Double> scoresBinary = exec(commandObjects.zmscore(key.getBytes(), member1.getBytes(), member2.getBytes()));\n    assertThat(scoresBinary, contains(score1, score2));\n  }\n\n  @Test\n  public void testZrankAndZrevrank() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    Long rankMember1 = exec(commandObjects.zrank(key, member1));\n    assertThat(rankMember1, equalTo(0L));\n\n    Long rankMember2 = exec(commandObjects.zrank(key, member2));\n    assertThat(rankMember2, equalTo(1L));\n\n    Long rankMember1Binary = exec(commandObjects.zrank(key.getBytes(), member1.getBytes()));\n    assertThat(rankMember1Binary, equalTo(0L));\n\n    Long rankMember2Binary = exec(commandObjects.zrank(key.getBytes(), member2.getBytes()));\n    assertThat(rankMember2Binary, equalTo(1L));\n\n    Long revRankMember1 = exec(commandObjects.zrevrank(key, member1));\n    assertThat(revRankMember1, equalTo(1L));\n\n    Long revRankMember2 = exec(commandObjects.zrevrank(key, member2));\n    assertThat(revRankMember2, equalTo(0L));\n\n    Long revRankMember1Binary = exec(commandObjects.zrevrank(key.getBytes(), member1.getBytes()));\n    assertThat(revRankMember1Binary, equalTo(1L));\n\n    Long revRankMember2Binary = exec(commandObjects.zrevrank(key.getBytes(), member2.getBytes()));\n    assertThat(revRankMember2Binary, equalTo(0L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\")\n  public void testZrankWithScoreAndZrevrankWithScore() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    KeyValue<Long, Double> rankWithScoreMember1 = exec(commandObjects.zrankWithScore(key, member1));\n    assertThat(rankWithScoreMember1.getKey(), equalTo(0L));\n    assertThat(rankWithScoreMember1.getValue(), equalTo(score1));\n\n    KeyValue<Long, Double> rankWithScoreMember2 = exec(commandObjects.zrankWithScore(key, member2));\n    assertThat(rankWithScoreMember2.getKey(), equalTo(1L));\n    assertThat(rankWithScoreMember2.getValue(), equalTo(score2));\n\n    KeyValue<Long, Double> rankWithScoreMember1Binary = exec(commandObjects.zrankWithScore(key.getBytes(), member1.getBytes()));\n    assertThat(rankWithScoreMember1Binary.getKey(), equalTo(0L));\n    assertThat(rankWithScoreMember1Binary.getValue(), equalTo(score1));\n\n    KeyValue<Long, Double> rankWithScoreMember2Binary = exec(commandObjects.zrankWithScore(key.getBytes(), member2.getBytes()));\n    assertThat(rankWithScoreMember2Binary.getKey(), equalTo(1L));\n    assertThat(rankWithScoreMember2Binary.getValue(), equalTo(score2));\n\n    KeyValue<Long, Double> revRankWithScoreMember1 = exec(commandObjects.zrevrankWithScore(key, member1));\n    assertThat(revRankWithScoreMember1.getKey(), equalTo(1L));\n    assertThat(revRankWithScoreMember1.getValue(), equalTo(score1));\n\n    KeyValue<Long, Double> revRankWithScoreMember2 = exec(commandObjects.zrevrankWithScore(key, member2));\n    assertThat(revRankWithScoreMember2.getKey(), equalTo(0L));\n    assertThat(revRankWithScoreMember2.getValue(), equalTo(score2));\n\n    KeyValue<Long, Double> revRankWithScoreMember1Binary = exec(commandObjects.zrevrankWithScore(key.getBytes(), member1.getBytes()));\n    assertThat(revRankWithScoreMember1Binary.getKey(), equalTo(1L));\n    assertThat(revRankWithScoreMember1Binary.getValue(), equalTo(score1));\n\n    KeyValue<Long, Double> revRankWithScoreMember2Binary = exec(commandObjects.zrevrankWithScore(key.getBytes(), member2.getBytes()));\n    assertThat(revRankWithScoreMember2Binary.getKey(), equalTo(0L));\n    assertThat(revRankWithScoreMember2Binary.getValue(), equalTo(score2));\n  }\n\n  @Test\n  public void testZpopmax() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    Tuple poppedMax = exec(commandObjects.zpopmax(key));\n    assertThat(poppedMax.getElement(), equalTo(member2));\n    assertThat(poppedMax.getScore(), equalTo(score2));\n\n    List<Tuple> poppedMaxMultiple = exec(commandObjects.zpopmax(key, 2));\n    assertThat(poppedMaxMultiple, hasSize(1)); // Since we already popped the max, only one remains\n    assertThat(poppedMaxMultiple.get(0).getElement(), equalTo(member1));\n    assertThat(poppedMaxMultiple.get(0).getScore(), equalTo(score1));\n  }\n\n  @Test\n  public void testZpopmaxBinary() {\n    byte[] key = \"zset\".getBytes();\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1.getBytes()));\n    exec(commandObjects.zadd(key, score2, member2.getBytes()));\n\n    Tuple poppedMaxBinary = exec(commandObjects.zpopmax(key));\n    assertThat(poppedMaxBinary.getBinaryElement(), equalTo(member2.getBytes()));\n    assertThat(poppedMaxBinary.getScore(), equalTo(score2));\n\n    List<Tuple> poppedMaxMultipleBinary = exec(commandObjects.zpopmax(key, 2));\n    assertThat(poppedMaxMultipleBinary, hasSize(1)); // Since we already popped the max, only one remains\n    assertThat(poppedMaxMultipleBinary.get(0).getBinaryElement(), equalTo(member1.getBytes()));\n    assertThat(poppedMaxMultipleBinary.get(0).getScore(), equalTo(score1));\n  }\n\n  @Test\n  public void testZpopmin() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    Tuple poppedMin = exec(commandObjects.zpopmin(key));\n    assertThat(poppedMin.getElement(), equalTo(member1));\n    assertThat(poppedMin.getScore(), equalTo(score1));\n\n    List<Tuple> poppedMinMultiple = exec(commandObjects.zpopmin(key, 2));\n    assertThat(poppedMinMultiple, hasSize(1)); // Since we already popped the min, only one remains\n    assertThat(poppedMinMultiple.get(0).getElement(), equalTo(member2));\n    assertThat(poppedMinMultiple.get(0).getScore(), equalTo(score2));\n  }\n\n  @Test\n  public void testZpopminBinary() {\n    byte[] key = \"zset\".getBytes();\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1.getBytes()));\n    exec(commandObjects.zadd(key, score2, member2.getBytes()));\n\n    Tuple poppedMinBinary = exec(commandObjects.zpopmin(key));\n    assertThat(poppedMinBinary.getBinaryElement(), equalTo(member1.getBytes()));\n    assertThat(poppedMinBinary.getScore(), equalTo(score1));\n\n    List<Tuple> poppedMinMultipleBinary = exec(commandObjects.zpopmin(key, 2));\n    assertThat(poppedMinMultipleBinary, hasSize(1)); // Since we already popped the min, only one remains\n    assertThat(poppedMinMultipleBinary.get(0).getBinaryElement(), equalTo(member2.getBytes()));\n    assertThat(poppedMinMultipleBinary.get(0).getScore(), equalTo(score2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBzpopmaxAndBzpopmin() {\n    String key1 = \"zset1\";\n    String key2 = \"zset2\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double timeout = 2.0; // 2 seconds timeout for blocking operations\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<String, Tuple> poppedMax = exec(commandObjects.bzpopmax(timeout, key1, key2));\n    assertThat(poppedMax.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(poppedMax.getValue().getScore(), anyOf(equalTo(score1), equalTo(score2)));\n\n    KeyValue<String, Tuple> poppedMin = exec(commandObjects.bzpopmin(timeout, key1, key2));\n    assertThat(poppedMin.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(poppedMin.getValue().getScore(), anyOf(equalTo(score1), equalTo(score2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBzpopmaxAndBzpopminBinary() {\n    byte[] key1 = \"zset1\".getBytes();\n    byte[] key2 = \"zset2\".getBytes();\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double timeout = 2.0; // 2 seconds timeout for blocking operations\n\n    exec(commandObjects.zadd(key1, score1, member1.getBytes()));\n    exec(commandObjects.zadd(key2, score2, member2.getBytes()));\n\n    KeyValue<byte[], Tuple> poppedMaxBinary = exec(commandObjects.bzpopmax(timeout, key1, key2));\n    assertThat(poppedMaxBinary.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(poppedMaxBinary.getValue().getScore(), anyOf(equalTo(score1), equalTo(score2)));\n\n    KeyValue<byte[], Tuple> poppedMinBinary = exec(commandObjects.bzpopmin(timeout, key1, key2));\n    assertThat(poppedMinBinary.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(poppedMinBinary.getValue().getScore(), anyOf(equalTo(score1), equalTo(score2)));\n  }\n\n  @Test\n  public void testZcount() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    String member3 = \"three\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double score3 = 3.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n    exec(commandObjects.zadd(key, score3, member3));\n\n    Long countInNumericRange = exec(commandObjects.zcount(key, 1.5, 2.5));\n    assertThat(countInNumericRange, equalTo(1L));\n\n    Long countInStringRange = exec(commandObjects.zcount(key, \"(1\", \"3\"));\n    assertThat(countInStringRange, equalTo(2L));\n\n    Long countInNumericRangeBinary = exec(commandObjects.zcount(key.getBytes(), 1.5, 2.5));\n    assertThat(countInNumericRangeBinary, equalTo(1L));\n\n    Long countInBinaryRange = exec(commandObjects.zcount(key.getBytes(), \"(1\".getBytes(), \"3\".getBytes()));\n    assertThat(countInBinaryRange, equalTo(2L));\n  }\n\n  @Test\n  public void testZrange() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<String> range = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(range, contains(member1, member2));\n\n    List<byte[]> rangeBinary = exec(commandObjects.zrange(key.getBytes(), 0, -1));\n    assertThat(rangeBinary, contains(member1.getBytes(), member2.getBytes()));\n\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(0, -1);\n\n    List<String> rangeWithParams = exec(commandObjects.zrange(key, zRangeParams));\n    assertThat(rangeWithParams, hasItems(member1, member2));\n\n    List<byte[]> rangeWithParamsBinary = exec(commandObjects.zrange(key.getBytes(), zRangeParams));\n    assertThat(rangeWithParamsBinary.get(0), equalTo(member1.getBytes()));\n    assertThat(rangeWithParamsBinary.get(1), equalTo(member2.getBytes()));\n  }\n\n  @Test\n  public void testZrangeWithScores() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<Tuple> rangeWithScores = exec(commandObjects.zrangeWithScores(key, 0, -1));\n\n    assertThat(rangeWithScores, hasSize(2));\n    assertThat(rangeWithScores.get(0).getElement(), equalTo(member1));\n    assertThat(rangeWithScores.get(0).getScore(), equalTo(score1));\n    assertThat(rangeWithScores.get(1).getElement(), equalTo(member2));\n    assertThat(rangeWithScores.get(1).getScore(), equalTo(score2));\n\n    List<Tuple> rangeWithScoresBinary = exec(commandObjects.zrangeWithScores(key.getBytes(), 0, -1));\n\n    assertThat(rangeWithScoresBinary, hasSize(2));\n    assertThat(rangeWithScoresBinary.get(0).getBinaryElement(), equalTo(member1.getBytes()));\n    assertThat(rangeWithScoresBinary.get(0).getScore(), equalTo(score1));\n    assertThat(rangeWithScoresBinary.get(1).getBinaryElement(), equalTo(member2.getBytes()));\n    assertThat(rangeWithScoresBinary.get(1).getScore(), equalTo(score2));\n\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(0, -1);\n\n    List<Tuple> rangeWithScoresParams = exec(commandObjects.zrangeWithScores(key, zRangeParams));\n\n    assertThat(rangeWithScoresParams, hasSize(2));\n    assertThat(rangeWithScoresParams.get(0).getElement(), equalTo(member1));\n    assertThat(rangeWithScoresParams.get(0).getScore(), equalTo(score1));\n    assertThat(rangeWithScoresParams.get(1).getElement(), equalTo(member2));\n    assertThat(rangeWithScoresParams.get(1).getScore(), equalTo(score2));\n\n    List<Tuple> rangeWithScoresParamsBinary = exec(commandObjects.zrangeWithScores(key.getBytes(), zRangeParams));\n\n    assertThat(rangeWithScoresParamsBinary, hasSize(2));\n    assertThat(rangeWithScoresParamsBinary.get(0).getBinaryElement(), equalTo(member1.getBytes()));\n    assertThat(rangeWithScoresParamsBinary.get(0).getScore(), equalTo(score1));\n    assertThat(rangeWithScoresParamsBinary.get(1).getBinaryElement(), equalTo(member2.getBytes()));\n    assertThat(rangeWithScoresParamsBinary.get(1).getScore(), equalTo(score2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZrangestore() {\n    String srcKey = \"zsetSrc\";\n    String destKey = \"zsetDest\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(srcKey, score1, member1));\n    exec(commandObjects.zadd(srcKey, score2, member2));\n\n    ZRangeParams zRangeParams = ZRangeParams.zrangeByScoreParams(score1, score2);\n\n    Long zrangeStore = exec(commandObjects.zrangestore(destKey, srcKey, zRangeParams));\n\n    assertThat(zrangeStore, equalTo(2L));\n\n    List<Tuple> zrangeWithScores = exec(commandObjects.zrangeWithScores(destKey, 0, -1));\n\n    assertThat(zrangeWithScores, hasSize(2));\n    assertThat(zrangeWithScores.get(0).getElement(), equalTo(member1));\n    assertThat(zrangeWithScores.get(1).getElement(), equalTo(member2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZrangestoreBinary() {\n    byte[] srcKey = \"zsetSrcB\".getBytes();\n    byte[] destKey = \"zsetDestB\".getBytes();\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(srcKey, score1, member1.getBytes()));\n    exec(commandObjects.zadd(srcKey, score2, member2.getBytes()));\n\n    ZRangeParams zRangeParams = ZRangeParams.zrangeByScoreParams(score1, score2);\n\n    Long zrangeStore = exec(commandObjects.zrangestore(destKey, srcKey, zRangeParams));\n\n    assertThat(zrangeStore, equalTo(2L));\n\n    List<Tuple> zrangeWithScores = exec(commandObjects.zrangeWithScores(destKey, 0, -1));\n\n    assertThat(zrangeWithScores, hasSize(2));\n    assertThat(zrangeWithScores.get(0).getBinaryElement(), equalTo(member1.getBytes()));\n    assertThat(zrangeWithScores.get(1).getBinaryElement(), equalTo(member2.getBytes()));\n  }\n\n  @Test\n  public void testZrevrange() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<String> revRange = exec(commandObjects.zrevrange(key, 0, -1));\n    assertThat(revRange, contains(member2, member1));\n\n    List<byte[]> revRangeBinary = exec(commandObjects.zrevrange(key.getBytes(), 0, -1));\n    assertThat(revRangeBinary, contains(member2.getBytes(), member1.getBytes()));\n  }\n\n  @Test\n  public void testZrevrangeWithScores() {\n    String key = \"zset\";\n    String member1 = \"one\";\n    String member2 = \"two\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    List<Tuple> revRangeWithScores = exec(commandObjects.zrevrangeWithScores(key, 0, -1));\n\n    assertThat(revRangeWithScores, hasSize(2));\n    assertThat(revRangeWithScores.get(0).getElement(), equalTo(member2));\n    assertThat(revRangeWithScores.get(0).getScore(), equalTo(score2));\n    assertThat(revRangeWithScores.get(1).getElement(), equalTo(member1));\n    assertThat(revRangeWithScores.get(1).getScore(), equalTo(score1));\n\n    List<Tuple> revRangeWithScoresBinary = exec(commandObjects.zrevrangeWithScores(key.getBytes(), 0, -1));\n\n    assertThat(revRangeWithScoresBinary, hasSize(2));\n    assertThat(revRangeWithScoresBinary.get(0).getBinaryElement(), equalTo(member2.getBytes()));\n    assertThat(revRangeWithScoresBinary.get(0).getScore(), equalTo(score2));\n    assertThat(revRangeWithScoresBinary.get(1).getBinaryElement(), equalTo(member1.getBytes()));\n    assertThat(revRangeWithScoresBinary.get(1).getScore(), equalTo(score1));\n  }\n\n  @Test\n  public void testZrangeByScore() {\n    String key = \"zset\";\n    double min = 1.0;\n    double max = 10.0;\n    String smin = \"1\";\n    String smax = \"10\";\n    byte[] bmin = \"1.0\".getBytes();\n    byte[] bmax = \"10.0\".getBytes();\n    int offset = 0;\n    int count = 1;\n\n    exec(commandObjects.zadd(key, 1, \"one\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n    exec(commandObjects.zadd(key, 3, \"three\"));\n    exec(commandObjects.zadd(key, 13, \"four\"));\n\n    List<String> numericRange = exec(commandObjects.zrangeByScore(key, min, max));\n    assertThat(numericRange, contains(\"one\", \"two\", \"three\"));\n\n    List<String> stringRange = exec(commandObjects.zrangeByScore(key, smin, smax));\n    assertThat(stringRange, contains(\"one\", \"two\", \"three\"));\n\n    List<String> numericRangeOffsetCount = exec(commandObjects.zrangeByScore(key, min, max, offset, count));\n    assertThat(numericRangeOffsetCount, contains(\"one\"));\n\n    List<String> stringRangeOffsetCount = exec(commandObjects.zrangeByScore(key, smin, smax, offset, count));\n    assertThat(stringRangeOffsetCount, contains(\"one\"));\n\n    List<byte[]> numericRangeBinary = exec(commandObjects.zrangeByScore(key.getBytes(), min, max));\n    assertThat(numericRangeBinary.get(0), equalTo(\"one\".getBytes()));\n    assertThat(numericRangeBinary.get(1), equalTo(\"two\".getBytes()));\n    assertThat(numericRangeBinary.get(2), equalTo(\"three\".getBytes()));\n\n    List<byte[]> stringRangeBinary = exec(commandObjects.zrangeByScore(key.getBytes(), bmin, bmax));\n    assertThat(stringRangeBinary, contains(\"one\".getBytes(), \"two\".getBytes(), \"three\".getBytes()));\n\n    List<byte[]> numericRangeOffsetCountBinary = exec(commandObjects.zrangeByScore(key.getBytes(), min, max, offset, count));\n    assertThat(numericRangeOffsetCountBinary.get(0), equalTo(\"one\".getBytes()));\n\n    List<byte[]> stringRangeOffsetCountBinary = exec(commandObjects.zrangeByScore(key.getBytes(), bmin, bmax, offset, count));\n    assertThat(stringRangeOffsetCountBinary, contains(\"one\".getBytes()));\n  }\n\n  @Test\n  public void testZrevrangeByScore() {\n    String key = \"zset\";\n    double max = 10.0;\n    double min = 1.0;\n    String smax = \"10\";\n    String smin = \"1\";\n    byte[] bmax = \"10.0\".getBytes();\n    byte[] bmin = \"1.0\".getBytes();\n    int offset = 0;\n    int count = 1;\n\n    exec(commandObjects.zadd(key, 13, \"four\"));\n    exec(commandObjects.zadd(key, 3, \"three\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n    exec(commandObjects.zadd(key, 1, \"one\"));\n\n    List<String> numericRevrange = exec(commandObjects.zrevrangeByScore(key, max, min));\n    assertThat(numericRevrange, contains(\"three\", \"two\", \"one\"));\n\n    List<String> stringRevrange = exec(commandObjects.zrevrangeByScore(key, smax, smin));\n    assertThat(stringRevrange, contains(\"three\", \"two\", \"one\"));\n\n    List<String> numericRevrangeOffsetCount = exec(commandObjects.zrevrangeByScore(key, max, min, offset, count));\n    assertThat(numericRevrangeOffsetCount, contains(\"three\"));\n\n    List<String> stringRevrangeOffsetCount = exec(commandObjects.zrevrangeByScore(key, smax, smin, offset, count));\n    assertThat(stringRevrangeOffsetCount, contains(\"three\"));\n\n    List<byte[]> numericRevrangeBinary = exec(commandObjects.zrevrangeByScore(key.getBytes(), max, min));\n    assertThat(numericRevrangeBinary, contains(\"three\".getBytes(), \"two\".getBytes(), \"one\".getBytes()));\n\n    List<byte[]> stringRevrangeBinary = exec(commandObjects.zrevrangeByScore(key.getBytes(), bmax, bmin));\n    assertThat(stringRevrangeBinary, contains(\"three\".getBytes(), \"two\".getBytes(), \"one\".getBytes()));\n\n    List<byte[]> numericRevrangeOffsetCountBinary = exec(commandObjects.zrevrangeByScore(key.getBytes(), max, min, offset, count));\n    assertThat(numericRevrangeOffsetCountBinary.get(0), equalTo(\"three\".getBytes()));\n\n    List<byte[]> stringRevrangeOffsetCountBinary = exec(commandObjects.zrevrangeByScore(key.getBytes(), bmax, bmin, offset, count));\n    assertThat(stringRevrangeOffsetCountBinary, contains(\"three\".getBytes()));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScores() {\n    String key = \"zset\";\n    double min = 1.0;\n    double max = 10.0;\n    String smin = \"1\";\n    String smax = \"10\";\n    byte[] bmin = \"1.0\".getBytes();\n    byte[] bmax = \"10.0\".getBytes();\n    int offset = 0;\n    int count = 2;\n\n    exec(commandObjects.zadd(key, 1, \"one\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n    exec(commandObjects.zadd(key, 3, \"three\"));\n\n    List<Tuple> numericRange = exec(commandObjects.zrangeByScoreWithScores(key, min, max));\n    assertThat(numericRange, contains(\n        new Tuple(\"one\", 1d),\n        new Tuple(\"two\", 2d),\n        new Tuple(\"three\", 3d)));\n\n    List<Tuple> stringRange = exec(commandObjects.zrangeByScoreWithScores(key, smin, smax));\n    assertThat(stringRange, contains(\n        new Tuple(\"one\", 1d),\n        new Tuple(\"two\", 2d),\n        new Tuple(\"three\", 3d)));\n\n    List<Tuple> numericRangeOffsetCount = exec(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count));\n    assertThat(numericRangeOffsetCount, contains(\n        new Tuple(\"one\", 1d),\n        new Tuple(\"two\", 2d)));\n\n    List<Tuple> stringRangeOffsetCount = exec(commandObjects.zrangeByScoreWithScores(key, smin, smax, offset, count));\n    assertThat(stringRangeOffsetCount, contains(\n        new Tuple(\"one\", 1d),\n        new Tuple(\"two\", 2d)));\n\n    List<Tuple> numericRangeBinary = exec(commandObjects.zrangeByScoreWithScores(key.getBytes(), min, max));\n    assertThat(numericRangeBinary, contains(\n        new Tuple(\"one\".getBytes(), 1d),\n        new Tuple(\"two\".getBytes(), 2d),\n        new Tuple(\"three\".getBytes(), 3d)));\n\n    List<Tuple> stringRangeBinary = exec(commandObjects.zrangeByScoreWithScores(key.getBytes(), bmin, bmax));\n    assertThat(stringRangeBinary, contains(\n        new Tuple(\"one\".getBytes(), 1d),\n        new Tuple(\"two\".getBytes(), 2d),\n        new Tuple(\"three\".getBytes(), 3d)));\n\n    List<Tuple> numericRangeOffsetCountBinary = exec(commandObjects.zrangeByScoreWithScores(key.getBytes(), min, max, offset, count));\n    assertThat(numericRangeOffsetCountBinary, contains(\n        new Tuple(\"one\".getBytes(), 1d),\n        new Tuple(\"two\".getBytes(), 2d)));\n\n    List<Tuple> stringRangeOffsetCountBinary = exec(commandObjects.zrangeByScoreWithScores(key.getBytes(), bmin, bmax, offset, count));\n    assertThat(stringRangeOffsetCountBinary, contains(\n        new Tuple(\"one\".getBytes(), 1d),\n        new Tuple(\"two\".getBytes(), 2d)));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScores() {\n    String key = \"zset\";\n    double max = 10.0;\n    double min = 1.0;\n    String smax = \"10\";\n    String smin = \"1\";\n    byte[] bmax = \"10\".getBytes();\n    byte[] bmin = \"1\".getBytes();\n    int offset = 0;\n    int count = 2;\n\n    exec(commandObjects.zadd(key, 3, \"three\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n    exec(commandObjects.zadd(key, 1, \"one\"));\n\n    List<Tuple> numericRevrange = exec(commandObjects.zrevrangeByScoreWithScores(key, max, min));\n    assertThat(numericRevrange, contains(\n        new Tuple(\"three\", 3d),\n        new Tuple(\"two\", 2d),\n        new Tuple(\"one\", 1d)));\n\n    List<Tuple> stringRevrange = exec(commandObjects.zrevrangeByScoreWithScores(key, smax, smin));\n    assertThat(stringRevrange, contains(\n        new Tuple(\"three\", 3d),\n        new Tuple(\"two\", 2d),\n        new Tuple(\"one\", 1d)));\n\n    List<Tuple> numericRevrangeOffsetCount = exec(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count));\n    assertThat(numericRevrangeOffsetCount, contains(\n        new Tuple(\"three\", 3d),\n        new Tuple(\"two\", 2d)));\n\n    List<Tuple> stringRevrangeOffsetCount = exec(commandObjects.zrevrangeByScoreWithScores(key, smax, smin, offset, count));\n    assertThat(stringRevrangeOffsetCount, contains(\n        new Tuple(\"three\", 3d),\n        new Tuple(\"two\", 2d)));\n\n    List<Tuple> numericRevrangeBinary = exec(commandObjects.zrevrangeByScoreWithScores(key.getBytes(), max, min));\n    assertThat(numericRevrangeBinary, contains(\n        new Tuple(\"three\".getBytes(), 3d),\n        new Tuple(\"two\".getBytes(), 2d),\n        new Tuple(\"one\".getBytes(), 1d)));\n\n    List<Tuple> stringRevrangeBinary = exec(commandObjects.zrevrangeByScoreWithScores(key.getBytes(), bmax, bmin));\n    assertThat(stringRevrangeBinary, contains(\n        new Tuple(\"three\".getBytes(), 3d),\n        new Tuple(\"two\".getBytes(), 2d),\n        new Tuple(\"one\".getBytes(), 1d)));\n\n    List<Tuple> numericRevrangeOffsetCountBinary = exec(commandObjects.zrevrangeByScoreWithScores(key.getBytes(), max, min, offset, count));\n    assertThat(numericRevrangeOffsetCountBinary, contains(\n        new Tuple(\"three\".getBytes(), 3d),\n        new Tuple(\"two\".getBytes(), 2d)));\n\n    List<Tuple> stringRevrangeOffsetCountBinary = exec(commandObjects.zrevrangeByScoreWithScores(key.getBytes(), bmax, bmin, offset, count));\n    assertThat(stringRevrangeOffsetCountBinary, contains(\n        new Tuple(\"three\".getBytes(), 3d),\n        new Tuple(\"two\".getBytes(), 2d)));\n  }\n\n  @Test\n  public void testZremrangeByRank() {\n    String key = \"zset\";\n    long start = 0;\n    long stop = 1;\n\n    exec(commandObjects.zadd(key, 1, \"one\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n    exec(commandObjects.zadd(key, 3, \"three\"));\n\n    Long removedCount = exec(commandObjects.zremrangeByRank(key, start, stop));\n    assertThat(removedCount, equalTo(2L));\n\n    List<String> remainingElements = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(remainingElements, contains(\"three\"));\n  }\n\n  @Test\n  public void testZremrangeByRankBinary() {\n    byte[] key = \"zset\".getBytes();\n    long start = 0;\n    long stop = 1;\n\n    exec(commandObjects.zadd(key, 1, \"one\".getBytes()));\n    exec(commandObjects.zadd(key, 2, \"two\".getBytes()));\n    exec(commandObjects.zadd(key, 3, \"three\".getBytes()));\n\n    Long removedCount = exec(commandObjects.zremrangeByRank(key, start, stop));\n    assertThat(removedCount, equalTo(2L));\n\n    List<byte[]> remainingElements = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(remainingElements, contains(\"three\".getBytes()));\n  }\n\n  @Test\n  public void testZremrangeByScore() {\n    String key = \"zset\";\n    double min = 1.0;\n    double max = 2.0;\n    String smin = \"1\";\n    String smax = \"2\";\n\n    exec(commandObjects.zadd(key, 1, \"one\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n    exec(commandObjects.zadd(key, 3, \"three\"));\n\n    Long removedCountNumeric = exec(commandObjects.zremrangeByScore(key, min, max));\n    assertThat(removedCountNumeric, equalTo(2L));\n\n    List<String> remainingElements = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(remainingElements, contains(\"three\"));\n\n    exec(commandObjects.zadd(key, 1, \"one\"));\n    exec(commandObjects.zadd(key, 2, \"two\"));\n\n    Long removedCountString = exec(commandObjects.zremrangeByScore(key, smin, smax));\n    assertThat(removedCountString, equalTo(2L));\n\n    remainingElements = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(remainingElements, contains(\"three\"));\n  }\n\n  @Test\n  public void testZremrangeByScoreBinary() {\n    byte[] bkey = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n    byte[] bmin = \"1\".getBytes();\n    byte[] bmax = \"2\".getBytes();\n\n    exec(commandObjects.zadd(bkey, 1, \"one\".getBytes()));\n    exec(commandObjects.zadd(bkey, 2, \"two\".getBytes()));\n    exec(commandObjects.zadd(bkey, 3, \"three\".getBytes()));\n\n    Long removedCountNumericBinary = exec(commandObjects.zremrangeByScore(bkey, min, max));\n    assertThat(removedCountNumericBinary, equalTo(2L));\n\n    List<byte[]> remainingElements = exec(commandObjects.zrange(bkey, 0, -1));\n    assertThat(remainingElements, contains(\"three\".getBytes()));\n\n    exec(commandObjects.zadd(bkey, 1, \"one\".getBytes()));\n    exec(commandObjects.zadd(bkey, 2, \"two\".getBytes()));\n\n    Long removedCountStringBinary = exec(commandObjects.zremrangeByScore(bkey, bmin, bmax));\n    assertThat(removedCountStringBinary, equalTo(2L));\n\n    remainingElements = exec(commandObjects.zrange(bkey, 0, -1));\n    assertThat(remainingElements, contains(\"three\".getBytes()));\n  }\n\n  @Test\n  public void testZlexcount() {\n    String key = \"zset\";\n    String min = \"[a\", max = \"(g\";\n\n    exec(commandObjects.zadd(key, 0, \"abc\"));\n    exec(commandObjects.zadd(key, 0, \"def\"));\n    exec(commandObjects.zadd(key, 0, \"ghi\"));\n\n    Long count = exec(commandObjects.zlexcount(key, min, max));\n    assertThat(count, equalTo(2L));\n\n    Long countBinary = exec(commandObjects.zlexcount(key.getBytes(), min.getBytes(), max.getBytes()));\n    assertThat(countBinary, equalTo(2L));\n  }\n\n  @Test\n  public void testZrangeByLex() {\n    String key = \"zset\";\n    String min = \"[abc\";\n    String max = \"(cde\";\n    int offset = 0;\n    int count = 2;\n\n    exec(commandObjects.zadd(key, 0, \"aaa\"));\n    exec(commandObjects.zadd(key, 0, \"abc\"));\n    exec(commandObjects.zadd(key, 0, \"bcd\"));\n    exec(commandObjects.zadd(key, 0, \"cde\"));\n\n    List<String> range = exec(commandObjects.zrangeByLex(key, min, max));\n    assertThat(range, contains(\"abc\", \"bcd\"));\n\n    List<String> limitedRange = exec(commandObjects.zrangeByLex(key, min, max, offset, count));\n    assertThat(limitedRange, contains(\"abc\", \"bcd\"));\n\n    List<byte[]> rangeBinary = exec(commandObjects.zrangeByLex(key.getBytes(), min.getBytes(), max.getBytes()));\n    assertThat(rangeBinary, contains(\"abc\".getBytes(), \"bcd\".getBytes()));\n\n    List<byte[]> limitedRangeBinary = exec(commandObjects.zrangeByLex(key.getBytes(), min.getBytes(), max.getBytes(), offset, count));\n    assertThat(limitedRangeBinary, contains(\"abc\".getBytes(), \"bcd\".getBytes()));\n  }\n\n  @Test\n  public void testZrevrangeByLex() {\n    String key = \"zset\";\n    String max = \"[cde\";\n    String min = \"(aaa\";\n    int offset = 0;\n    int count = 2;\n\n    exec(commandObjects.zadd(key, 0, \"aaa\"));\n    exec(commandObjects.zadd(key, 0, \"abc\"));\n    exec(commandObjects.zadd(key, 0, \"bcd\"));\n    exec(commandObjects.zadd(key, 0, \"cde\"));\n\n    List<String> revRange = exec(commandObjects.zrevrangeByLex(key, max, min));\n    assertThat(revRange, contains(\"cde\", \"bcd\", \"abc\"));\n\n    List<String> limitedRevRange = exec(commandObjects.zrevrangeByLex(key, max, min, offset, count));\n    assertThat(limitedRevRange, contains(\"cde\", \"bcd\"));\n\n    List<byte[]> revRangeBinary = exec(commandObjects.zrevrangeByLex(key.getBytes(), max.getBytes(), min.getBytes()));\n    assertThat(revRangeBinary.get(0), equalTo(\"cde\".getBytes()));\n    assertThat(revRangeBinary.get(1), equalTo(\"bcd\".getBytes()));\n    assertThat(revRangeBinary.get(2), equalTo(\"abc\".getBytes()));\n\n    List<byte[]> limitedRevRangeBinary = exec(commandObjects.zrevrangeByLex(key.getBytes(), max.getBytes(), min.getBytes(), offset, count));\n    assertThat(limitedRevRangeBinary.get(0), equalTo(\"cde\".getBytes()));\n    assertThat(limitedRevRangeBinary.get(1), equalTo(\"bcd\".getBytes()));\n  }\n\n  @Test\n  public void testZremrangeByLex() {\n    String key = \"zset\";\n    String min = \"[aaa\";\n    String max = \"(ccc\";\n\n    exec(commandObjects.zadd(key, 0, \"aaa\"));\n    exec(commandObjects.zadd(key, 0, \"bbb\"));\n    exec(commandObjects.zadd(key, 0, \"ccc\"));\n\n    Long removedCount = exec(commandObjects.zremrangeByLex(key, min, max));\n    assertThat(removedCount, equalTo(2L));\n\n    List<String> remainingElements = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(remainingElements, contains(\"ccc\"));\n  }\n\n  @Test\n  public void testZremrangeByLexBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"[aaa\".getBytes();\n    byte[] bmax = \"(ccc\".getBytes();\n\n    exec(commandObjects.zadd(key, 0, \"aaa\".getBytes()));\n    exec(commandObjects.zadd(key, 0, \"bbb\".getBytes()));\n    exec(commandObjects.zadd(key, 0, \"ccc\".getBytes()));\n\n    Long removedCount = exec(commandObjects.zremrangeByLex(key, min, bmax));\n    assertThat(removedCount, equalTo(2L));\n\n    List<byte[]> remainingElements = exec(commandObjects.zrange(key, 0, -1));\n    assertThat(remainingElements, contains(\"ccc\".getBytes()));\n  }\n\n  @Test\n  public void testZscan() {\n    String key = \"zset\";\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().count(2);\n    String member1 = \"one\";\n    double score1 = 1.0;\n    String member2 = \"two\";\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key, score1, member1));\n    exec(commandObjects.zadd(key, score2, member2));\n\n    ScanResult<Tuple> result = exec(commandObjects.zscan(key, cursor, params));\n    assertThat(result.getResult(), containsInAnyOrder(\n        new Tuple(member1, score1),\n        new Tuple(member2, score2)));\n\n    ScanResult<Tuple> resultBinar = exec(commandObjects.zscan(key.getBytes(), cursor.getBytes(), params));\n    assertThat(resultBinar.getResult(), containsInAnyOrder(\n        new Tuple(member1, score1),\n        new Tuple(member2, score2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZdiffAndZdiffWithScores() {\n    String key1 = \"zset1\";\n    String key2 = \"zset2\";\n    String member1 = \"one\";\n    double score1 = 1.0;\n    String member2 = \"two\";\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key1, score2, member2));\n    exec(commandObjects.zadd(key2, score1, member1));\n\n    List<String> diff = exec(commandObjects.zdiff(key1, key2));\n    assertThat(diff, containsInAnyOrder(member2));\n\n    List<Tuple> diffWithScores = exec(commandObjects.zdiffWithScores(key1, key2));\n    assertThat(diffWithScores, containsInAnyOrder(new Tuple(member2, score2)));\n\n    List<byte[]> diffBinary = exec(commandObjects.zdiff(key1.getBytes(), key2.getBytes()));\n    assertThat(diffBinary, containsInAnyOrder(member2.getBytes()));\n\n    List<Tuple> diffWithScoresBinary = exec(commandObjects.zdiffWithScores(key1.getBytes(), key2.getBytes()));\n    assertThat(diffWithScoresBinary, containsInAnyOrder(new Tuple(member2.getBytes(), score2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZdiffStore() {\n    String dstKey = \"result\";\n\n    exec(commandObjects.zadd(\"set1\", 1, \"member1\"));\n    exec(commandObjects.zadd(\"set1\", 2, \"member2\"));\n    exec(commandObjects.zadd(\"set2\", 3, \"member2\"));\n\n    Long result = exec(commandObjects.zdiffStore(dstKey, \"set1\", \"set2\"));\n    assertThat(result, equalTo(1L));\n\n    List<String> resultSet = exec(commandObjects.zrange(dstKey, 0, -1));\n    assertThat(resultSet, containsInAnyOrder(\"member1\"));\n\n    exec(commandObjects.del(dstKey));\n\n    result = exec(commandObjects.zdiffstore(dstKey, \"set1\", \"set2\"));\n    assertThat(result, equalTo(1L));\n\n    resultSet = exec(commandObjects.zrange(dstKey, 0, -1));\n    assertThat(resultSet, hasSize(1));\n    assertThat(resultSet, containsInAnyOrder(\"member1\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZdiffStoreBinary() {\n    byte[] dstKey = \"result\".getBytes();\n\n    exec(commandObjects.zadd(\"set1\".getBytes(), 1, \"member1\".getBytes()));\n    exec(commandObjects.zadd(\"set1\".getBytes(), 2, \"member2\".getBytes()));\n    exec(commandObjects.zadd(\"set2\".getBytes(), 3, \"member2\".getBytes()));\n\n    Long result = exec(commandObjects.zdiffStore(dstKey, \"set1\".getBytes(), \"set2\".getBytes()));\n    assertThat(result, equalTo(1L));\n\n    List<byte[]> resultSet = exec(commandObjects.zrange(dstKey, 0, -1));\n    assertThat(resultSet, hasSize(1));\n    assertThat(resultSet, containsInAnyOrder(\"member1\".getBytes()));\n\n    exec(commandObjects.del(dstKey));\n\n    result = exec(commandObjects.zdiffstore(dstKey, \"set1\".getBytes(), \"set2\".getBytes()));\n    assertThat(result, equalTo(1L));\n\n    resultSet = exec(commandObjects.zrange(dstKey, 0, -1));\n    assertThat(resultSet, hasSize(1));\n    assertThat(resultSet, containsInAnyOrder(\"member1\".getBytes()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZinterAndZintercard() {\n    ZParams params = new ZParams().aggregate(ZParams.Aggregate.SUM).weights(1, 2);\n\n    exec(commandObjects.zadd(\"set1\", 1, \"member1\"));\n    exec(commandObjects.zadd(\"set2\", 2, \"member1\"));\n    exec(commandObjects.zadd(\"set2\", 2, \"member2\"));\n\n    List<String> inter = exec(commandObjects.zinter(params, \"set1\", \"set2\"));\n    assertThat(inter, containsInAnyOrder(\"member1\"));\n\n    List<Tuple> interWithScores = exec(commandObjects.zinterWithScores(params, \"set1\", \"set2\"));\n    assertThat(interWithScores, containsInAnyOrder(\n        new Tuple(\"member1\", 5.0)));\n\n    Long card = exec(commandObjects.zintercard(\"set1\", \"set2\"));\n    assertThat(card, equalTo(1L));\n\n    Long cardLimited = exec(commandObjects.zintercard(1L, \"set1\", \"set2\"));\n    assertThat(cardLimited, equalTo(1L));\n\n    List<byte[]> interBinary = exec(commandObjects.zinter(params, \"set1\".getBytes(), \"set2\".getBytes()));\n    assertThat(interBinary, containsInAnyOrder(\"member1\".getBytes()));\n\n    List<Tuple> interWithScoresBinary = exec(commandObjects.zinterWithScores(params, \"set1\".getBytes(), \"set2\".getBytes()));\n    assertThat(interWithScoresBinary, hasItem(\n        new Tuple(\"member1\".getBytes(), 5.0)));\n\n    Long cardBinary = exec(commandObjects.zintercard(\"set1\".getBytes(), \"set2\".getBytes()));\n    assertThat(cardBinary, equalTo(1L));\n\n    Long cardLimitedBinary = exec(commandObjects.zintercard(1L, \"set1\".getBytes(), \"set2\".getBytes()));\n    assertThat(cardLimitedBinary, equalTo(1L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZinterstore() {\n    String dstKey = \"destinationIntersect\";\n    String set1 = \"sortedSet1\";\n    String set2 = \"sortedSet2\";\n    String set3 = \"sortedSet3\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double score3 = 3.0;\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    String member3 = \"member3\";\n\n    exec(commandObjects.zadd(set1, score1, member1));\n    exec(commandObjects.zadd(set1, score2, member2));\n\n    exec(commandObjects.zadd(set2, score2, member2));\n    exec(commandObjects.zadd(set2, score3, member3));\n\n    exec(commandObjects.zadd(set3, score1, member1));\n    exec(commandObjects.zadd(set3, score3, member3));\n\n    ZParams params = new ZParams().aggregate(ZParams.Aggregate.SUM);\n\n    Long interStore = exec(commandObjects.zinterstore(dstKey, set1, set2, set3));\n    assertThat(interStore, equalTo(0L));\n\n    Long interStoreWithParams = exec(commandObjects.zinterstore(dstKey, params, set1, set2));\n    assertThat(interStoreWithParams, equalTo(1L));\n\n    List<Tuple> dstSetContent = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetContent, hasSize(1));\n    assertThat(dstSetContent.get(0).getElement(), equalTo(member2));\n    assertThat(dstSetContent.get(0).getScore(), equalTo(score2 * 2)); // Score aggregated as SUM\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZinterstoreBinary() {\n    byte[] dstKey = \"destinationIntersect\".getBytes();\n    byte[] set1 = \"sortedSet1\".getBytes();\n    byte[] set2 = \"sortedSet2\".getBytes();\n    byte[] set3 = \"sortedSet3\".getBytes();\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double score3 = 3.0;\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n    byte[] member3 = \"member3\".getBytes();\n\n    exec(commandObjects.zadd(set1, score1, member1));\n    exec(commandObjects.zadd(set1, score2, member2));\n\n    exec(commandObjects.zadd(set2, score2, member2));\n    exec(commandObjects.zadd(set2, score3, member3));\n\n    exec(commandObjects.zadd(set3, score1, member1));\n    exec(commandObjects.zadd(set3, score3, member3));\n\n    ZParams params = new ZParams().aggregate(ZParams.Aggregate.SUM);\n\n    Long interStore = exec(commandObjects.zinterstore(dstKey, set1, set2, set3));\n    assertThat(interStore, equalTo(0L));\n\n    List<Tuple> dstSetContent = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetContent, empty());\n\n    Long interStoreParams = exec(commandObjects.zinterstore(dstKey, params, set1, set2));\n    assertThat(interStoreParams, equalTo(1L));\n\n    List<Tuple> dstSetParamsContent = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetParamsContent, hasSize(1));\n    assertThat(dstSetParamsContent.get(0).getBinaryElement(), equalTo(member2));\n    assertThat(dstSetParamsContent.get(0).getScore(), equalTo(score2 * 2)); // Score aggregated as SUM\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZunionAndZunionWithScores() {\n    String key1 = \"sortedSet1\";\n    String key2 = \"sortedSet2\";\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n\n    exec(commandObjects.zadd(key1, score1, member1));\n\n    exec(commandObjects.zadd(key2, score2, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    ZParams params = new ZParams().aggregate(ZParams.Aggregate.SUM);\n\n    List<String> zunion = exec(commandObjects.zunion(params, key1, key2));\n    assertThat(zunion, containsInAnyOrder(member1, member2));\n\n    List<Tuple> zunionWithScores = exec(commandObjects.zunionWithScores(params, key1, key2));\n    assertThat(zunionWithScores, containsInAnyOrder(\n        new Tuple(member1, score1 + score2),\n        new Tuple(member2, score2)));\n\n    List<byte[]> zunionBinary = exec(commandObjects.zunion(params, key1.getBytes(), key2.getBytes()));\n    assertThat(zunionBinary, containsInAnyOrder(member1.getBytes(), member2.getBytes()));\n\n    List<Tuple> zunionWithScoresBinary = exec(commandObjects.zunionWithScores(params, key1.getBytes(), key2.getBytes()));\n    assertThat(zunionWithScoresBinary, containsInAnyOrder(\n        new Tuple(member1, score1 + score2),\n        new Tuple(member2, score2)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZunionstore() {\n    String dstKey = \"destinationSet\";\n    String set1 = \"sortedSet1\";\n    String set2 = \"sortedSet2\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double score3 = 3.0;\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    String member3 = \"member3\";\n\n    exec(commandObjects.zadd(set1, score1, member1));\n    exec(commandObjects.zadd(set1, score2, member2));\n    exec(commandObjects.zadd(set1, score3, member3));\n\n    exec(commandObjects.zadd(set2, score3, member3));\n\n    ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);\n\n    Long zunionStore = exec(commandObjects.zunionstore(dstKey, set1, set2));\n    assertThat(zunionStore, equalTo(3L));\n\n    List<Tuple> dstSetContent = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetContent, containsInAnyOrder(\n        new Tuple(member1, score1),\n        new Tuple(member2, score2),\n        new Tuple(member3, score3 * 2)));\n\n    Long zunionStoreParams = exec(commandObjects.zunionstore(dstKey, params, set1, set2));\n    assertThat(zunionStoreParams, equalTo(3L));\n\n    List<Tuple> dstSetContentParams = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetContentParams, containsInAnyOrder(\n        new Tuple(member1, score1),\n        new Tuple(member2, score2),\n        new Tuple(member3, score3)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZunionstoreBinary() {\n    byte[] dstKey = \"destinationSet\".getBytes();\n    byte[] set1 = \"sortedSet1\".getBytes();\n    byte[] set2 = \"sortedSet2\".getBytes();\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double score3 = 3.0;\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n    byte[] member3 = \"member3\".getBytes();\n\n    exec(commandObjects.zadd(set1, score1, member1));\n    exec(commandObjects.zadd(set1, score2, member2));\n    exec(commandObjects.zadd(set1, score3, member3));\n\n    exec(commandObjects.zadd(set2, score3, member3));\n\n    ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);\n\n    Long zunionStore = exec(commandObjects.zunionstore(dstKey, set1, set2));\n    assertThat(zunionStore, equalTo(3L));\n\n    List<Tuple> dstSetContent = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetContent, containsInAnyOrder(\n        new Tuple(member1, score1),\n        new Tuple(member2, score2),\n        new Tuple(member3, score3 * 2)));\n\n    Long zunionStoreParams = exec(commandObjects.zunionstore(dstKey, params, set1, set2));\n    assertThat(zunionStoreParams, equalTo(3L));\n\n    List<Tuple> dstSetContentParams = exec(commandObjects.zrangeWithScores(dstKey, 0, -1));\n    assertThat(dstSetContentParams, containsInAnyOrder(\n        new Tuple(member1, score1),\n        new Tuple(member2, score2),\n        new Tuple(member3, score3)));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZmpopAndZmpopWithCount() {\n    String key1 = \"sortedSet1\";\n    String key2 = \"sortedSet2\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<String, List<Tuple>> zmpop = exec(commandObjects.zmpop(SortedSetOption.MAX, key1, key2));\n\n    assertThat(zmpop, notNullValue());\n    assertThat(zmpop.getKey(), either(equalTo(key1)).or(equalTo(key2)));\n    assertThat(zmpop.getValue(), hasSize(1));\n\n    KeyValue<String, List<Tuple>> zmpopCount = exec(commandObjects.zmpop(SortedSetOption.MIN, 2, key1, key2));\n\n    assertThat(zmpopCount, notNullValue());\n    assertThat(zmpopCount.getKey(), either(equalTo(key1)).or(equalTo(key2)));\n    assertThat(zmpopCount.getValue(), hasSize(1));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testZmpopAndZmpopWithCountBinary() {\n    byte[] key1 = \"sortedSet1\".getBytes();\n    byte[] key2 = \"sortedSet2\".getBytes();\n    double score1 = 1.0;\n    double score2 = 2.0;\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<byte[], List<Tuple>> zmpopBinary = exec(commandObjects.zmpop(SortedSetOption.MAX, key1, key2));\n\n    assertThat(zmpopBinary, notNullValue());\n    assertThat(zmpopBinary.getKey(), either(equalTo(key1)).or(equalTo(key2)));\n    assertThat(zmpopBinary.getValue(), hasSize(1));\n\n    KeyValue<byte[], List<Tuple>> zmpopCountBinary = exec(commandObjects.zmpop(SortedSetOption.MIN, 2, key1, key2));\n\n    assertThat(zmpopCountBinary, notNullValue());\n    assertThat(zmpopCountBinary.getKey(), either(equalTo(key1)).or(equalTo(key2)));\n    assertThat(zmpopCountBinary.getValue(), hasSize(1));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBzmpop() {\n    String key1 = \"sortedSet1\";\n    String key2 = \"sortedSet2\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n    double timeout = 0.1;\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key1, score2, member2));\n\n    exec(commandObjects.zadd(key2, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<String, List<Tuple>> bzmpopMax = exec(commandObjects.bzmpop(timeout, SortedSetOption.MAX, key1, key2));\n    assertThat(bzmpopMax, notNullValue());\n    assertThat(bzmpopMax.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(bzmpopMax.getValue(), contains(new Tuple(member2, score2)));\n\n    KeyValue<String, List<Tuple>> bzmpopMin = exec(commandObjects.bzmpop(timeout, SortedSetOption.MIN, key1, key2));\n    assertThat(bzmpopMin, notNullValue());\n    assertThat(bzmpopMin.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(bzmpopMin.getValue(), contains(new Tuple(member1, score1)));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBzmpopBinary() {\n    byte[] key1 = \"sortedSet1\".getBytes();\n    byte[] key2 = \"sortedSet2\".getBytes();\n    double score1 = 1.0;\n    double score2 = 2.0;\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n    double timeout = 0.1;\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key1, score2, member2));\n\n    exec(commandObjects.zadd(key2, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<byte[], List<Tuple>> bzmpopMax = exec(commandObjects.bzmpop(timeout, SortedSetOption.MAX, key1, key2));\n    assertThat(bzmpopMax, notNullValue());\n    assertThat(bzmpopMax.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(bzmpopMax.getValue(), contains(new Tuple(member2, score2)));\n\n    KeyValue<byte[], List<Tuple>> bzmpopMin = exec(commandObjects.bzmpop(timeout, SortedSetOption.MIN, key1, key2));\n    assertThat(bzmpopMin, notNullValue());\n    assertThat(bzmpopMin.getKey(), anyOf(equalTo(key1), equalTo(key2)));\n    assertThat(bzmpopMin.getValue(), contains(new Tuple(member1, score1)));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBzmpopCount() {\n    String key1 = \"sortedSet1\";\n    String key2 = \"sortedSet2\";\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double timeout = 0.1;\n    String member1 = \"member1\";\n    String member2 = \"member2\";\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<String, List<Tuple>> bzmpop = exec(commandObjects.bzmpop(timeout, SortedSetOption.MAX, 1, key1, key2));\n    assertThat(bzmpop, notNullValue());\n    assertThat(bzmpop.getKey(), either(equalTo(key1)).or(equalTo(key2)));\n    assertThat(bzmpop.getValue(), hasSize(1));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testBzmpopCountBinary() {\n    byte[] key1 = \"sortedSet1\".getBytes();\n    byte[] key2 = \"sortedSet2\".getBytes();\n    double score1 = 1.0;\n    double score2 = 2.0;\n    double timeout = 0.1;\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n\n    exec(commandObjects.zadd(key1, score1, member1));\n    exec(commandObjects.zadd(key2, score2, member2));\n\n    KeyValue<byte[], List<Tuple>> bzmpopBinary = exec(commandObjects.bzmpop(timeout, SortedSetOption.MAX, 1, key1, key2));\n    assertThat(bzmpopBinary, notNullValue());\n    assertThat(bzmpopBinary.getKey(), either(equalTo(key1)).or(equalTo(key2)));\n    assertThat(bzmpopBinary.getValue(), hasSize(1));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsStandaloneTestBase.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisProtocol;\n\n/**\n * Base class for tests that use the standalone client.\n */\n@Tag(\"integration\")\npublic abstract class CommandObjectsStandaloneTestBase extends CommandObjectsTestBase {\n\n  @RegisterExtension\n  RedisVersionCondition redisVersionCondition = new RedisVersionCondition(() -> endpoint);\n  @RegisterExtension\n  EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(() -> endpoint);\n  @RegisterExtension\n  static EnvCondition envCondition = new EnvCondition();\n\n  public CommandObjectsStandaloneTestBase(RedisProtocol protocol) {\n    super(protocol, Endpoints.getRedisEndpoint(\"standalone0\"));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsStreamCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\n\nimport java.util.AbstractMap;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XAutoClaimParams;\nimport redis.clients.jedis.params.XClaimParams;\nimport redis.clients.jedis.params.XPendingParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.params.XTrimParams;\nimport redis.clients.jedis.resps.StreamConsumerInfo;\nimport redis.clients.jedis.resps.StreamConsumersInfo;\nimport redis.clients.jedis.resps.StreamEntry;\nimport redis.clients.jedis.resps.StreamFullInfo;\nimport redis.clients.jedis.resps.StreamGroupInfo;\nimport redis.clients.jedis.resps.StreamInfo;\nimport redis.clients.jedis.resps.StreamPendingEntry;\nimport redis.clients.jedis.resps.StreamPendingSummary;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=stream\">Stream</a> commands.\n */\npublic class CommandObjectsStreamCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsStreamCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testXaddAndXlen() {\n    String streamKey = \"testStream\";\n    StreamEntryID entryID = StreamEntryID.NEW_ENTRY;\n\n    Map<String, String> entryData = new HashMap<>();\n    entryData.put(\"field1\", \"value1\");\n    entryData.put(\"field2\", \"value2\");\n\n    StreamEntryID addedEntryId = exec(commandObjects.xadd(streamKey, entryID, entryData));\n    assertThat(addedEntryId, notNullValue());\n\n    XAddParams params = new XAddParams().maxLen(1000);\n    StreamEntryID addedEntryIdWithParams = exec(commandObjects.xadd(streamKey, params, entryData));\n    assertThat(addedEntryIdWithParams, notNullValue());\n\n    Long streamLength = exec(commandObjects.xlen(streamKey));\n    assertThat(streamLength, equalTo(2L));\n  }\n\n  @Test\n  public void testXaddAndXlenBinary() {\n    byte[] streamKey = \"streamKey\".getBytes();\n\n    Map<byte[], byte[]> entryData = new HashMap<>();\n    entryData.put(\"field1\".getBytes(), \"value1\".getBytes());\n    entryData.put(\"field2\".getBytes(), \"value2\".getBytes());\n\n    XAddParams params = new XAddParams().maxLen(1000);\n    byte[] addedEntryId = exec(commandObjects.xadd(streamKey, params, entryData));\n    assertThat(addedEntryId, notNullValue());\n\n    Long streamLengthBytes = exec(commandObjects.xlen(streamKey));\n    assertThat(streamLengthBytes, equalTo(1L));\n  }\n\n  @Test\n  public void testXrangeWithIdParameters() {\n    String key = \"testStream\";\n\n    Map<String, String> entryData1 = new HashMap<>();\n    entryData1.put(\"field1\", \"value1\");\n\n    Map<String, String> entryData2 = new HashMap<>();\n    entryData2.put(\"field2\", \"value2\");\n\n    StreamEntryID startID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData1));\n    StreamEntryID endID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData2));\n\n    List<StreamEntry> xrangeAll = exec(commandObjects.xrange(key, null, (StreamEntryID) null));\n    assertThat(xrangeAll.size(), equalTo(2));\n    assertThat(xrangeAll.get(0).getFields(), equalTo(entryData1));\n    assertThat(xrangeAll.get(1).getFields(), equalTo(entryData2));\n\n    List<StreamEntry> xrangeAllCount = exec(commandObjects.xrange(key, null, (StreamEntryID) null, 1));\n    assertThat(xrangeAllCount.size(), equalTo(1));\n    assertThat(xrangeAllCount.get(0).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrangeStartEnd = exec(commandObjects.xrange(key, startID, endID));\n    assertThat(xrangeStartEnd.size(), equalTo(2));\n    assertThat(xrangeStartEnd.get(0).getFields(), equalTo(entryData1));\n    assertThat(xrangeStartEnd.get(1).getFields(), equalTo(entryData2));\n\n    List<StreamEntry> xrangeStartEndCount = exec(commandObjects.xrange(key, startID, endID, 1));\n    assertThat(xrangeStartEndCount.size(), equalTo(1));\n    assertThat(xrangeStartEndCount.get(0).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrangeUnknown = exec(commandObjects.xrange(\"nonExistingStream\", null, (StreamEntryID) null));\n    assertThat(xrangeUnknown, empty());\n  }\n\n  @Test\n  public void testXrangeWithStringParameters() {\n    String key = \"testStreamWithString\";\n\n    Map<String, String> entryData1 = new HashMap<>();\n    entryData1.put(\"field1\", \"value1\");\n\n    Map<String, String> entryData2 = new HashMap<>();\n    entryData2.put(\"field2\", \"value2\");\n\n    StreamEntryID startID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData1));\n    StreamEntryID endID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData2));\n\n    String start = startID.toString();\n    String end = endID.toString();\n\n    List<StreamEntry> xrangeStartEnd = exec(commandObjects.xrange(key, start, end));\n    assertThat(xrangeStartEnd.size(), equalTo(2));\n    assertThat(xrangeStartEnd.get(0).getFields(), equalTo(entryData1));\n    assertThat(xrangeStartEnd.get(1).getFields(), equalTo(entryData2));\n\n    List<StreamEntry> xrangeStartEndCount = exec(commandObjects.xrange(key, start, end, 1));\n    assertThat(xrangeStartEndCount.size(), equalTo(1));\n    assertThat(xrangeStartEndCount.get(0).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrangeUnknown = exec(commandObjects.xrange(\"nonExistingStream\", start, end));\n    assertThat(xrangeUnknown, empty());\n  }\n\n  @Test\n  public void testXrangeWithBinaryParameters() {\n    String keyStr = \"testStreamWithBytes\";\n    byte[] key = keyStr.getBytes();\n\n    Map<String, String> entryData1 = new HashMap<>();\n    entryData1.put(\"field1\", \"value1\");\n\n    Map<String, String> entryData2 = new HashMap<>();\n    entryData2.put(\"field2\", \"value2\");\n\n    StreamEntryID startID = exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, entryData1));\n    StreamEntryID endID = exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, entryData2));\n\n    byte[] start = startID.toString().getBytes();\n    byte[] end = endID.toString().getBytes();\n\n    List<Object> xrangeAll = exec(commandObjects.xrange(key, null, null));\n    assertThat(xrangeAll, hasSize(2));\n    assertThat(xrangeAll.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrangeAll.get(0)).get(0), equalTo(start));\n    assertThat(((List<?>) xrangeAll.get(1)).get(0), equalTo(end));\n\n    List<Object> xrangeStartEnd = exec(commandObjects.xrange(key, start, end));\n    assertThat(xrangeStartEnd, hasSize(2));\n    assertThat(xrangeStartEnd.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrangeStartEnd.get(0)).get(0), equalTo(start));\n    assertThat(((List<?>) xrangeStartEnd.get(1)).get(0), equalTo(end));\n\n    List<Object> xrangeAllCount = exec(commandObjects.xrange(key, null, null, 1));\n    assertThat(xrangeAllCount, hasSize(1));\n    assertThat(xrangeAllCount.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrangeAllCount.get(0)).get(0), equalTo(start));\n\n    List<Object> xrangeStartEndCount = exec(commandObjects.xrange(key, start, end, 1));\n    assertThat(xrangeStartEndCount, hasSize(1));\n    assertThat(xrangeStartEndCount.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrangeStartEndCount.get(0)).get(0), equalTo(start));\n\n    List<Object> xrangeUnknown = exec(commandObjects.xrange(\"nonExistingStream\".getBytes(), start, end));\n    assertThat(xrangeUnknown, empty());\n  }\n\n  @Test\n  public void testXrevrangeWithIdParameters() {\n    String key = \"testStreamForXrevrange\";\n\n    Map<String, String> entryData1 = new HashMap<>();\n    entryData1.put(\"field1\", \"value1\");\n\n    Map<String, String> entryData2 = new HashMap<>();\n    entryData2.put(\"field2\", \"value2\");\n\n    StreamEntryID startID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData1));\n    StreamEntryID endID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData2));\n\n    List<StreamEntry> xrevrangeAll = exec(commandObjects.xrevrange(key, null, (StreamEntryID) null));\n    assertThat(xrevrangeAll.size(), equalTo(2));\n    assertThat(xrevrangeAll.get(0).getFields(), equalTo(entryData2)); // The latest entry comes first\n    assertThat(xrevrangeAll.get(1).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrevrangeAllCount = exec(commandObjects.xrevrange(key, null, (StreamEntryID) null, 1));\n    assertThat(xrevrangeAllCount.size(), equalTo(1));\n    assertThat(xrevrangeAllCount.get(0).getFields(), equalTo(entryData2)); // Only the latest entry is returned\n\n    List<StreamEntry> xrevrangeEndStart = exec(commandObjects.xrevrange(key, endID, startID));\n    assertThat(xrevrangeEndStart.size(), equalTo(2));\n    assertThat(xrevrangeEndStart.get(0).getFields(), equalTo(entryData2));\n    assertThat(xrevrangeEndStart.get(1).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrevrangeStartEndCount = exec(commandObjects.xrevrange(key, endID, startID, 1));\n    assertThat(xrevrangeStartEndCount.size(), equalTo(1));\n    assertThat(xrevrangeStartEndCount.get(0).getFields(), equalTo(entryData2));\n\n    List<StreamEntry> xrevrangeUnknown = exec(commandObjects.xrevrange(\"nonExistingStream\", null, (StreamEntryID) null));\n    assertThat(xrevrangeUnknown, empty());\n  }\n\n  @Test\n  public void testXrevrangeWithStringParameters() {\n    String key = \"testStreamForXrevrangeString\";\n\n    Map<String, String> entryData1 = new HashMap<>();\n    entryData1.put(\"field1\", \"value1\");\n\n    Map<String, String> entryData2 = new HashMap<>();\n    entryData2.put(\"field2\", \"value2\");\n\n    StreamEntryID startID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData1));\n    StreamEntryID endID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData2));\n\n    String start = startID.toString();\n    String end = endID.toString();\n\n    List<StreamEntry> xrevrangeAll = exec(commandObjects.xrevrange(key, null, (StreamEntryID) null));\n    assertThat(xrevrangeAll.size(), equalTo(2));\n    assertThat(xrevrangeAll.get(0).getFields(), equalTo(entryData2)); // The latest entry comes first\n    assertThat(xrevrangeAll.get(1).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrevrangeEndStart = exec(commandObjects.xrevrange(key, end, start));\n    assertThat(xrevrangeEndStart.size(), equalTo(2));\n    assertThat(xrevrangeEndStart.get(0).getFields(), equalTo(entryData2));\n    assertThat(xrevrangeEndStart.get(1).getFields(), equalTo(entryData1));\n\n    List<StreamEntry> xrevrangeAllCount = exec(commandObjects.xrevrange(key, null, (StreamEntryID) null, 1));\n    assertThat(xrevrangeAllCount.size(), equalTo(1));\n    assertThat(xrevrangeAllCount.get(0).getFields(), equalTo(entryData2));\n\n    List<StreamEntry> xrevrangeEndStartCount = exec(commandObjects.xrevrange(key, end, start, 1));\n    assertThat(xrevrangeEndStartCount.size(), equalTo(1));\n    assertThat(xrevrangeEndStartCount.get(0).getFields(), equalTo(entryData2));\n\n    List<StreamEntry> xrevrangeUnknown = exec(commandObjects.xrevrange(\"nonExistingStream\", end, start));\n    assertThat(xrevrangeUnknown, empty());\n  }\n\n  @Test\n  public void testXrevrangeWithBinaryParameters() {\n    String keyStr = \"testStreamForXrevrangeBytes\";\n    byte[] key = keyStr.getBytes();\n\n    Map<String, String> entryData1 = new HashMap<>();\n    entryData1.put(\"field1\", \"value1\");\n\n    Map<String, String> entryData2 = new HashMap<>();\n    entryData2.put(\"field2\", \"value2\");\n\n    StreamEntryID startID = exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, entryData1));\n    StreamEntryID endID = exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, entryData2));\n\n    byte[] start = startID.toString().getBytes();\n    byte[] end = endID.toString().getBytes();\n\n    List<Object> xrevrangeAll = exec(commandObjects.xrevrange(key, null, null));\n    assertThat(xrevrangeAll, hasSize(2));\n    assertThat(xrevrangeAll.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrevrangeAll.get(0)).get(0), equalTo(end));\n    assertThat(((List<?>) xrevrangeAll.get(1)).get(0), equalTo(start));\n\n    List<Object> xrevrangeEndStart = exec(commandObjects.xrevrange(key, end, start));\n    assertThat(xrevrangeEndStart, hasSize(2));\n    assertThat(xrevrangeEndStart.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrevrangeEndStart.get(0)).get(0), equalTo(end));\n    assertThat(((List<?>) xrevrangeEndStart.get(1)).get(0), equalTo(start));\n\n    List<Object> xrevrangeAllCount = exec(commandObjects.xrevrange(key, null, null, 1));\n    assertThat(xrevrangeAllCount, hasSize(1));\n    assertThat(xrevrangeAllCount.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrevrangeAllCount.get(0)).get(0), equalTo(end));\n\n    List<Object> xrevrangeEndStartCount = exec(commandObjects.xrevrange(key, end, start, 1));\n    assertThat(xrevrangeEndStartCount, hasSize(1));\n    assertThat(xrevrangeEndStartCount.get(0), instanceOf(List.class));\n    assertThat(((List<?>) xrevrangeEndStartCount.get(0)).get(0), equalTo(end));\n\n    List<Object> xrevrangeUnknown = exec(commandObjects.xrevrange(\"nonExistingStream\".getBytes(), end, start));\n    assertThat(xrevrangeUnknown, empty());\n  }\n\n  @Test\n  public void testXaddWithNullId() {\n    String key = \"testStreamWithString\";\n\n    // Add two entries, don't specify the IDs\n    StreamEntryID firstId = exec(commandObjects.xadd(key, (StreamEntryID) null, Collections.singletonMap(\"field\", \"value\")));\n    assertThat(firstId, notNullValue());\n\n    StreamEntryID secondId = exec(commandObjects.xadd(key, (StreamEntryID) null, Collections.singletonMap(\"field\", \"value\")));\n    assertThat(secondId, notNullValue());\n\n    assertThat(secondId, not(equalTo(firstId)));\n    assertThat(secondId.getSequence(), greaterThanOrEqualTo(firstId.getSequence()));\n\n    List<StreamEntry> xrangeAll = exec(commandObjects.xrange(key, (StreamEntryID) null, null));\n    assertThat(xrangeAll.size(), equalTo(2));\n    assertThat(xrangeAll.get(0).getID(), equalTo(firstId));\n    assertThat(xrangeAll.get(1).getID(), equalTo(secondId));\n  }\n\n  @Test\n  public void testXackXpending() {\n    String key = \"testStreamForXackEffect\";\n    String group = \"testGroup\";\n    String consumer = \"testConsumer\";\n\n    Map<String, String> entryData = new HashMap<>();\n    entryData.put(\"field1\", \"value1\");\n\n    exec(commandObjects.xgroupCreate(key, group, new StreamEntryID(), true));\n\n    StreamEntryID entryID = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, entryData));\n\n    Map<String, StreamEntryID> streams = Collections.singletonMap(key, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    XReadGroupParams params = new XReadGroupParams();\n\n    List<Map.Entry<String, List<StreamEntry>>> messages = exec(commandObjects.xreadGroup(group, consumer, params, streams));\n\n    assertThat(messages, hasSize(1));\n    assertThat(messages.get(0).getKey(), equalTo(key));\n    assertThat(messages.get(0).getValue(), hasSize(1));\n    assertThat(messages.get(0).getValue().get(0).getID(), equalTo(entryID));\n\n    StreamPendingSummary pendingSummary = exec(commandObjects.xpending(key, group));\n    assertThat(pendingSummary.getTotal(), equalTo(1L));\n\n    XPendingParams xPendingParams = new XPendingParams()\n        .start(StreamEntryID.MINIMUM_ID).end(StreamEntryID.MAXIMUM_ID).count(1000);\n    List<StreamPendingEntry> pendingSummaryWithParams = exec(commandObjects.xpending(key, group, xPendingParams));\n\n    assertThat(pendingSummaryWithParams, hasSize(1));\n    assertThat(pendingSummaryWithParams.get(0).getConsumerName(), equalTo(consumer));\n    assertThat(pendingSummaryWithParams.get(0).getID(), equalTo(entryID));\n\n    Long ack = exec(commandObjects.xack(key, group, entryID));\n    assertThat(ack, equalTo(1L));\n\n    pendingSummary = exec(commandObjects.xpending(key, group));\n    assertThat(pendingSummary.getTotal(), equalTo(0L));\n\n    pendingSummaryWithParams = exec(commandObjects.xpending(key, group, xPendingParams));\n    assertThat(pendingSummaryWithParams, empty());\n  }\n\n  @Test\n  public void testXackXPendingBinary() {\n    String keyStr = \"testStreamForXackEffect\";\n    byte[] key = keyStr.getBytes();\n    byte[] group = \"testGroup\".getBytes();\n    byte[] consumer = \"testConsumer\".getBytes();\n\n    Map<String, String> entryData = new HashMap<>();\n    entryData.put(\"field1\", \"value1\");\n\n    exec(commandObjects.xgroupCreate(key, group, new StreamEntryID().toString().getBytes(), true));\n\n    StreamEntryID entryID = exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, entryData));\n\n    Map.Entry<byte[], byte[]> stream = new AbstractMap.SimpleEntry<>(key, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY.toString().getBytes());\n\n    XReadGroupParams params = new XReadGroupParams();\n\n    List<Object> messages = exec(commandObjects.xreadGroup(group, consumer, params, stream));\n    assertThat(messages, hasSize(1));\n\n    Object pendingSummary = exec(commandObjects.xpending(key, group));\n\n    assertThat(pendingSummary, instanceOf(List.class));\n    assertThat(((List<?>) pendingSummary).get(0), equalTo(1L));\n\n    XPendingParams xPendingParams = new XPendingParams()\n        .start(StreamEntryID.MINIMUM_ID).end(StreamEntryID.MAXIMUM_ID).count(1000);\n\n    List<Object> pendingList = exec(commandObjects.xpending(key, group, xPendingParams));\n    assertThat(pendingList, hasSize(1));\n\n    Long ack = exec(commandObjects.xack(key, group, entryID.toString().getBytes()));\n    assertThat(ack, equalTo(1L));\n\n    pendingSummary = exec(commandObjects.xpending(key, group));\n    assertThat(pendingSummary, instanceOf(List.class));\n    assertThat(((List<?>) pendingSummary).get(0), equalTo(0L));\n\n    pendingList = exec(commandObjects.xpending(key, group, xPendingParams));\n    assertThat(pendingList, empty());\n  }\n\n  @Test\n  public void testXGroupSetID() {\n    String key = \"testStream\";\n    String groupName = \"testGroup\";\n\n    StreamEntryID initialId = new StreamEntryID();\n    StreamEntryID newId = new StreamEntryID(\"0-1\");\n    StreamEntryID newId2 = new StreamEntryID(\"0-2\");\n\n    exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value\")));\n\n    exec(commandObjects.xgroupCreate(key, groupName, initialId, false));\n\n    List<StreamGroupInfo> groupIdBefore = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdBefore, hasSize(1));\n    assertThat(groupIdBefore.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdBefore.get(0).getLastDeliveredId(), equalTo(initialId));\n\n    String xgroupSetId = exec(commandObjects.xgroupSetID(key, groupName, newId));\n    assertThat(xgroupSetId, equalTo(\"OK\"));\n\n    List<StreamGroupInfo> groupIdAfter = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdAfter, hasSize(1));\n    assertThat(groupIdAfter.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdAfter.get(0).getLastDeliveredId(), equalTo(newId));\n\n    String xgroupSetIdBinary = exec(commandObjects.xgroupSetID(key.getBytes(), groupName.getBytes(), newId2.toString().getBytes()));\n    assertThat(xgroupSetIdBinary, equalTo(\"OK\"));\n\n    List<StreamGroupInfo> groupIdAfterBinary = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdAfterBinary, hasSize(1));\n    assertThat(groupIdAfterBinary.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdAfterBinary.get(0).getLastDeliveredId(), equalTo(newId2));\n\n    List<Object> binaryGroupIdAfterBinary = exec(commandObjects.xinfoGroups(key.getBytes()));\n    assertThat(binaryGroupIdAfterBinary, notNullValue());\n  }\n\n  @Test\n  public void testXGroupDestroy() {\n    String key = \"testStream\";\n    String groupName = \"testGroup\";\n\n    StreamEntryID initialId = new StreamEntryID();\n\n    exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value\")));\n\n    exec(commandObjects.xgroupCreate(key, groupName, initialId, false));\n\n    List<StreamGroupInfo> groupIdBefore = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdBefore, hasSize(1));\n    assertThat(groupIdBefore.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdBefore.get(0).getLastDeliveredId(), equalTo(initialId));\n\n    Long xgroupDestroy = exec(commandObjects.xgroupDestroy(key, groupName));\n    assertThat(xgroupDestroy, equalTo(1L));\n\n    List<StreamGroupInfo> groupInfoAfter = exec(commandObjects.xinfoGroups(key));\n    assertThat(groupInfoAfter, empty());\n\n    // Re-create the group\n    exec(commandObjects.xgroupCreate(key, groupName, initialId, false));\n\n    List<StreamGroupInfo> groupIdBeforeBinary = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdBeforeBinary, hasSize(1));\n    assertThat(groupIdBeforeBinary.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdBeforeBinary.get(0).getLastDeliveredId(), equalTo(initialId));\n\n    Long xgroupDestroyBinary = exec(commandObjects.xgroupDestroy(key.getBytes(), groupName.getBytes()));\n    assertThat(xgroupDestroyBinary, equalTo(1L));\n\n    List<StreamGroupInfo> groupInfoAfterBinary = exec(commandObjects.xinfoGroups(key));\n    assertThat(groupInfoAfterBinary, empty());\n  }\n\n  @Test\n  public void testXGroupConsumer() {\n    String key = \"testStream\";\n    String groupName = \"testGroup\";\n    String consumerName = \"testConsumer\";\n\n    StreamEntryID initialId = new StreamEntryID();\n\n    exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value\")));\n\n    exec(commandObjects.xgroupCreate(key, groupName, initialId, false));\n\n    List<StreamGroupInfo> groupIdBefore = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdBefore, hasSize(1));\n    assertThat(groupIdBefore.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdBefore.get(0).getConsumers(), equalTo(0L));\n\n    Boolean createConsumer = exec(commandObjects.xgroupCreateConsumer(key, groupName, consumerName));\n    assertThat(createConsumer, equalTo(true));\n\n    List<StreamGroupInfo> groupIdAfterCreateConsumer = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdAfterCreateConsumer, hasSize(1));\n    assertThat(groupIdAfterCreateConsumer.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdAfterCreateConsumer.get(0).getConsumers(), equalTo(1L));\n\n    Long deleteConsumer = exec(commandObjects.xgroupDelConsumer(key, groupName, consumerName));\n    assertThat(deleteConsumer, equalTo(0L));\n\n    List<StreamGroupInfo> groupIdAfterDeleteConsumer = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdAfterDeleteConsumer, hasSize(1));\n    assertThat(groupIdAfterDeleteConsumer.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdAfterDeleteConsumer.get(0).getConsumers(), equalTo(0L));\n\n    Boolean createConsumerBinary = exec(commandObjects.xgroupCreateConsumer(\n        key.getBytes(), groupName.getBytes(), consumerName.getBytes()));\n    assertThat(createConsumerBinary, equalTo(true));\n\n    List<StreamGroupInfo> groupIdAfterCreateConsumerBinary = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdAfterCreateConsumerBinary, hasSize(1));\n    assertThat(groupIdAfterCreateConsumerBinary.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdAfterCreateConsumerBinary.get(0).getConsumers(), equalTo(1L));\n\n    Long deleteConsumerBinary = exec(commandObjects.xgroupDelConsumer(\n        key.getBytes(), groupName.getBytes(), consumerName.getBytes()));\n    assertThat(deleteConsumerBinary, equalTo(0L));\n\n    List<StreamGroupInfo> groupIdAfterDeleteConsumerBinary = exec(commandObjects.xinfoGroups(key));\n\n    assertThat(groupIdAfterDeleteConsumerBinary, hasSize(1));\n    assertThat(groupIdAfterDeleteConsumerBinary.get(0).getName(), equalTo(groupName));\n    assertThat(groupIdAfterDeleteConsumerBinary.get(0).getConsumers(), equalTo(0L));\n  }\n\n  @Test\n  public void testXDelWithStreamSize() {\n    String key = \"testStream\";\n\n    StreamEntryID id1 = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field1\", \"value1\")));\n    StreamEntryID id2 = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field2\", \"value2\")));\n\n    Long sizeBefore = exec(commandObjects.xlen(key));\n    assertThat(sizeBefore, equalTo(2L));\n\n    Long xdel = exec(commandObjects.xdel(key, id1, id2));\n    assertThat(xdel, equalTo(2L));\n\n    Long sizeAfterStringDeletion = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterStringDeletion, equalTo(0L));\n\n    StreamEntryID id3 = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field3\", \"value3\")));\n    StreamEntryID id4 = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field4\", \"value4\")));\n\n    Long sizeBeforeBinaryDeletion = exec(commandObjects.xlen(key));\n    assertThat(sizeBeforeBinaryDeletion, equalTo(2L));\n\n    Long xdelBinary = exec(commandObjects.xdel(\n        key.getBytes(), id3.toString().getBytes(), id4.toString().getBytes()));\n    assertThat(xdelBinary, equalTo(2L));\n\n    Long sizeAfterBinaryDeletion = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterBinaryDeletion, equalTo(0L));\n  }\n\n  @Test\n  public void testXTrimCommands() {\n    String key = \"testStream\";\n\n    // Populate the stream with more entries than we intend to keep\n    for (int i = 0; i < 10; i++) {\n      exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\" + i, \"value\" + i)));\n    }\n\n    Long sizeBeforeTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeBeforeTrim, equalTo(10L));\n\n    Long xtrim = exec(commandObjects.xtrim(key, 6, false));\n    assertThat(xtrim, equalTo(4L));\n\n    Long sizeAfterTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterTrim, equalTo(6L));\n\n    Long xtrimApproximate = exec(commandObjects.xtrim(key, 3, true));\n    assertThat(xtrimApproximate, lessThanOrEqualTo(3L));\n\n    Long sizeAfterApproximateTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterApproximateTrim, greaterThanOrEqualTo(3L));\n  }\n\n  @Test\n  public void testXTrimCommandsBinary() {\n    String keyStr = \"testStream\";\n    byte[] key = keyStr.getBytes();\n\n    // Populate the stream with more entries than we intend to keep\n    for (int i = 0; i < 10; i++) {\n      exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\" + i, \"value\" + i)));\n    }\n\n    Long sizeBeforeBinaryTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeBeforeBinaryTrim, equalTo(10L));\n\n    Long xtrimBinary = exec(commandObjects.xtrim(key, 6, false));\n    assertThat(xtrimBinary, equalTo(4L));\n\n    Long sizeAfterBinaryTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterBinaryTrim, equalTo(6L));\n\n    Long xtrimApproximateBinary = exec(commandObjects.xtrim(key, 3, true));\n    assertThat(xtrimApproximateBinary, lessThanOrEqualTo(3L));\n\n    Long sizeAfterApproximateBinaryTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterApproximateBinaryTrim, greaterThanOrEqualTo(3L));\n  }\n\n  @Test\n  public void testXTrimWithParams() {\n    String key = \"testStream\";\n\n    // Populate the stream with more entries than we intend to keep\n    for (int i = 0; i < 10; i++) {\n      exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\" + i, \"value\" + i)));\n    }\n\n    Long sizeBeforeTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeBeforeTrim, equalTo(10L));\n\n    XTrimParams params = new XTrimParams().maxLen(6);\n\n    Long xtrim = exec(commandObjects.xtrim(key, params));\n    assertThat(xtrim, equalTo(4L));\n\n    Long sizeAfterTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterTrim, equalTo(6L));\n  }\n\n  @Test\n  public void testXTrimWithParamsBinary() {\n    String keyStr = \"testStream\";\n    byte[] key = keyStr.getBytes();\n\n    // Populate the stream with more entries than we intend to keep\n    for (int i = 0; i < 10; i++) {\n      exec(commandObjects.xadd(keyStr, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\" + i, \"value\" + i)));\n    }\n\n    Long sizeBeforeTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeBeforeTrim, equalTo(10L));\n\n    XTrimParams params = new XTrimParams().maxLen(6);\n\n    Long xtrimBinary = exec(commandObjects.xtrim(key, params));\n    assertThat(xtrimBinary, equalTo(4L));\n\n    Long sizeAfterTrim = exec(commandObjects.xlen(key));\n    assertThat(sizeAfterTrim, equalTo(6L));\n  }\n\n  @Test\n  public void testXClaim() throws InterruptedException {\n    String key = \"testStream\";\n    String group = \"testGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    StreamEntryID initialId = new StreamEntryID();\n\n    exec(commandObjects.xgroupCreate(key, group, initialId, true));\n\n    StreamEntryID messageId = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value2\")));\n\n    // Consumer1 reads the message to make it pending\n    Map<String, StreamEntryID> stream = Collections.singletonMap(key, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> readEntries = exec(\n        commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    assertThat(readEntries, hasSize(1));\n    assertThat(readEntries.get(0).getKey(), equalTo(key));\n    assertThat(readEntries.get(0).getValue(), hasSize(1));\n    assertThat(readEntries.get(0).getValue().get(0).getID(), equalTo(messageId));\n\n    Thread.sleep(200); // Wait a bit\n\n    // Claim the message for consumer2\n    List<StreamEntry> claimedMessages = exec(\n        commandObjects.xclaim(key, group, consumer2, 1, new XClaimParams(), messageId));\n\n    assertThat(claimedMessages, hasSize(1));\n    assertThat(claimedMessages.get(0).getID(), equalTo(messageId));\n  }\n\n  @Test\n  public void testXClaimBinary() throws InterruptedException {\n    String key = \"testStream\";\n    String group = \"testGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    StreamEntryID initialId = new StreamEntryID();\n\n    exec(commandObjects.xgroupCreate(key, group, initialId, true));\n\n    StreamEntryID messageId = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value2\")));\n\n    // Consumer1 reads the message to make it pending\n    Map<String, StreamEntryID> stream = Collections.singletonMap(key, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> readEntries = exec(\n        commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    assertThat(readEntries, hasSize(1));\n    assertThat(readEntries.get(0).getKey(), equalTo(key));\n    assertThat(readEntries.get(0).getValue(), hasSize(1));\n    assertThat(readEntries.get(0).getValue().get(0).getID(), equalTo(messageId));\n\n    Thread.sleep(200); // Wait a bit\n\n    byte[] bMessageId = messageId.toString().getBytes();\n\n    // Claim the message for consumer2\n    List<byte[]> claimedMessagesBytes = exec(\n        commandObjects.xclaim(key.getBytes(), group.getBytes(), consumer2.getBytes(), 1, new XClaimParams(), bMessageId));\n    assertThat(claimedMessagesBytes, hasSize(1));\n    // Good luck with asserting the content of this!\n  }\n\n  @Test\n  public void testXClaimJustId() throws InterruptedException {\n    String key = \"testStream\";\n    String group = \"testGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    StreamEntryID initialId = new StreamEntryID();\n\n    exec(commandObjects.xgroupCreate(key, group, initialId, true));\n\n    StreamEntryID messageId = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value2\")));\n\n    // Consumer1 reads the message to make it pending\n    Map<String, StreamEntryID> stream = Collections.singletonMap(key, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> readEntries = exec(\n        commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    assertThat(readEntries, hasSize(1));\n    assertThat(readEntries.get(0).getKey(), equalTo(key));\n    assertThat(readEntries.get(0).getValue(), hasSize(1));\n    assertThat(readEntries.get(0).getValue().get(0).getID(), equalTo(messageId));\n\n    Thread.sleep(200); // Wait a bit\n\n    // Claim the message for consumer2 with String parameters\n    List<StreamEntryID> claimedMessagesString = exec(\n        commandObjects.xclaimJustId(key, group, consumer2, 1, new XClaimParams(), messageId));\n    assertThat(claimedMessagesString, hasSize(1));\n    assertThat(claimedMessagesString.get(0), equalTo(messageId));\n  }\n\n  @Test\n  public void testXClaimJustIdBinary() throws InterruptedException {\n    String key = \"testStream\";\n    String group = \"testGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    StreamEntryID initialId = new StreamEntryID();\n\n    exec(commandObjects.xgroupCreate(key, group, initialId, true));\n\n    StreamEntryID messageId = exec(commandObjects.xadd(key, StreamEntryID.NEW_ENTRY, Collections.singletonMap(\"field\", \"value2\")));\n\n    // Consumer1 reads the message to make it pending\n    Map<String, StreamEntryID> stream = Collections.singletonMap(key, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> readEntries = exec(\n        commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    assertThat(readEntries, hasSize(1));\n    assertThat(readEntries.get(0).getKey(), equalTo(key));\n    assertThat(readEntries.get(0).getValue(), hasSize(1));\n    assertThat(readEntries.get(0).getValue().get(0).getID(), equalTo(messageId));\n\n    Thread.sleep(200); // Wait a bit\n\n    byte[] bMessageId = messageId.toString().getBytes();\n\n    // Claim the message for consumer2 with byte[] parameters\n    List<byte[]> claimedMessagesBytes = exec(\n        commandObjects.xclaimJustId(key.getBytes(), group.getBytes(), consumer2.getBytes(), 1, new XClaimParams(), bMessageId));\n    assertThat(claimedMessagesBytes, hasSize(1));\n    // Good luck with asserting the content of this!\n  }\n\n  @Test\n  public void testXAutoClaim() throws InterruptedException {\n    String streamKey = \"testStream\";\n    String group = \"testGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    exec(commandObjects.xgroupCreate(streamKey, group, new StreamEntryID(), true));\n\n    Map<String, String> messageBody = Collections.singletonMap(\"field\", \"value\");\n    StreamEntryID initialEntryId = exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody));\n\n    Map<String, StreamEntryID> stream = Collections.singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    exec(commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    Thread.sleep(200); // Wait a bit\n\n    StreamEntryID startId = new StreamEntryID(initialEntryId.getTime() - 1, initialEntryId.getSequence());\n    XAutoClaimParams params = new XAutoClaimParams().count(1);\n\n    // Auto claim message for consumer2\n    Map.Entry<StreamEntryID, List<StreamEntry>> autoClaimResult = exec(\n        commandObjects.xautoclaim(streamKey, group, consumer2, 1, startId, params));\n\n    assertThat(autoClaimResult.getValue(), hasSize(1));\n    assertThat(autoClaimResult.getValue().get(0).getFields(), equalTo(messageBody));\n  }\n\n  @Test\n  public void testXAutoClaimBinary() throws InterruptedException {\n    byte[] streamKey = \"testStream\".getBytes();\n    byte[] group = \"testGroup\".getBytes();\n    byte[] consumer1 = \"consumer1\".getBytes();\n    byte[] consumer2 = \"consumer2\".getBytes();\n\n    exec(commandObjects.xgroupCreate(streamKey, group, new StreamEntryID().toString().getBytes(), true));\n\n    Map<byte[], byte[]> messageBody = Collections.singletonMap(\"field\".getBytes(), \"value\".getBytes());\n    byte[] initialEntryId = exec(commandObjects.xadd(streamKey, new XAddParams(), messageBody));\n\n    Map.Entry<byte[], byte[]> entry = new AbstractMap.SimpleEntry<>(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY.toString().getBytes());\n    exec(commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), entry));\n\n    Thread.sleep(200); // Wait a bit\n\n    StreamEntryID initialStreamEntryID = new StreamEntryID(new String(initialEntryId));\n    byte[] startId = new StreamEntryID(initialStreamEntryID.getTime() - 1, 0).toString().getBytes();\n    XAutoClaimParams params = new XAutoClaimParams().count(1);\n\n    // Auto claim message for consumer2 in binary\n    List<Object> autoClaimResultBinary = exec(commandObjects.xautoclaim(streamKey, group, consumer2, 1, startId, params));\n    assertThat(autoClaimResultBinary, not(empty()));\n  }\n\n  @Test\n  public void testXAutoClaimJustId() throws InterruptedException {\n    String streamKey = \"testStream\";\n    String group = \"testGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    exec(commandObjects.xgroupCreate(streamKey, group, new StreamEntryID(), true));\n\n    Map<String, String> messageBody = Collections.singletonMap(\"fieldSingle\", \"valueSingle\");\n    StreamEntryID initialEntryId = exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody));\n\n    Map<String, StreamEntryID> stream = Collections.singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    exec(commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    Thread.sleep(200); // Wait a bit\n\n    StreamEntryID startId = new StreamEntryID(initialEntryId.getTime() - 1, initialEntryId.getSequence());\n    XAutoClaimParams params = new XAutoClaimParams().count(1);\n\n    Map.Entry<StreamEntryID, List<StreamEntryID>> autoClaimResult = exec(\n        commandObjects.xautoclaimJustId(streamKey, group, consumer2, 1, startId, params));\n\n    assertThat(autoClaimResult.getValue(), hasSize(1));\n    assertThat(autoClaimResult.getValue().get(0), equalTo(initialEntryId));\n  }\n\n  @Test\n  public void testXAutoClaimJustIdBinary() throws InterruptedException {\n    byte[] streamKey = \"testStream\".getBytes();\n    byte[] group = \"testGroup\".getBytes();\n    byte[] consumer1 = \"consumer1\".getBytes();\n    byte[] consumer2 = \"consumer2\".getBytes();\n\n    exec(commandObjects.xgroupCreate(streamKey, group, new StreamEntryID().toString().getBytes(), true));\n\n    Map<byte[], byte[]> messageBody = Collections.singletonMap(\"fieldBinary\".getBytes(), \"valueBinary\".getBytes());\n    byte[] initialEntryId = exec(commandObjects.xadd(streamKey, new XAddParams(), messageBody));\n\n    Map.Entry<byte[], byte[]> stream = new AbstractMap.SimpleEntry<>(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY.toString().getBytes());\n    exec(commandObjects.xreadGroup(group, consumer1, new XReadGroupParams().count(1), stream));\n\n    Thread.sleep(200); // Wait a bit\n\n    StreamEntryID initialStreamEntryID = new StreamEntryID(new String(initialEntryId));\n    byte[] startId = new StreamEntryID(initialStreamEntryID.getTime() - 1, 0).toString().getBytes();\n    XAutoClaimParams params = new XAutoClaimParams().count(1);\n\n    List<Object> autoClaimResultBinary = exec(\n        commandObjects.xautoclaimJustId(streamKey, group, consumer2, 1, startId, params));\n    assertThat(autoClaimResultBinary, not(empty()));\n  }\n\n  @Test\n  public void testXInfoStream() {\n    String streamKey = \"testStreamInfo\";\n\n    Map<String, String> messageBody = Collections.singletonMap(\"fieldInfo\", \"valueInfo\");\n\n    exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody));\n\n    StreamInfo streamInfo = exec(commandObjects.xinfoStream(streamKey));\n\n    assertThat(streamInfo, notNullValue());\n    assertThat(streamInfo.getLength(), equalTo(1L));\n    assertThat(streamInfo.getFirstEntry().getFields(), equalTo(messageBody));\n\n    Object streamInfoBinary = exec(commandObjects.xinfoStream(streamKey.getBytes()));\n    assertThat(streamInfoBinary, notNullValue());\n  }\n\n  @Test\n  public void testXInfoStreamFull() {\n    String streamKey = \"testStreamFullInfo\";\n\n    Map<String, String> messageBody = Collections.singletonMap(\"fieldFull\", \"valueFull\");\n\n    exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody));\n\n    StreamFullInfo streamFullInfo = exec(commandObjects.xinfoStreamFull(streamKey));\n\n    assertThat(streamFullInfo, notNullValue());\n    assertThat(streamFullInfo.getEntries(), not(empty()));\n    assertThat(streamFullInfo.getEntries().get(0).getFields(), equalTo(messageBody));\n\n    StreamFullInfo streamFullInfoWithCount = exec(commandObjects.xinfoStreamFull(streamKey, 1));\n    assertThat(streamFullInfoWithCount, notNullValue());\n    assertThat(streamFullInfoWithCount.getEntries(), hasSize(1));\n\n    Object streamInfoBinaryFull = exec(commandObjects.xinfoStreamFull(streamKey.getBytes()));\n    assertThat(streamInfoBinaryFull, notNullValue());\n\n    Object streamInfoBinaryFullWithCount = exec(commandObjects.xinfoStreamFull(streamKey.getBytes(), 1));\n    assertThat(streamInfoBinaryFullWithCount, notNullValue());\n  }\n\n  @Test\n  @Deprecated\n  public void testXInfoConsumersWithActiveConsumers() {\n    String streamKey = \"testStreamWithConsumers\";\n    String group = \"testConsumerGroup\";\n    String consumer1 = \"consumer1\";\n    String consumer2 = \"consumer2\";\n\n    Map<String, String> messageBody1 = Collections.singletonMap(\"field1\", \"value1\");\n    Map<String, String> messageBody2 = Collections.singletonMap(\"field2\", \"value2\");\n\n    exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody1));\n    exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody2));\n\n    exec(commandObjects.xgroupCreate(streamKey, group, new StreamEntryID(), true));\n\n    XReadGroupParams xReadGroupParams = new XReadGroupParams().count(1);\n    Map<String, StreamEntryID> stream = Collections.singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    exec(commandObjects.xreadGroup(group, consumer1, xReadGroupParams, stream));\n    exec(commandObjects.xreadGroup(group, consumer2, xReadGroupParams, stream));\n\n    List<StreamConsumersInfo> consumersInfoList = exec(commandObjects.xinfoConsumers(streamKey, group));\n    assertThat(consumersInfoList, notNullValue());\n    assertThat(consumersInfoList, hasSize(2));\n\n    Optional<StreamConsumersInfo> consumersInfo1 = consumersInfoList.stream().filter(c -> c.getName().equals(consumer1)).findFirst();\n    Optional<StreamConsumersInfo> consumersInfo2 = consumersInfoList.stream().filter(c -> c.getName().equals(consumer2)).findFirst();\n\n    assertThat(consumersInfo1.isPresent(), equalTo(true));\n    assertThat(consumersInfo1.get().getPending(), equalTo(1L));\n\n    assertThat(consumersInfo2.isPresent(), equalTo(true));\n    assertThat(consumersInfo2.get().getPending(), equalTo(1L));\n\n    List<StreamConsumerInfo> consumerInfoList = exec(commandObjects.xinfoConsumers2(streamKey, group));\n    assertThat(consumerInfoList, notNullValue());\n    assertThat(consumerInfoList, hasSize(2));\n\n    Optional<StreamConsumerInfo> consumerInfo1 = consumerInfoList.stream().filter(c -> c.getName().equals(consumer1)).findFirst();\n    Optional<StreamConsumerInfo> consumerInfo2 = consumerInfoList.stream().filter(c -> c.getName().equals(consumer2)).findFirst();\n\n    assertThat(consumerInfo1.isPresent(), equalTo(true));\n    assertThat(consumerInfo1.get().getPending(), equalTo(1L));\n\n    assertThat(consumerInfo2.isPresent(), equalTo(true));\n    assertThat(consumerInfo2.get().getPending(), equalTo(1L));\n\n    List<Object> consumersInfoBinary = exec(commandObjects.xinfoConsumers(streamKey.getBytes(), group.getBytes()));\n    assertThat(consumersInfoBinary, notNullValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testXRead() {\n    String streamKey1 = \"testStream1\";\n    String streamKey2 = \"testStream2\";\n\n    Map<String, String> messageBody1 = Collections.singletonMap(\"field1\", \"value1\");\n    Map<String, String> messageBody2 = Collections.singletonMap(\"field2\", \"value2\");\n\n    StreamEntryID messageId1 = exec(commandObjects.xadd(streamKey1, StreamEntryID.NEW_ENTRY, messageBody1));\n    StreamEntryID messageId2 = exec(commandObjects.xadd(streamKey2, StreamEntryID.NEW_ENTRY, messageBody2));\n\n    XReadParams params = XReadParams.xReadParams().count(1).block(1000);\n    Map<String, StreamEntryID> streams = new HashMap<>();\n    streams.put(streamKey1, new StreamEntryID());\n    streams.put(streamKey2, new StreamEntryID());\n\n    List<Map.Entry<String, List<StreamEntry>>> xread = exec(commandObjects.xread(params, streams));\n\n    assertThat(xread, not(empty()));\n    assertThat(xread.size(), equalTo(2));\n    assertThat(xread.get(0).getKey(), equalTo(streamKey1));\n    assertThat(xread.get(1).getKey(), equalTo(streamKey2));\n    assertThat(xread.get(0).getValue().get(0).getID(), equalTo(messageId1));\n    assertThat(xread.get(1).getValue().get(0).getID(), equalTo(messageId2));\n    assertThat(xread.get(0).getValue().get(0).getFields(), equalTo(messageBody1));\n    assertThat(xread.get(1).getValue().get(0).getFields(), equalTo(messageBody2));\n\n    byte[] streamKey1Binary = streamKey1.getBytes();\n    byte[] streamKey2Binary = streamKey2.getBytes();\n    Map.Entry<byte[], byte[]> stream1 = new AbstractMap.SimpleEntry<>(streamKey1Binary, new StreamEntryID().toString().getBytes());\n    Map.Entry<byte[], byte[]> stream2 = new AbstractMap.SimpleEntry<>(streamKey2Binary, new StreamEntryID().toString().getBytes());\n\n    List<Object> xreadBinary = exec(commandObjects.xread(params, stream1, stream2));\n    assertThat(xreadBinary, not(empty()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testXReadAsMap() {\n    String streamKey1 = \"testStreamMap1\";\n    String streamKey2 = \"testStreamMap2\";\n\n    Map<String, String> messageBody1 = Collections.singletonMap(\"fieldMap1\", \"valueMap1\");\n    Map<String, String> messageBody2 = Collections.singletonMap(\"fieldMap2\", \"valueMap2\");\n\n    exec(commandObjects.xadd(streamKey1, StreamEntryID.NEW_ENTRY, messageBody1));\n    exec(commandObjects.xadd(streamKey2, StreamEntryID.NEW_ENTRY, messageBody2));\n\n    XReadParams params = new XReadParams().count(1).block(1000);\n\n    Map<String, StreamEntryID> streams = new HashMap<>();\n    streams.put(streamKey1, new StreamEntryID());\n    streams.put(streamKey2, new StreamEntryID());\n\n    Map<String, List<StreamEntry>> xreadAsMap = exec(commandObjects.xreadAsMap(params, streams));\n    assertThat(xreadAsMap, notNullValue());\n    assertThat(xreadAsMap.keySet(), hasSize(2)); // Expecting keys for both streams\n    assertThat(xreadAsMap.get(streamKey1).get(0).getFields(), equalTo(messageBody1));\n    assertThat(xreadAsMap.get(streamKey2).get(0).getFields(), equalTo(messageBody2));\n  }\n\n  @Test\n  public void testXReadGroupAsMap() {\n    String streamKey = \"testStreamGroupMap\";\n    String group = \"testGroupMap\";\n    String consumer1 = \"testConsumerMap1\";\n    String consumer2 = \"testConsumerMap2\";\n\n    Map<String, String> messageBody = Collections.singletonMap(\"fieldGroupMap\", \"valueGroupMap\");\n\n    exec(commandObjects.xgroupCreate(streamKey, group, new StreamEntryID(), true));\n\n    StreamEntryID initialMessageId = exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody));\n    StreamEntryID secondMessageId = exec(commandObjects.xadd(streamKey, StreamEntryID.NEW_ENTRY, messageBody));\n\n    XReadGroupParams params = new XReadGroupParams().count(1);\n\n    Map<String, StreamEntryID> streams = new HashMap<>();\n    streams.put(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    Map<String, List<StreamEntry>> xreadGroupConsumer1 = exec(commandObjects.xreadGroupAsMap(group, consumer1, params, streams));\n\n    assertThat(xreadGroupConsumer1, notNullValue());\n    assertThat(xreadGroupConsumer1.keySet(), hasSize(1));\n    assertThat(xreadGroupConsumer1.get(streamKey), not(empty()));\n    assertThat(xreadGroupConsumer1.get(streamKey).get(0).getID(), equalTo(initialMessageId));\n    assertThat(xreadGroupConsumer1.get(streamKey).get(0).getFields(), equalTo(messageBody));\n\n    Map<String, List<StreamEntry>> xreadGroupConsumer2 = exec(commandObjects.xreadGroupAsMap(group, consumer2, params, streams));\n\n    assertThat(xreadGroupConsumer2, notNullValue());\n    assertThat(xreadGroupConsumer2.keySet(), hasSize(1)); // Expecting keys for the stream\n    assertThat(xreadGroupConsumer2.get(streamKey), not(empty())); // Expecting at least one message\n    assertThat(xreadGroupConsumer2.get(streamKey).get(0).getID(), equalTo(secondMessageId));\n    assertThat(xreadGroupConsumer2.get(streamKey).get(0).getFields(), equalTo(messageBody));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsStringCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.resps.LCSMatchResult;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=string\">String</a> commands.\n */\n@Tag(\"integration\")\npublic class CommandObjectsStringCommandsTest extends CommandObjectsStandaloneTestBase {\n\n  public CommandObjectsStringCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testAppend() {\n    String key = \"testKey\";\n    String value = \"testValue\";\n\n    String initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, nullValue());\n\n    Long append = exec(commandObjects.append(key, value));\n    assertThat(append, equalTo((long) value.length()));\n\n    String getAfterAppend = exec(commandObjects.get(key));\n    assertThat(getAfterAppend, equalTo(value));\n\n    Long secondAppend = exec(commandObjects.append(key, value));\n    assertThat(secondAppend, equalTo((long) value.length() * 2));\n\n    String getAfterSecondAppend = exec(commandObjects.get(key));\n    assertThat(getAfterSecondAppend, equalTo(value + value));\n  }\n\n  @Test\n  public void testAppendBinary() {\n    byte[] key = \"testKeyBytes\".getBytes();\n    byte[] value = \"testValueBytes\".getBytes();\n\n    byte[] initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, nullValue());\n\n    Long append = exec(commandObjects.append(key, value));\n    assertThat(append, equalTo((long) value.length));\n\n    byte[] getAfterAppend = exec(commandObjects.get(key));\n    assertThat(getAfterAppend, equalTo(value));\n\n    Long secondAppend = exec(commandObjects.append(key, value));\n    assertThat(secondAppend, equalTo((long) value.length * 2));\n\n    byte[] getAfterSecondAppend = exec(commandObjects.get(key));\n\n    byte[] expected = new byte[value.length + value.length];\n    System.arraycopy(value, 0, expected, 0, value.length);\n    System.arraycopy(value, 0, expected, value.length, value.length);\n\n    assertThat(getAfterSecondAppend, equalTo(expected));\n  }\n\n  @Test\n  public void testDecrementOperations() {\n    String key = \"testDecr\";\n\n    exec(commandObjects.set(key, String.valueOf(10L)));\n\n    String initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(\"10\"));\n\n    Long decr = exec(commandObjects.decr(key));\n    assertThat(decr, equalTo(9L));\n\n    String getAfterDecr = exec(commandObjects.get(key));\n    assertThat(getAfterDecr, equalTo(\"9\"));\n\n    Long decrBy = exec(commandObjects.decrBy(key, 2L));\n    assertThat(decrBy, equalTo(7L));\n\n    String getAfterDecrBy = exec(commandObjects.get(key));\n    assertThat(getAfterDecrBy, equalTo(\"7\"));\n  }\n\n  @Test\n  public void testDecrementOperationsBinary() {\n    byte[] key = \"testDecrBytes\".getBytes();\n\n    exec(commandObjects.set(key, String.valueOf(10L).getBytes()));\n\n    byte[] initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(\"10\".getBytes()));\n\n    Long decr = exec(commandObjects.decr(key));\n    assertThat(decr, equalTo(9L));\n\n    byte[] getAfterDecr = exec(commandObjects.get(key));\n    assertThat(getAfterDecr, equalTo(\"9\".getBytes()));\n\n    Long decrBy = exec(commandObjects.decrBy(key, 2L));\n    assertThat(decrBy, equalTo(7L));\n\n    byte[] getAfterDecrBy = exec(commandObjects.get(key));\n    assertThat(getAfterDecrBy, equalTo(\"7\".getBytes()));\n  }\n\n  @Test\n  public void testGetOperations() {\n    String key = \"testGet\";\n    String value = \"value\";\n\n    exec(commandObjects.set(key, value));\n\n    String initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(value));\n\n    String getDel = exec(commandObjects.getDel(key));\n    assertThat(getDel, equalTo(value));\n\n    String getAfterGetDel = exec(commandObjects.get(key));\n    assertThat(getAfterGetDel, nullValue());\n\n    // set again\n    exec(commandObjects.set(key, value));\n\n    Long initialTtl = exec(commandObjects.ttl(key));\n    assertThat(initialTtl, equalTo(-1L));\n\n    GetExParams getExParams = GetExParams.getExParams().ex(10);\n    String getEx = exec(commandObjects.getEx(key, getExParams));\n    assertThat(getEx, equalTo(value));\n\n    Long ttlAfterGetEx = exec(commandObjects.ttl(key));\n    assertThat(ttlAfterGetEx, greaterThan(0L));\n  }\n\n  @Test\n  public void testGetOperationsBinary() {\n    byte[] key = \"testGetBytes\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    exec(commandObjects.set(key, value));\n\n    byte[] initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(value));\n\n    byte[] getDel = exec(commandObjects.getDel(key));\n    assertThat(getDel, equalTo(value));\n\n    byte[] getAfterGetDel = exec(commandObjects.get(key));\n    assertThat(getAfterGetDel, nullValue());\n\n    // set again\n    exec(commandObjects.set(key, value));\n\n    Long initialTtl = exec(commandObjects.ttl(key));\n    assertThat(initialTtl, equalTo(-1L));\n\n    GetExParams getExParams = GetExParams.getExParams().ex(10);\n    byte[] getEx = exec(commandObjects.getEx(key, getExParams));\n    assertThat(getEx, equalTo(value));\n\n    Long ttlAfterGetEx = exec(commandObjects.ttl(key));\n    assertThat(ttlAfterGetEx, greaterThan(0L));\n  }\n\n  @Test\n  @Deprecated\n  public void testGetSet() {\n    String key = \"testGetSet\";\n    String initialValue = \"initialValue\";\n    String newValue = \"newValue\";\n\n    exec(commandObjects.set(key, initialValue));\n\n    String initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(initialValue));\n\n    String getSet = exec(commandObjects.getSet(key, newValue));\n    assertThat(getSet, equalTo(initialValue));\n\n    String getAfterGetSet = exec(commandObjects.get(key));\n    assertThat(getAfterGetSet, equalTo(newValue));\n  }\n\n  @Test\n  @Deprecated\n  public void testGetSetBinary() {\n    byte[] key = \"testGetSetBytes\".getBytes();\n    byte[] initialValue = \"initialValue\".getBytes();\n    byte[] newValue = \"newValue\".getBytes();\n\n    exec(commandObjects.set(key, initialValue));\n\n    byte[] initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(initialValue));\n\n    byte[] getSet = exec(commandObjects.getSet(key, newValue));\n    assertThat(getSet, equalTo(initialValue));\n\n    byte[] getAfterGetSet = exec(commandObjects.get(key));\n    assertThat(getAfterGetSet, equalTo(newValue));\n  }\n\n  @Test\n  public void testSetRangeAndGetRange() {\n    String key = \"testRange\";\n    String initial = \"Hello World\";\n    String replacement = \"Everyone\";\n    long replacementOffset = 6L;\n\n    exec(commandObjects.set(key, initial));\n\n    Long setRange = exec(commandObjects.setrange(key, replacementOffset, replacement));\n    assertThat(setRange, equalTo(14L)); // Length after replacement\n\n    String getRange = exec(commandObjects.getrange(key, 0, -1));\n    assertThat(getRange, equalTo(\"Hello Everyone\"));\n  }\n\n  @Test\n  public void testSetRangeAndGetRangeBinary() {\n    byte[] key = \"testRangeBytes\".getBytes();\n    byte[] initialValue = \"Hello World\".getBytes();\n    byte[] replacement = \"Everyone\".getBytes();\n    long replacementOffset = 6L;\n\n    exec(commandObjects.set(key, initialValue));\n\n    Long setRange = exec(commandObjects.setrange(key, replacementOffset, replacement));\n    assertThat(setRange, equalTo(14L)); // Length after replacement\n\n    byte[] getRange = exec(commandObjects.getrange(key, 0, -1));\n    assertThat(getRange, equalTo(\"Hello Everyone\".getBytes()));\n  }\n\n  @Test\n  public void testIncrementOperations() {\n    String key = \"testIncr\";\n\n    exec(commandObjects.set(key, \"0\"));\n\n    Long incr = exec(commandObjects.incr(key));\n    assertThat(incr, equalTo(1L));\n\n    String getAfterIncr = exec(commandObjects.get(key));\n    assertThat(getAfterIncr, equalTo(\"1\"));\n\n    Long incrBy = exec(commandObjects.incrBy(key, 5L));\n    assertThat(incrBy, equalTo(6L));\n\n    String getAfterIncrBy = exec(commandObjects.get(key));\n    assertThat(getAfterIncrBy, equalTo(\"6\"));\n\n    Double incrByFloat = exec(commandObjects.incrByFloat(key, 2.5));\n    assertThat(incrByFloat, closeTo(8.5, 0.001));\n\n    String getAfterIncrByFloat = exec(commandObjects.get(key));\n    assertThat(getAfterIncrByFloat, equalTo(\"8.5\"));\n  }\n\n  @Test\n  public void testIncrementOperationsBinary() {\n    byte[] key = \"testIncrBytes\".getBytes();\n\n    exec(commandObjects.set(key, \"0\".getBytes()));\n\n    Long incr = exec(commandObjects.incr(key));\n    assertThat(incr, equalTo(1L));\n\n    byte[] getAfterIncr = exec(commandObjects.get(key));\n    assertThat(getAfterIncr, equalTo(\"1\".getBytes()));\n\n    Long incrBy = exec(commandObjects.incrBy(key, 5L));\n    assertThat(incrBy, equalTo(6L));\n\n    byte[] getAfterIncrBy = exec(commandObjects.get(key));\n    assertThat(getAfterIncrBy, equalTo(\"6\".getBytes()));\n\n    Double incrByFloat = exec(commandObjects.incrByFloat(key, 2.5));\n    assertThat(incrByFloat, closeTo(8.5, 0.001));\n\n    byte[] getAfterIncrByFloat = exec(commandObjects.get(key));\n    assertThat(getAfterIncrByFloat, equalTo(\"8.5\".getBytes()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testLcs() {\n    String keyA = \"keyA\";\n    String keyB = \"keyB\";\n\n    // \"abcdfg\" is the common substring\n    String valueA = \"abcdfgh\";\n    String valueB = \"abcdefg\";\n\n    exec(commandObjects.set(keyA, valueA));\n    exec(commandObjects.set(keyB, valueB));\n\n    LCSMatchResult lcsLen = exec(commandObjects.lcs(keyA, keyB, new LCSParams().len()));\n    assertThat(lcsLen.getLen(), equalTo(6L));\n    assertThat(lcsLen.getMatchString(), nullValue());\n\n    LCSMatchResult lcs = exec(commandObjects.lcs(keyA, keyB, new LCSParams()));\n    assertThat(lcs.getLen(), equalTo(0L));\n    assertThat(lcs.getMatchString(), equalTo(\"abcdfg\"));\n\n    LCSMatchResult lcsMatches = exec(\n      commandObjects.lcs(keyA, keyB, new LCSParams().idx().withMatchLen()));\n    assertThat(lcsMatches.getLen(), equalTo(6L));\n    assertThat(lcsMatches.getMatchString(), nullValue());\n    assertThat(lcsMatches.getMatches(), hasSize(2));\n\n    LCSMatchResult.MatchedPosition match1 = lcsMatches.getMatches().get(0);\n    assertThat(match1.getMatchLen(), equalTo(2L));\n    assertThat(match1.getA().getStart(), equalTo(4L));\n    assertThat(match1.getA().getEnd(), equalTo(5L));\n    assertThat(match1.getB().getStart(), equalTo(5L));\n    assertThat(match1.getB().getEnd(), equalTo(6L));\n\n    LCSMatchResult.MatchedPosition match2 = lcsMatches.getMatches().get(1);\n    assertThat(match2.getMatchLen(), equalTo(4L));\n    assertThat(match2.getA().getStart(), equalTo(0L));\n    assertThat(match2.getA().getEnd(), equalTo(3L));\n    assertThat(match2.getB().getStart(), equalTo(0L));\n    assertThat(match2.getB().getEnd(), equalTo(3L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testLcsBinary() {\n    byte[] keyA = \"keyA\".getBytes();\n    byte[] keyB = \"keyB\".getBytes();\n\n    // \"abcdfg\" is the common substring\n    String valueA = \"abcdfgh\";\n    String valueB = \"abcdefg\";\n\n    exec(commandObjects.set(keyA, valueA.getBytes()));\n    exec(commandObjects.set(keyB, valueB.getBytes()));\n\n    LCSMatchResult lcsLen = exec(commandObjects.lcs(keyA, keyB, new LCSParams().len()));\n    assertThat(lcsLen.getLen(), equalTo(6L));\n    assertThat(lcsLen.getMatchString(), nullValue());\n\n    LCSMatchResult lcs = exec(commandObjects.lcs(keyA, keyB, new LCSParams()));\n    assertThat(lcs.getLen(), equalTo(0L));\n    assertThat(lcs.getMatchString(), equalTo(\"abcdfg\"));\n\n    LCSMatchResult lcsMatches = exec(\n      commandObjects.lcs(keyA, keyB, new LCSParams().idx().withMatchLen()));\n    assertThat(lcsMatches.getLen(), equalTo(6L));\n    assertThat(lcsMatches.getMatchString(), nullValue());\n    assertThat(lcsMatches.getMatches(), hasSize(2));\n\n    LCSMatchResult.MatchedPosition match1 = lcsMatches.getMatches().get(0);\n    assertThat(match1.getMatchLen(), equalTo(2L));\n    assertThat(match1.getA().getStart(), equalTo(4L));\n    assertThat(match1.getA().getEnd(), equalTo(5L));\n    assertThat(match1.getB().getStart(), equalTo(5L));\n    assertThat(match1.getB().getEnd(), equalTo(6L));\n\n    LCSMatchResult.MatchedPosition match2 = lcsMatches.getMatches().get(1);\n    assertThat(match2.getMatchLen(), equalTo(4L));\n    assertThat(match2.getA().getStart(), equalTo(0L));\n    assertThat(match2.getA().getEnd(), equalTo(3L));\n    assertThat(match2.getB().getStart(), equalTo(0L));\n    assertThat(match2.getB().getEnd(), equalTo(3L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testMgetMsetAndMsetnx() {\n    String key1 = \"key1\";\n    String key2 = \"key2\";\n\n    String mset = exec(commandObjects.mset(key1, \"value1\", key2, \"value2\"));\n    assertThat(mset, equalTo(\"OK\"));\n\n    List<String> mget = exec(commandObjects.mget(key1, key2));\n    assertThat(mget, contains(\"value1\", \"value2\"));\n\n    Long msetNx = exec(commandObjects.msetnx(key1, \"new1\", key2, \"new2\"));\n    assertThat(msetNx, equalTo(0L));\n\n    List<String> mgetAfterMsetNx = exec(commandObjects.mget(key1, key2));\n    assertThat(mgetAfterMsetNx, contains(\"value1\", \"value2\"));\n\n    Long del = exec(commandObjects.del(key1, key2));\n    assertThat(del, equalTo(2L));\n\n    List<String> mgetAfterDel = exec(commandObjects.mget(key1, key2));\n    assertThat(mgetAfterDel, contains(nullValue(), nullValue()));\n\n    Long msetNxAfterDel = exec(commandObjects.msetnx(key1, \"new1\", key2, \"new2\"));\n    assertThat(msetNxAfterDel, equalTo(1L));\n\n    List<String> mgetAfterMsetNxAfterDel = exec(commandObjects.mget(key1, key2));\n    assertThat(mgetAfterMsetNxAfterDel, contains(\"new1\", \"new2\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testMgetMsetAndMsetnxBinary() {\n    byte[] key1 = \"key1Bytes\".getBytes();\n    byte[] key2 = \"key2Bytes\".getBytes();\n\n    String mset = exec(commandObjects.mset(key1, \"value1\".getBytes(), key2, \"value2\".getBytes()));\n    assertThat(mset, equalTo(\"OK\"));\n\n    List<byte[]> mget = exec(commandObjects.mget(key1, key2));\n    assertThat(mget, contains(\"value1\".getBytes(), \"value2\".getBytes()));\n\n    Long msetNx = exec(commandObjects.msetnx(key1, \"new1\".getBytes(), key2, \"new2\".getBytes()));\n    assertThat(msetNx, equalTo(0L));\n\n    List<byte[]> mgetAfterMsetNx = exec(commandObjects.mget(key1, key2));\n    assertThat(mgetAfterMsetNx, contains(\"value1\".getBytes(), \"value2\".getBytes()));\n\n    Long del = exec(commandObjects.del(key1, key2));\n    assertThat(del, equalTo(2L));\n\n    List<byte[]> mgetAfterDel = exec(commandObjects.mget(key1, key2));\n    assertThat(mgetAfterDel, contains(nullValue(), nullValue()));\n\n    Long msetNxAfterDel = exec(\n      commandObjects.msetnx(key1, \"new1\".getBytes(), key2, \"new2\".getBytes()));\n    assertThat(msetNxAfterDel, equalTo(1L));\n\n    List<byte[]> mgetAfterMsetNxAfterDel = exec(commandObjects.mget(key1, key2));\n    assertThat(mgetAfterMsetNxAfterDel, contains(\"new1\".getBytes(), \"new2\".getBytes()));\n  }\n\n  @Test\n  public void testPsetexPttl() {\n    String key = \"tempKey\";\n    long milliseconds = 1000L;\n\n    String psetEx = exec(commandObjects.psetex(key, milliseconds, \"tempValue\"));\n    assertThat(psetEx, equalTo(\"OK\"));\n\n    Long pttl = exec(commandObjects.pttl(key));\n    assertThat(pttl, greaterThan(0L));\n  }\n\n  @Test\n  public void testPsetexPttlBinary() {\n    byte[] key = \"tempKey\".getBytes();\n    long milliseconds = 1000L;\n\n    String psetEx = exec(commandObjects.psetex(key, milliseconds, \"tempValue\".getBytes()));\n    assertThat(psetEx, equalTo(\"OK\"));\n\n    Long pttl = exec(commandObjects.pttl(key));\n    assertThat(pttl, greaterThan(0L));\n  }\n\n  @Test\n  public void testSetAndSetGet() {\n    String key = \"myKey\";\n\n    String set = exec(commandObjects.set(key, \"firstValue\"));\n    assertThat(set, equalTo(\"OK\"));\n\n    String initialGet = exec(commandObjects.get(key));\n    assertThat(initialGet, equalTo(\"firstValue\"));\n\n    SetParams setParams = new SetParams().ex(10);\n    String setWithParams = exec(commandObjects.set(key, \"secondValue\", setParams));\n    assertThat(setWithParams, equalTo(\"OK\"));\n\n    String getAfterSetWithParams = exec(commandObjects.get(key));\n    assertThat(getAfterSetWithParams, equalTo(\"secondValue\"));\n\n    String setGet = exec(commandObjects.setGet(key, \"thirdValue\"));\n    assertThat(setGet, equalTo(\"secondValue\"));\n\n    String getAfterSetGet = exec(commandObjects.get(key));\n    assertThat(getAfterSetGet, equalTo(\"thirdValue\"));\n\n    String setGetWithParams = exec(commandObjects.setGet(key, \"finalValue\", setParams));\n    assertThat(setGetWithParams, equalTo(\"thirdValue\"));\n\n    String finalGet = exec(commandObjects.get(key));\n    assertThat(finalGet, equalTo(\"finalValue\"));\n  }\n\n  @Test\n  public void testSetAndSetGetBinary() {\n    byte[] key = \"myKeyBytes\".getBytes();\n\n    String set = exec(commandObjects.set(key, \"firstValue\".getBytes()));\n    assertThat(set, equalTo(\"OK\"));\n\n    byte[] getAfterSet = exec(commandObjects.get(key));\n    assertThat(getAfterSet, equalTo(\"firstValue\".getBytes()));\n\n    SetParams setParams = new SetParams().ex(10);\n    String setWithParams = exec(commandObjects.set(key, \"secondValue\".getBytes(), setParams));\n    assertThat(setWithParams, equalTo(\"OK\"));\n\n    byte[] getAfterSetWithParams = exec(commandObjects.get(key));\n    assertThat(getAfterSetWithParams, equalTo(\"secondValue\".getBytes()));\n\n    byte[] setGet = exec(commandObjects.setGet(key, \"thirdValue\".getBytes()));\n    assertThat(setGet, equalTo(\"secondValue\".getBytes()));\n\n    byte[] getAfterSetGet = exec(commandObjects.get(key));\n    assertThat(getAfterSetGet, equalTo(\"thirdValue\".getBytes()));\n\n    byte[] setGetWithParams = exec(commandObjects.setGet(key, \"finalValue\".getBytes(), setParams));\n    assertThat(setGetWithParams, equalTo(\"thirdValue\".getBytes()));\n\n    byte[] getAfterSetGetWithParams = exec(commandObjects.get(key));\n    assertThat(getAfterSetGetWithParams, equalTo(\"finalValue\".getBytes()));\n  }\n\n  @Test\n  public void testSetnxAndSetexWithGets() {\n    String key = \"uniqueKey\";\n\n    Long setNx = exec(commandObjects.setnx(key, \"helloWorld\"));\n    assertThat(setNx, equalTo(1L));\n\n    String getAfterSetNx = exec(commandObjects.get(key));\n    assertThat(getAfterSetNx, equalTo(\"helloWorld\"));\n\n    String setEx = exec(commandObjects.setex(key, 10L, \"newValue\"));\n    assertThat(setEx, equalTo(\"OK\"));\n\n    String getAfterSetEx = exec(commandObjects.get(key));\n    assertThat(getAfterSetEx, equalTo(\"newValue\"));\n\n    Long setNxAgain = exec(commandObjects.setnx(key, \"anotherNewValue\"));\n    assertThat(setNxAgain, equalTo(0L));\n\n    String getAfterSetNxAgain = exec(commandObjects.get(key));\n    assertThat(getAfterSetNxAgain, equalTo(\"newValue\"));\n  }\n\n  @Test\n  public void testSetnxAndSetexWithGetsBinary() {\n    byte[] key = \"uniqueKeyBytes\".getBytes();\n\n    Long setNx = exec(commandObjects.setnx(key, \"helloWorld\".getBytes()));\n    assertThat(setNx, equalTo(1L));\n\n    byte[] getAfterSetNx = exec(commandObjects.get(key));\n    assertThat(getAfterSetNx, equalTo(\"helloWorld\".getBytes()));\n\n    String setEx = exec(commandObjects.setex(key, 10L, \"newValue\".getBytes()));\n    assertThat(setEx, equalTo(\"OK\"));\n\n    byte[] getAfterSetEx = exec(commandObjects.get(key));\n    assertThat(getAfterSetEx, equalTo(\"newValue\".getBytes()));\n\n    Long setNxAgain = exec(commandObjects.setnx(key, \"anotherNewValueBytes\".getBytes()));\n    assertThat(setNxAgain, equalTo(0L));\n\n    byte[] getAfterSetNxAgain = exec(commandObjects.get(key));\n    assertThat(getAfterSetNxAgain, equalTo(\"newValue\".getBytes()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSubstrAndStrlen() {\n    String key = \"testKey\";\n    String value = \"HelloWorld\";\n\n    int start = 1;\n    int end = 5; // end is inclusive\n    String fragment = \"elloW\";\n\n    exec(commandObjects.set(key, value));\n\n    String substr = exec(commandObjects.substr(key, start, end));\n    assertThat(substr, equalTo(fragment));\n\n    byte[] substrBinary = exec(commandObjects.substr(key.getBytes(), start, end));\n    assertThat(substrBinary, equalTo(fragment.getBytes()));\n\n    Long strlen = exec(commandObjects.strlen(key));\n    assertThat(strlen, equalTo((long) value.length()));\n\n    Long strlenBinary = exec(commandObjects.strlen(key.getBytes()));\n    assertThat(strlenBinary, equalTo((long) value.length()));\n  }\n\n  // MSETEX NX + expiration matrix\n  static Stream<Arguments> msetexNxArgsProvider() {\n    return Stream.of(Arguments.of(\"EX\", new MSetExParams().nx().ex(5)),\n      Arguments.of(\"PX\", new MSetExParams().nx().px(5000)),\n      Arguments.of(\"EXAT\", new MSetExParams().nx().exAt(System.currentTimeMillis() / 1000 + 5)),\n      Arguments.of(\"PXAT\", new MSetExParams().nx().pxAt(System.currentTimeMillis() + 5000)),\n      Arguments.of(\"KEEPTTL\", new MSetExParams().nx().keepTtl()));\n  }\n\n  @ParameterizedTest(name = \"MSETEX NX + {0}\")\n  @MethodSource(\"msetexNxArgsProvider\")\n  @EnabledOnCommand(value = \"MSETEX\")\n  public void testMsetexNx_parametrized(String optionLabel, MSetExParams params) {\n    String k1 = \"{t}msetex:k1\";\n    String k2 = \"{t}msetex:k2\";\n\n    Boolean result = exec(commandObjects.msetex(params, k1, \"v1\", k2, \"v2\"));\n    assertThat(result, equalTo(true));\n\n    Long ttl = exec(commandObjects.ttl(k1));\n    if (\"KEEPTTL\".equals(optionLabel)) {\n      assertThat(ttl, equalTo(-1L));\n    } else {\n      assertThat(ttl, greaterThan(0L));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsTDigestCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasKey;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.hamcrest.Matchers.notANumber;\nimport static org.hamcrest.Matchers.notNullValue;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.bloom.TDigestMergeParams;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=tdigest\">T-Digest</a> commands.\n */\npublic class CommandObjectsTDigestCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsTDigestCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testTDigestAddMinMax() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 1.0, 2.0, 3.0, 4.0, 5.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    Double minValue = exec(commandObjects.tdigestMin(key));\n    assertThat(minValue, equalTo(1.0));\n\n    Double maxValue = exec(commandObjects.tdigestMax(key));\n    assertThat(maxValue, equalTo(5.0));\n  }\n\n  @Test\n  public void testTDigestMerge() {\n    String destinationKey = \"testTDigestMergeDest\";\n    String sourceKey1 = \"testTDigestSource1\";\n    String sourceKey2 = \"testTDigestSource2\";\n\n    String create1 = exec(commandObjects.tdigestCreate(sourceKey1));\n    assertThat(create1, equalTo(\"OK\"));\n\n    String create2 = exec(commandObjects.tdigestCreate(sourceKey2));\n    assertThat(create2, equalTo(\"OK\"));\n\n    String add1 = exec(commandObjects.tdigestAdd(sourceKey1, 1.0, 2.0));\n    assertThat(add1, equalTo(\"OK\"));\n\n    String add2 = exec(commandObjects.tdigestAdd(sourceKey2, 3.0, 4.0));\n    assertThat(add2, equalTo(\"OK\"));\n\n    String merge = exec(commandObjects.tdigestMerge(destinationKey, sourceKey1, sourceKey2));\n    assertThat(merge, equalTo(\"OK\"));\n\n    Double minAfterMerge = exec(commandObjects.tdigestMin(destinationKey));\n    assertThat(minAfterMerge, equalTo(1.0));\n\n    Double maxAfterMerge = exec(commandObjects.tdigestMax(destinationKey));\n    assertThat(maxAfterMerge, equalTo(4.0));\n  }\n\n  @Test\n  public void testTDigestMergeWithParams() {\n    String destinationKey = \"testTDigestMergeDestParams\";\n    String sourceKey1 = \"testTDigestSource1Params\";\n    String sourceKey2 = \"testTDigestSource2Params\";\n\n    TDigestMergeParams mergeParams = new TDigestMergeParams().compression(100);\n\n    String create1 = exec(commandObjects.tdigestCreate(sourceKey1, 100));\n    assertThat(create1, equalTo(\"OK\"));\n\n    String create2 = exec(commandObjects.tdigestCreate(sourceKey2, 100));\n    assertThat(create2, equalTo(\"OK\"));\n\n    String add1 = exec(commandObjects.tdigestAdd(sourceKey1, 10.0, 20.0));\n    assertThat(add1, equalTo(\"OK\"));\n\n    String add2 = exec(commandObjects.tdigestAdd(sourceKey2, 30.0, 40.0));\n    assertThat(add2, equalTo(\"OK\"));\n\n    String merge = exec(commandObjects.tdigestMerge(mergeParams, destinationKey, sourceKey1, sourceKey2));\n    assertThat(merge, equalTo(\"OK\"));\n\n    Double minAfterMerge = exec(commandObjects.tdigestMin(destinationKey));\n    assertThat(minAfterMerge, equalTo(10.0));\n\n    Double maxAfterMerge = exec(commandObjects.tdigestMax(destinationKey));\n    assertThat(maxAfterMerge, equalTo(40.0));\n  }\n\n  @Test\n  public void testTDigestReset() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 5.0, 10.0, 15.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    Double minBeforeReset = exec(commandObjects.tdigestMin(key));\n    assertThat(minBeforeReset, equalTo(5.0));\n\n    Double maxBeforeReset = exec(commandObjects.tdigestMax(key));\n    assertThat(maxBeforeReset, equalTo(15.0));\n\n    String reset = exec(commandObjects.tdigestReset(key));\n    assertThat(reset, equalTo(\"OK\"));\n\n    Double minAfterReset = exec(commandObjects.tdigestMin(key));\n    assertThat(minAfterReset, notANumber());\n\n    Double maxAfterReset = exec(commandObjects.tdigestMax(key));\n    assertThat(maxAfterReset, notANumber());\n  }\n\n  @Test\n  public void testTDigestCdf() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 1.0, 2.0, 3.0, 4.0, 5.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    List<Double> cdf = exec(commandObjects.tdigestCDF(key, 1.0, 3.0, 5.0));\n\n    assertThat(cdf, notNullValue());\n    assertThat(cdf.size(), equalTo(3));\n    assertThat(cdf.get(0), lessThanOrEqualTo(0.2));\n    assertThat(cdf.get(1), lessThanOrEqualTo(0.6));\n    assertThat(cdf.get(2), lessThanOrEqualTo(1.0));\n  }\n\n  @Test\n  public void testTDigestQuantile() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 1.0, 2.0, 3.0, 4.0, 5.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    List<Double> quantile = exec(commandObjects.tdigestQuantile(key, 0.25, 0.5, 0.75));\n\n    assertThat(quantile, notNullValue());\n    assertThat(quantile.size(), equalTo(3));\n    assertThat(quantile.get(0), lessThanOrEqualTo(2.0));\n    assertThat(quantile.get(1), lessThanOrEqualTo(3.0));\n    assertThat(quantile.get(2), lessThanOrEqualTo(4.0));\n  }\n\n  @Test\n  public void testTDigestTrimmedMean() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 1.0, 2.0, 3.0, 4.0, 5.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    Double trimmedMean = exec(commandObjects.tdigestTrimmedMean(key, 0.1, 0.9));\n\n    assertThat(trimmedMean, notNullValue());\n    assertThat(trimmedMean, lessThanOrEqualTo(4.0));\n    assertThat(trimmedMean, greaterThanOrEqualTo(2.0));\n  }\n\n  @Test\n  public void testTDigestRankAndRevRank() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 1.0, 2.0, 3.0, 4.0, 5.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    List<Long> rank = exec(commandObjects.tdigestRank(key, 1.0, 3.0, 5.0));\n\n    assertThat(rank, notNullValue());\n    assertThat(rank.size(), equalTo(3));\n    assertThat(rank.get(0), lessThanOrEqualTo(1L));\n    assertThat(rank.get(1), lessThanOrEqualTo(3L));\n    assertThat(rank.get(2), lessThanOrEqualTo(5L));\n\n    List<Long> revRank = exec(commandObjects.tdigestRevRank(key, 1.0, 3.0, 5.0));\n\n    assertThat(revRank, notNullValue());\n    assertThat(revRank.size(), equalTo(3));\n    assertThat(revRank.get(0), greaterThanOrEqualTo(4L));\n    assertThat(revRank.get(1), greaterThanOrEqualTo(2L));\n    assertThat(revRank.get(2), greaterThanOrEqualTo(0L));\n  }\n\n  @Test\n  public void testTDigestByRankAndByRevRank() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 0.5, 1.5, 2.5, 3.5, 4.5));\n    assertThat(add, equalTo(\"OK\"));\n\n    List<Double> byRank = exec(commandObjects.tdigestByRank(key, 0, 2, 4));\n\n    assertThat(byRank, notNullValue());\n    assertThat(byRank.size(), equalTo(3));\n    assertThat(byRank.get(0), closeTo(0.5, 0.1));\n    assertThat(byRank.get(1), closeTo(2.5, 0.1));\n    assertThat(byRank.get(2), closeTo(4.5, 0.1));\n\n    List<Double> byRevRank = exec(commandObjects.tdigestByRevRank(key, 0, 2, 4));\n\n    assertThat(byRevRank, notNullValue());\n    assertThat(byRevRank.size(), equalTo(3));\n    assertThat(byRevRank.get(0), closeTo(4.5, 0.1));\n    assertThat(byRevRank.get(1), closeTo(2.5, 0.1));\n    assertThat(byRevRank.get(2), closeTo(0.5, 0.1));\n  }\n\n  @Test\n  public void testTDigestInfo() {\n    String key = \"testTDigest\";\n\n    String create = exec(commandObjects.tdigestCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    String add = exec(commandObjects.tdigestAdd(key, 1.0, 2.0, 3.0));\n    assertThat(add, equalTo(\"OK\"));\n\n    Map<String, Object> info = exec(commandObjects.tdigestInfo(key));\n\n    assertThat(info, notNullValue());\n    assertThat(info, hasKey(\"Compression\"));\n    assertThat(info, hasEntry(\"Observations\", 3L));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsTestBase.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\n\nimport java.util.Collection;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.commands.CommandsTestsParameters;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.executors.DefaultCommandExecutor;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\n\n/**\n * Base class for CommandObjects tests. The tests are parameterized to run with\n * several versions of RESP. The idea is to test commands at this low level, using\n * a simple executor. Higher level concepts like {@link redis.clients.jedis.UnifiedJedis},\n * or {@link redis.clients.jedis.PipeliningBase} can be tested separately with mocks.\n * <p>\n * This class provides the basic setup, except the {@link HostAndPort} for connecting\n * to a running Redis server. That one is provided by abstract subclasses, depending\n * on if a Redis Stack server is needed, or a standalone suffices.\n * <p>\n * In principle all subclasses of this class should be parameterized tests,\n * to run with several versions of RESP. {@link CommandsTestsParameters#respVersions}\n */\n@Tag(\"integration\")\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic abstract class CommandObjectsTestBase {\n\n  /**\n   * RESP protocol used in the tests. Injected from subclasses.\n   */\n  protected final RedisProtocol protocol;\n\n  /**\n   * Host and port of the Redis server to connect to. Injected from subclasses.\n   */\n  protected final EndpointConfig endpoint;\n\n  /**\n   * The {@link CommandObjects} to use for the tests. This is the subject-under-test.\n   */\n  protected final CommandObjects commandObjects;\n\n  /**\n   * A {@link CommandExecutor} that can execute commands against the running Redis server.\n   * Not exposed to subclasses, which should use a convenience method instead.\n   */\n  private CommandExecutor commandExecutor;\n\n  public CommandObjectsTestBase(RedisProtocol protocol, EndpointConfig endpoint) {\n    this.protocol = protocol;\n    this.endpoint = endpoint;\n    commandObjects = new CommandObjects();\n    commandObjects.setProtocol(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    // Configure a default command executor.\n    DefaultJedisClientConfig clientConfig = endpoint.getClientConfigBuilder().protocol(protocol)\n        .build();\n\n    ConnectionProvider connectionProvider = new PooledConnectionProvider(endpoint.getHostAndPort(),\n        clientConfig);\n\n    commandExecutor = new DefaultCommandExecutor(connectionProvider);\n\n    // Cleanup before each test.\n    assertThat(exec(commandObjects.flushAll()), equalTo(\"OK\"));\n  }\n\n  /**\n   * Convenience method for subclasses, for running any {@link CommandObject}.\n   */\n  protected <T> T exec(CommandObject<T> commandObject) {\n    return commandExecutor.executeCommand(commandObject);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsTimeSeriesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.AbstractMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.timeseries.AggregationType;\nimport redis.clients.jedis.timeseries.TSAlterParams;\nimport redis.clients.jedis.timeseries.TSCreateParams;\nimport redis.clients.jedis.timeseries.TSElement;\nimport redis.clients.jedis.timeseries.TSGetParams;\nimport redis.clients.jedis.timeseries.TSInfo;\nimport redis.clients.jedis.timeseries.TSMGetElement;\nimport redis.clients.jedis.timeseries.TSMGetParams;\nimport redis.clients.jedis.timeseries.TSMRangeElements;\nimport redis.clients.jedis.timeseries.TSMRangeParams;\nimport redis.clients.jedis.timeseries.TSRangeParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=timeseries\">Time series</a> commands.\n */\npublic class CommandObjectsTimeSeriesCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsTimeSeriesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testTsAddAndRange() throws InterruptedException {\n    String key = \"testTs\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    long currentTime = System.currentTimeMillis();\n    double[] values = { 42.0, 43.0, 44.0 };\n    for (double value : values) {\n      Long add = exec(commandObjects.tsAdd(key, value));\n      assertThat(add, notNullValue());\n\n      // short delay to avoid the same timestamp\n      Thread.sleep(10);\n    }\n\n    List<TSElement> range = exec(commandObjects.tsRange(key, currentTime - 1000, currentTime + 1000));\n\n    assertThat(range, hasSize(values.length));\n    for (int i = 0; i < values.length; i++) {\n      assertThat(range.get(i).getValue(), equalTo(values[i]));\n    }\n  }\n\n  @Test\n  public void testTsAddWithTimestampDelAndRangeWithPreDeleteAssert() {\n    String key = \"testTs\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    long timestamp1 = 1000;\n    double value1 = 42.0;\n\n    long timestamp2 = 2000;\n    double value2 = 43.0;\n\n    Long add1 = exec(commandObjects.tsAdd(key, timestamp1, value1));\n    assertThat(add1, notNullValue());\n\n    Long add2 = exec(commandObjects.tsAdd(key, timestamp2, value2));\n    assertThat(add2, notNullValue());\n\n    List<TSElement> preDelRange = exec(commandObjects.tsRange(key, timestamp1 - 500, timestamp2 + 500));\n\n    assertThat(preDelRange, hasSize(2));\n    assertThat(preDelRange.get(0).getValue(), equalTo(value1));\n    assertThat(preDelRange.get(1).getValue(), equalTo(value2));\n\n    Long del = exec(commandObjects.tsDel(key, timestamp1 - 500, timestamp1 + 500));\n    assertThat(del, equalTo(1L));\n\n    List<TSElement> postDelRange = exec(commandObjects.tsRange(key, timestamp1 - 500, timestamp2 + 500));\n\n    assertThat(postDelRange, hasSize(1));\n    assertThat(postDelRange.get(0).getValue(), equalTo(value2));\n  }\n\n  @Test\n  public void testTsAddWithParams() {\n    String key = \"testTs\";\n\n    long timestamp = System.currentTimeMillis();\n    double value = 42.0;\n\n    TSCreateParams createParams = new TSCreateParams()\n        .uncompressed().retention(86400000);\n\n    Long add = exec(commandObjects.tsAdd(key, timestamp, value, createParams));\n    assertThat(add, notNullValue());\n\n    List<TSElement> range = exec(commandObjects.tsRange(key, timestamp - 1000, timestamp + 1000));\n\n    assertThat(range, hasSize(1));\n    assertThat(range.get(0).getTimestamp(), equalTo(timestamp));\n    assertThat(range.get(0).getValue(), equalTo(value));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testTsMAdd() {\n    String key1 = \"testTsMAdd1\";\n    String key2 = \"testTsMAdd2\";\n\n    String create1 = exec(commandObjects.tsCreate(key1));\n    assertThat(create1, equalTo(\"OK\"));\n\n    String create2 = exec(commandObjects.tsCreate(key2));\n    assertThat(create2, equalTo(\"OK\"));\n\n    long timestamp1 = 2000;\n    long timestamp2 = 3000;\n\n    Map.Entry<String, TSElement> entry1 =\n        new AbstractMap.SimpleEntry<>(key1, new TSElement(timestamp1, 42.0));\n\n    Map.Entry<String, TSElement> entry2 =\n        new AbstractMap.SimpleEntry<>(key2, new TSElement(timestamp2, 43.0));\n\n    List<Long> mAdd = exec(commandObjects.tsMAdd(entry1, entry2));\n    assertThat(mAdd, contains(timestamp1, timestamp2));\n\n    List<TSElement> range1 = exec(commandObjects.tsRange(key1, timestamp1 - 1000, timestamp1 + 1000));\n\n    assertThat(range1, hasSize(1));\n    assertThat(range1.get(0).getTimestamp(), equalTo(timestamp1));\n    assertThat(range1.get(0).getValue(), equalTo(42.0));\n\n    List<TSElement> range2 = exec(commandObjects.tsRange(key2, timestamp2 - 1000, timestamp2 + 1000));\n\n    assertThat(range2, hasSize(1));\n    assertThat(range2.get(0).getTimestamp(), equalTo(timestamp2));\n    assertThat(range2.get(0).getValue(), equalTo(43.0));\n  }\n\n  @Test\n  public void testTsIncrByAndDecrBy() throws InterruptedException {\n    String key = \"testTs\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    double initialValue = 10.0;\n    double incrementValue = 5.0;\n    double decrementValue = 3.0;\n\n    Long initialAdd = exec(commandObjects.tsAdd(key, System.currentTimeMillis(), initialValue));\n    assertThat(initialAdd, notNullValue());\n\n    Thread.sleep(50);\n\n    Long incr = exec(commandObjects.tsIncrBy(key, incrementValue));\n    assertThat(incr, notNullValue());\n\n    Thread.sleep(50);\n\n    Long decr = exec(commandObjects.tsDecrBy(key, decrementValue));\n    assertThat(decr, notNullValue());\n\n    TSElement latestElement = exec(commandObjects.tsGet(key));\n    double expectedValue = initialValue + incrementValue - decrementValue;\n    assertThat(latestElement.getValue(), equalTo(expectedValue));\n\n    List<TSElement> range = exec(commandObjects.tsRange(\n        key, latestElement.getTimestamp() - 1000, latestElement.getTimestamp() + 1000));\n\n    assertThat(range.stream().map(TSElement::getValue).collect(Collectors.toList()), contains(\n        closeTo(initialValue, 0.001),\n        closeTo(initialValue + incrementValue, 0.001),\n        closeTo(expectedValue, 0.001)));\n  }\n\n  @Test\n  public void testTsIncrByAndDecrByWithTimestamp() {\n    String key = \"testTs\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    double initialValue = 10.0;\n    double incrementValue = 5.0;\n    double decrementValue = 3.0;\n\n    long initialTimestamp = System.currentTimeMillis();\n\n    Long initialAdd = exec(commandObjects.tsAdd(key, initialTimestamp, initialValue));\n    assertThat(initialAdd, equalTo(initialTimestamp));\n\n    long incrementTimestamp = initialTimestamp + 1000;\n\n    Long incr = exec(commandObjects.tsIncrBy(key, incrementValue, incrementTimestamp));\n    assertThat(incr, equalTo(incrementTimestamp));\n\n    long decrementTimestamp = incrementTimestamp + 1000;\n\n    Long decr = exec(commandObjects.tsDecrBy(key, decrementValue, decrementTimestamp));\n    assertThat(decr, equalTo(decrementTimestamp));\n\n    List<TSElement> range = exec(commandObjects.tsRange(\n        key, initialTimestamp - 1000, decrementTimestamp + 1000));\n\n    assertThat(range.stream().map(TSElement::getValue).collect(Collectors.toList()), contains(\n        closeTo(initialValue, 0.001),\n        closeTo(initialValue + incrementValue, 0.001),\n        closeTo(initialValue + incrementValue - decrementValue, 0.001)));\n  }\n\n  @Test\n  public void testTsRange() {\n    String key = \"tsKey\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    long fromTimestamp = 1000L;\n    long toTimestamp = 2000L;\n\n    List<TSElement> initialRange = exec(commandObjects.tsRange(key, fromTimestamp - 100, toTimestamp + 100));\n    assertThat(initialRange, hasSize(0));\n\n    exec(commandObjects.tsAdd(key, fromTimestamp, 1.0));\n    exec(commandObjects.tsAdd(key, toTimestamp, 2.0));\n\n    List<TSElement> elementsByTimestamp = exec(commandObjects.tsRange(key, fromTimestamp - 100, toTimestamp + 100));\n\n    assertThat(elementsByTimestamp.stream().map(TSElement::getValue).collect(Collectors.toList()), contains(\n        closeTo(1.0, 0.001),\n        closeTo(2.0, 0.001)));\n\n    TSRangeParams rangeParams = new TSRangeParams(fromTimestamp - 100, toTimestamp + 100);\n\n    List<TSElement> elementsByParams = exec(commandObjects.tsRange(key, rangeParams));\n\n    assertThat(elementsByParams.stream().map(TSElement::getValue).collect(Collectors.toList()), contains(\n        closeTo(1.0, 0.001),\n        closeTo(2.0, 0.001)));\n  }\n\n  @Test\n  public void testTsRevRange() {\n    String key = \"tsRevKey\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    long fromTimestamp = 1000L;\n    long toTimestamp = 2000L;\n\n    List<TSElement> initialRevRange = exec(commandObjects.tsRevRange(key, fromTimestamp - 100, toTimestamp + 100));\n    assertThat(initialRevRange, hasSize(0));\n\n    exec(commandObjects.tsAdd(key, fromTimestamp, 1.0));\n    exec(commandObjects.tsAdd(key, toTimestamp, 2.0));\n\n    List<TSElement> elementsByTimestamp = exec(commandObjects.tsRevRange(key, fromTimestamp - 100, toTimestamp + 100));\n\n    assertThat(elementsByTimestamp.stream().map(TSElement::getValue).collect(Collectors.toList()), contains(\n        closeTo(2.0, 0.001),\n        closeTo(1.0, 0.001)));\n\n    TSRangeParams rangeParams = new TSRangeParams(fromTimestamp - 100, toTimestamp + 100);\n\n    List<TSElement> elementsByParams = exec(commandObjects.tsRevRange(key, rangeParams));\n\n    assertThat(elementsByParams.stream().map(TSElement::getValue).collect(Collectors.toList()), contains(\n        closeTo(2.0, 0.001),\n        closeTo(1.0, 0.001)));\n  }\n\n  @Test\n  public void testTsMRangeCommands() {\n    String key1 = \"tsMRangeKey1\";\n    String key2 = \"tsMRangeKey2\";\n\n    long fromTimestamp = 1000L;\n    long toTimestamp = 3000L;\n\n    String filter = \"sensor_id=1234\";\n\n    Map<String, TSMRangeElements> initialMRange = exec(commandObjects.tsMRange(\n        fromTimestamp - 100, toTimestamp + 100, filter));\n    assertThat(initialMRange.entrySet(), hasSize(0));\n\n    TSCreateParams createParams = new TSCreateParams()\n        .uncompressed().label(\"sensor_id\", \"1234\");\n\n    exec(commandObjects.tsAdd(key1, fromTimestamp, 1.0, createParams));\n    exec(commandObjects.tsAdd(key1, fromTimestamp + 500, 1.5, createParams));\n    exec(commandObjects.tsAdd(key2, toTimestamp - 500, 2.5, createParams));\n    exec(commandObjects.tsAdd(key2, toTimestamp, 2.0, createParams));\n\n    Map<String, TSMRangeElements> range = exec(commandObjects.tsMRange(\n        fromTimestamp - 100, toTimestamp + 100, filter));\n\n    assertThat(range.keySet(), hasItems(key1, key2));\n    assertThat(range.get(key1).getElements().stream().map(TSElement::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(1.0, 0.001), closeTo(1.5, 0.001)));\n    assertThat(range.get(key2).getElements().stream().map(TSElement::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(2.5, 0.001), closeTo(2.0, 0.001)));\n\n    Map<String, TSMRangeElements> revRangeResult = exec(commandObjects.tsMRevRange(\n        fromTimestamp - 100, toTimestamp + 100, filter));\n\n    assertThat(revRangeResult.keySet(), hasItems(key1, key2));\n    assertThat(revRangeResult.get(key1).getElements().stream().map(TSElement::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(1.5, 0.001), closeTo(1.0, 0.001)));\n    assertThat(revRangeResult.get(key2).getElements().stream().map(TSElement::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(closeTo(2.0, 0.001), closeTo(2.5, 0.001)));\n\n    TSMRangeParams multiRangeParamsA =\n        new TSMRangeParams(fromTimestamp - 100, toTimestamp + 100).filter(filter);\n\n    Map<String, TSMRangeElements> rangeResultWithParams = exec(commandObjects.tsMRange(multiRangeParamsA));\n\n    assertThat(rangeResultWithParams.keySet(), hasItems(key1, key2));\n    assertThat(rangeResultWithParams, equalTo(range));\n\n    TSMRangeParams multiRangeParams = new TSMRangeParams(fromTimestamp - 100, toTimestamp + 100).filter(filter);\n\n    Map<String, TSMRangeElements> revRangeResultWithParams = exec(commandObjects.tsMRevRange(multiRangeParams));\n\n    assertThat(revRangeResultWithParams.keySet(), hasItems(key1, key2));\n    assertThat(revRangeResultWithParams, equalTo(revRangeResult));\n  }\n\n  @Test\n  public void testTsGet() {\n    String key = \"tsGetKey\";\n\n    String create = exec(commandObjects.tsCreate(key));\n    assertThat(create, equalTo(\"OK\"));\n\n    long timestamp = 1000L;\n\n    double firstValue = 2.5;\n    double secondValue = 3.5;\n\n    TSElement initialGet = exec(commandObjects.tsGet(key));\n    assertThat(initialGet, nullValue());\n\n    exec(commandObjects.tsAdd(key, timestamp, firstValue));\n    exec(commandObjects.tsAdd(key, timestamp + 100, secondValue));\n\n    TSElement getLastValue = exec(commandObjects.tsGet(key));\n    assertThat(getLastValue, notNullValue());\n    assertThat(getLastValue.getValue(), closeTo(secondValue, 0.001));\n\n    TSElement getWithParams = exec(commandObjects.tsGet(key, new TSGetParams().latest()));\n    assertThat(getWithParams, notNullValue());\n    assertThat(getWithParams.getValue(), closeTo(secondValue, 0.001));\n  }\n\n  @Test\n  public void testTsMGet() {\n    String key1 = \"tsMGetKey1\";\n    String key2 = \"tsMGetKey2\";\n\n    long timestamp1 = 1000L;\n    long timestamp2 = 2000L;\n\n    double value1 = 1.0;\n    double value2 = 2.0;\n\n    String filter = \"sensor_id=1234\";\n\n    TSCreateParams createParams = new TSCreateParams()\n        .uncompressed().label(\"sensor_id\", \"1234\");\n\n    exec(commandObjects.tsAdd(key1, timestamp1, value1, createParams));\n    exec(commandObjects.tsAdd(key2, timestamp2, value2, createParams));\n\n    TSMGetParams multiGetParams = new TSMGetParams().withLabels();\n\n    Map<String, TSMGetElement> elements = exec(commandObjects.tsMGet(multiGetParams, filter));\n\n    assertThat(elements.keySet(), hasItems(key1, key2));\n\n    TSMGetElement element1 = elements.get(key1);\n    assertThat(element1, notNullValue());\n    assertThat(element1.getElement().getTimestamp(), equalTo(timestamp1));\n    assertThat(element1.getElement().getValue(), closeTo(value1, 0.001));\n\n    TSMGetElement element2 = elements.get(key2);\n    assertThat(element2, notNullValue());\n    assertThat(element2.getElement().getTimestamp(), equalTo(timestamp2));\n    assertThat(element2.getElement().getValue(), closeTo(value2, 0.001));\n\n    assertThat(element1.getLabels(), hasEntry(\"sensor_id\", \"1234\"));\n    assertThat(element2.getLabels(), hasEntry(\"sensor_id\", \"1234\"));\n  }\n\n  @Test\n  public void testTsCreateRule() {\n    String sourceKey = \"tsSourceKey\";\n    String destKey = \"tsDestKey\";\n\n    AggregationType aggregationType = AggregationType.AVG;\n\n    long timeBucket = 60000; // 1 minute\n\n    exec(commandObjects.tsCreate(sourceKey));\n    exec(commandObjects.tsCreate(destKey));\n\n    String createRule = exec(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, timeBucket));\n    assertThat(createRule, equalTo(\"OK\"));\n\n    long timestamp1 = 1000L; // 1 second\n    double value1 = 10.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp1, value1));\n\n    long timestamp2 = 30000L; // 30 seconds\n    double value2 = 20.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp2, value2));\n\n    long timestamp3 = 100000L; // 100 seconds, should be in the second aggregation bucket\n    double value3 = 30.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp3, value3));\n\n    long timestamp4 = 200000L; // 200 seconds, should be in the fourth aggregation bucket\n    double value4 = 1.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp4, value4));\n\n    // Verify that aggregated data appears in the destination key\n    // We only check the first three buckets, i.e. 180 seconds\n    // The average of value1 and value2 should be in the first bucket, value3 in the second\n    List<TSElement> destElements = exec(commandObjects.tsRange(destKey, 0, 180000));\n\n    assertThat(destElements.size(), equalTo(2));\n\n    double expectedAvgFirstBucket = (value1 + value2) / 2.0;\n    assertThat(destElements.get(0).getValue(), closeTo(expectedAvgFirstBucket, 0.001));\n\n    assertThat(destElements.get(1).getValue(), closeTo(value3, 0.001));\n  }\n\n  @Test\n  public void testTsCreateRuleWithAlign() {\n    String sourceKey = \"tsSourceKey\";\n    String destKey = \"tsDestKey\";\n\n    AggregationType aggregationType = AggregationType.AVG;\n\n    long timeBucket = 60000; // 1 minute\n\n    exec(commandObjects.tsCreate(sourceKey));\n    exec(commandObjects.tsCreate(destKey));\n\n    String createRule = exec(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, timeBucket, 2000));\n    assertThat(createRule, equalTo(\"OK\"));\n\n    long timestamp1 = 1000L; // 1 second\n    double value1 = 10.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp1, value1));\n\n    long timestamp2 = 30000L; // 30 seconds\n    double value2 = 20.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp2, value2));\n\n    long timestamp3 = 100000L; // 100 seconds, should be in the second aggregation bucket\n    double value3 = 30.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp3, value3));\n\n    long timestamp4 = 200000L; // 200 seconds, should be in the fourth aggregation bucket\n    double value4 = 1.0;\n    exec(commandObjects.tsAdd(sourceKey, timestamp4, value4));\n\n    // Verify that aggregated data appears in the destination key\n    // We only check the first three buckets, i.e. 180 seconds\n    // The average of value1 and value2 should be in the first bucket, value3 in the second\n    List<TSElement> destElements = exec(commandObjects.tsRange(destKey, 2000, 182000));\n\n    assertThat(destElements.size(), equalTo(2));\n    assertThat(destElements.get(0).getValue(), closeTo(value2, 0.001));\n    assertThat(destElements.get(1).getValue(), closeTo(value3, 0.001));\n  }\n\n\n  @Test\n  public void testTsDeleteRule() {\n    String sourceKey = \"tsSourceKeyForDeletionWithData\";\n    String destKey = \"tsDestKeyForDeletionWithData\";\n\n    AggregationType aggregationType = AggregationType.SUM;\n\n    long bucketDuration = 60000; // 1 minute\n\n    exec(commandObjects.tsCreate(sourceKey));\n    exec(commandObjects.tsCreate(destKey));\n\n    exec(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration));\n\n    long initialTimestamp = 1000;\n    exec(commandObjects.tsAdd(sourceKey, initialTimestamp, 10.0));\n\n    // This will force aggregation of the first bucket\n    exec(commandObjects.tsAdd(sourceKey, initialTimestamp + bucketDuration, 20.0));\n\n    List<TSElement> initialAggregatedData = exec(commandObjects.tsRange(destKey, 0, bucketDuration));\n\n    assertThat(initialAggregatedData.stream().map(TSElement::getValue).collect(Collectors.toList()),\n        contains(closeTo(10.0, 0.001)));\n\n    List<TSElement> initialAggregatedDataSecondBucket = exec(commandObjects.tsRange(destKey, bucketDuration, 2 * bucketDuration));\n\n    assertThat(initialAggregatedDataSecondBucket.stream().map(TSElement::getValue).collect(Collectors.toList()),\n        empty());\n\n    // Delete the rule\n    String deleteRule = exec(commandObjects.tsDeleteRule(sourceKey, destKey));\n    assertThat(deleteRule, equalTo(\"OK\"));\n\n    // Add more data to the source key after the rule has been deleted\n    long postDeletionTimestamp = initialTimestamp + bucketDuration + 10;\n\n    exec(commandObjects.tsAdd(sourceKey, postDeletionTimestamp, 20));\n\n    // This should force the aggregation of the second bucket, if there was a rule\n    exec(commandObjects.tsAdd(sourceKey, postDeletionTimestamp + bucketDuration, 20));\n\n    // Make sure that the data in the destination key has not changed\n    List<TSElement> postDeletionAggregatedData = exec(commandObjects.tsRange(destKey, 0, bucketDuration));\n\n    assertThat(postDeletionAggregatedData.stream().map(TSElement::getValue).collect(Collectors.toList()),\n        contains(closeTo(10.0, 0.001)));\n\n    List<TSElement> postDeletionAggregatedDataSecondBucket = exec(commandObjects.tsRange(destKey, bucketDuration, 2 * bucketDuration));\n\n    assertThat(postDeletionAggregatedDataSecondBucket.stream().map(TSElement::getValue).collect(Collectors.toList()),\n        empty());\n  }\n\n  @Test\n  public void testTsQueryIndexWithKeyCreation() {\n    String key1 = \"temperature:sensor:1\";\n    String key2 = \"temperature:sensor:2\";\n    String key3 = \"humidity:sensor:1\";\n\n    TSCreateParams paramsTempSensor1 = new TSCreateParams()\n        .label(\"type\", \"temperature\").label(\"sensor_id\", \"1\");\n\n    exec(commandObjects.tsCreate(key1, paramsTempSensor1));\n\n    TSCreateParams paramsTempSensor2 = new TSCreateParams()\n        .label(\"type\", \"temperature\").label(\"sensor_id\", \"2\");\n\n    exec(commandObjects.tsCreate(key2, paramsTempSensor2));\n\n    TSCreateParams paramsHumiditySensor1 = new TSCreateParams()\n        .label(\"type\", \"humidity\").label(\"sensor_id\", \"1\");\n\n    exec(commandObjects.tsCreate(key3, paramsHumiditySensor1));\n\n    String[] filters = new String[]{ \"type=temperature\" };\n    List<String> matchingKeys = exec(commandObjects.tsQueryIndex(filters));\n\n    assertThat(matchingKeys, containsInAnyOrder(key1, key2));\n  }\n\n  @Test\n  public void testTsAlterAndInfo() {\n    String key = \"tsKey\";\n\n    TSCreateParams createParams = new TSCreateParams()\n        .label(\"sensor\", \"temperature\");\n\n    TSAlterParams alterParams = new TSAlterParams()\n        .label(\"sensor\", \"humidity\");\n\n    String create = exec(commandObjects.tsCreate(key, createParams));\n    assertThat(create, equalTo(\"OK\"));\n\n    TSInfo info = exec(commandObjects.tsInfo(key));\n\n    assertThat(info, notNullValue());\n    assertThat(info.getLabels().get(\"sensor\"), equalTo(\"temperature\"));\n    assertThat(info.getChunks(), nullValue());\n\n    TSInfo debugInfo = exec(commandObjects.tsInfoDebug(key));\n\n    assertThat(debugInfo, notNullValue());\n    assertThat(debugInfo.getLabels().get(\"sensor\"), equalTo(\"temperature\"));\n    assertThat(debugInfo.getChunks(), notNullValue());\n\n    String alter = exec(commandObjects.tsAlter(key, alterParams));\n\n    assertThat(alter, equalTo(\"OK\"));\n\n    TSInfo infoAfter = exec(commandObjects.tsInfo(key));\n\n    assertThat(infoAfter, notNullValue());\n    assertThat(infoAfter.getLabels().get(\"sensor\"), equalTo(\"humidity\"));\n    assertThat(infoAfter.getChunks(), nullValue());\n\n    TSInfo debugInfoAfter = exec(commandObjects.tsInfoDebug(key));\n\n    assertThat(debugInfoAfter, notNullValue());\n    assertThat(debugInfoAfter.getLabels().get(\"sensor\"), equalTo(\"humidity\"));\n    assertThat(debugInfoAfter.getChunks(), notNullValue());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsTopkCommandsTest.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.aMapWithSize;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasKey;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\n\n/**\n * Tests related to <a href=\"https://redis.io/commands/?group=topk\">Top-k</a> commands.\n */\npublic class CommandObjectsTopkCommandsTest extends CommandObjectsModulesTestBase {\n\n  public CommandObjectsTopkCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testTopKAddAndQuery() {\n    String key = \"testTopK\";\n\n    long topKSize = 3;\n\n    String reserve = exec(commandObjects.topkReserve(key, topKSize));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    List<String> add = exec(commandObjects.topkAdd(key,\n        \"apple\", \"banana\", \"carrot\", \"apple\", \"banana\",\n        \"date\", \"eggplant\", \"fig\", \"grape\", \"apple\"));\n    // As the values are added, some items get kicked out from top 3. They are returned in the response.\n    assertThat(add, contains(\n        nullValue(), nullValue(), nullValue(), nullValue(), nullValue(),\n        equalTo(\"carrot\"), equalTo(\"date\"), equalTo(\"eggplant\"), equalTo(\"fig\"), nullValue()\n    ));\n\n    List<Boolean> query = exec(commandObjects.topkQuery(key, \"apple\", \"banana\", \"carrot\", \"grape\"));\n    assertThat(query, contains(true, true, false, true));\n  }\n\n  @Test\n  public void testTopKIncrBy() {\n    String key = \"testTopK\";\n\n    long topKSize = 3;\n\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"apple\", 2L);\n    itemIncrements.put(\"banana\", 3L);\n    itemIncrements.put(\"carrot\", 1L);\n    itemIncrements.put(\"date\", 5L);\n\n    String reserve = exec(commandObjects.topkReserve(key, topKSize, 2000, 7, 0.9));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    List<String> incrBy = exec(commandObjects.topkIncrBy(key, itemIncrements));\n    // Due to Map's unpredictable order, we can't assert ordering of the result\n    assertThat(incrBy, hasSize(4));\n\n    List<Boolean> query = exec(commandObjects.topkQuery(key, \"apple\", \"banana\", \"date\", \"carrot\"));\n    assertThat(query, contains(true, true, true, false));\n  }\n\n  @Test\n  public void testTopKListAndListWithCount() {\n    String key = \"testTopK\";\n\n    long topKSize = 3;\n\n    String reserve = exec(commandObjects.topkReserve(key, topKSize));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    List<String> add = exec(commandObjects.topkAdd(key,\n        \"apple\", \"banana\", \"carrot\", \"apple\", \"banana\",\n        \"date\", \"eggplant\", \"fig\", \"grape\", \"apple\"));\n    assertThat(add, notNullValue());\n\n    List<String> list = exec(commandObjects.topkList(key));\n    assertThat(list, contains(\"apple\", \"banana\", \"grape\"));\n\n    Map<String, Long> listWithCount = exec(commandObjects.topkListWithCount(key));\n    assertThat(listWithCount, aMapWithSize(3));\n    assertThat(listWithCount, hasEntry(\"apple\", 3L));\n    assertThat(listWithCount, hasEntry(\"banana\", 2L));\n    assertThat(listWithCount, hasEntry(\"grape\", 1L));\n  }\n\n  @Test\n  public void testTopKInfo() {\n    String key = \"testTopK\";\n\n    long topKSize = 3;\n    long width = 1000;\n    long depth = 7;\n    double decay = 0.9;\n\n    String reserve = exec(commandObjects.topkReserve(key, topKSize, width, depth, decay));\n    assertThat(reserve, equalTo(\"OK\"));\n\n    Map<String, Object> info = exec(commandObjects.topkInfo(key));\n\n    assertThat(info, notNullValue());\n    assertThat(info, hasEntry(\"k\", 3L));\n    assertThat(info, hasEntry(\"width\", width));\n    assertThat(info, hasEntry(\"depth\", depth));\n    assertThat(info, hasKey(\"decay\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/commandobjects/Person.java",
    "content": "package redis.clients.jedis.commands.commandobjects;\n\nimport java.util.Objects;\n\n/**\n * Bean class used for testing Redis JSON commands.\n */\npublic class Person {\n\n  private String name;\n  private int age;\n\n  public Person() {\n  }\n\n  public Person(String name, int age) {\n    this.name = name;\n    this.age = age;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public void setName(String name) {\n    this.name = name;\n  }\n\n  public int getAge() {\n    return age;\n  }\n\n  public void setAge(int age) {\n    this.age = age;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    Person person = (Person) o;\n    return age == person.age && Objects.equals(name, person.name);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(name, age);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/AccessControlListCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.hamcrest.Matchers.startsWith;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static redis.clients.jedis.util.ACLTestUtil.filterBinaryByClientId;\nimport static redis.clients.jedis.util.ACLTestUtil.filterByClientId;\nimport static redis.clients.jedis.util.RedisVersionUtil.getRedisVersion;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.utils.RedisVersion;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Transaction;\nimport redis.clients.jedis.exceptions.JedisAccessControlException;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.resps.AccessControlLogEntry;\nimport redis.clients.jedis.resps.AccessControlUser;\nimport redis.clients.jedis.util.ACLTestUtil;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * TODO: properly define and test exceptions\n */\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class AccessControlListCommandsTest extends JedisCommandsTestBase {\n\n  public static final String USER_NAME = \"newuser\";\n  public static final String USER_PASSWORD = \"secret\";\n  public static final String USER_ANTIREZ = \"antirez\";\n\n  @BeforeAll\n  public static void prepare() throws Exception {\n    // Use to check if the ACL test should be ran. ACL are available only in 6.0 and later\n    assumeTrue(getRedisVersion(endpoint).isGreaterThanOrEqualTo(RedisVersion.V6_0_0),\n        \"Not running ACL test on this version of Redis\");\n  }\n\n  public AccessControlListCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    try {\n      jedis.aclDelUser(USER_NAME);\n      jedis.aclDelUser(USER_ANTIREZ);\n    } catch (Exception e) {\n      // Ignore exception\n    }\n    super.tearDown();\n  }\n\n  @Test\n  public void aclWhoAmI() {\n    String string = jedis.aclWhoAmI();\n    assertEquals(\"default\", string);\n\n    byte[] binary = jedis.aclWhoAmIBinary();\n    assertArrayEquals(SafeEncoder.encode(\"default\"), binary);\n  }\n\n  @Test\n  public void aclListDefault() {\n    assertFalse(jedis.aclList().isEmpty());\n    assertFalse(jedis.aclListBinary().isEmpty());\n  }\n\n  @Test\n  public void addAndRemoveUser() {\n    int existingUsers = jedis.aclList().size();\n\n    String status = jedis.aclSetUser(USER_NAME);\n    assertEquals(\"OK\", status);\n    assertEquals(existingUsers + 1, jedis.aclList().size());\n    assertEquals(existingUsers + 1, jedis.aclListBinary().size()); // test binary\n\n    long count = jedis.aclDelUser(USER_NAME);\n    assertEquals(1, count);\n    assertEquals(existingUsers, jedis.aclList().size());\n    assertEquals(existingUsers, jedis.aclListBinary().size()); // test binary\n  }\n\n  @Test\n  public void aclUsers() {\n    List<String> users = jedis.aclUsers();\n    assertEquals(3, users.size());\n    assertThat(users, Matchers.hasItem(\"default\"));\n    assertThat(users, Matchers.hasItem(\"deploy\"));\n    assertThat(users, Matchers.hasItem(\"acljedis\"));\n    assertEquals(3, jedis.aclUsersBinary().size()); // Test binary\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Redis 6.2.x misses [~]>\")\n  public void aclGetUser() {\n    // get default user information\n    AccessControlUser userInfo = jedis.aclGetUser(\"default\");\n\n    assertFalse(userInfo.getFlags().isEmpty());\n    assertEquals(1, userInfo.getPassword().size());\n    assertEquals(\"+@all\", userInfo.getCommands());\n    assertEquals(\"~*\", userInfo.getKeys());\n\n    // create new user\n    jedis.aclSetUser(USER_NAME);\n    userInfo = jedis.aclGetUser(USER_NAME);\n    assertFalse(userInfo.getFlags().isEmpty());\n    assertEquals(\"off\", userInfo.getFlags().get(0));\n    assertTrue(userInfo.getPassword().isEmpty());\n    assertTrue(userInfo.getKeys().isEmpty());\n\n    // reset user\n    jedis.aclSetUser(USER_NAME, \"reset\", \"+@all\", \"~*\", \"-@string\", \"+incr\", \"-debug\",\n      \"+debug|digest\");\n    userInfo = jedis.aclGetUser(USER_NAME);\n    assertThat(userInfo.getCommands(), containsString(\"+@all\"));\n    assertThat(userInfo.getCommands(), containsString(\"-@string\"));\n    assertThat(userInfo.getCommands(), containsString(\"+debug|digest\"));\n  }\n\n  @Test\n  public void createUserAndPasswords() {\n    String status = jedis.aclSetUser(USER_NAME, \">\" + USER_PASSWORD);\n    assertEquals(\"OK\", status);\n\n    // create a new client to try to authenticate\n    Jedis jedis2 = new Jedis();\n\n    // the user is just created without any permission the authentication should fail\n    try {\n      jedis2.auth(USER_NAME, USER_PASSWORD);\n      fail(\"Should throw a WRONGPASS exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), startsWith(\"WRONGPASS \"));\n    }\n\n    // now activate the user\n    jedis.aclSetUser(USER_NAME, \"on\", \"+acl\");\n    jedis2.auth(USER_NAME, USER_PASSWORD);\n    assertEquals(USER_NAME, jedis2.aclWhoAmI());\n\n    // test invalid password\n    jedis2.close();\n\n    try {\n      jedis2.auth(USER_NAME, \"wrong-password\");\n      fail(\"Should throw a WRONGPASS exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), startsWith(\"WRONGPASS \"));\n    }\n\n    // remove password, and try to authenticate\n    jedis.aclSetUser(USER_NAME, \"<\" + USER_PASSWORD);\n    try {\n      jedis2.auth(USER_NAME, USER_PASSWORD);\n      fail(\"Should throw a WRONGPASS exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), startsWith(\"WRONGPASS \"));\n    }\n\n    jedis.aclDelUser(USER_NAME); // delete the user\n    try {\n      jedis2.auth(USER_NAME, \"wrong-password\");\n      fail(\"Should throw a WRONGPASS exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), startsWith(\"WRONGPASS \"));\n    }\n\n    jedis2.close();\n  }\n\n  @Test\n  public void aclSetUserWithAnyPassword() {\n    String status = jedis.aclSetUser(USER_NAME, \"nopass\");\n    assertEquals(\"OK\", status);\n    status = jedis.aclSetUser(USER_NAME, \"on\", \"+acl\");\n    assertEquals(\"OK\", status);\n\n    // connect with this new user and try to get/set keys\n    Jedis jedis2 = new Jedis();\n    String authResult = jedis2.auth(USER_NAME, \"any password\");\n    assertEquals(\"OK\", authResult);\n    jedis2.close();\n  }\n\n  @Test\n  public void aclExcudeSingleCommand() {\n    String status = jedis.aclSetUser(USER_NAME, \"nopass\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.aclSetUser(USER_NAME, \"on\", \"+acl\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.aclSetUser(USER_NAME, \"allcommands\", \"allkeys\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.aclSetUser(USER_NAME, \"-ping\");\n    assertEquals(\"OK\", status);\n\n    // connect with this new user and try to get/set keys\n    Jedis jedis2 = new Jedis();\n    String authResult = jedis2.auth(USER_NAME, \"any password\");\n    assertEquals(\"OK\", authResult);\n\n    jedis2.incr(\"mycounter\");\n\n    try {\n      jedis2.ping();\n      fail(\"Should throw a NOPERM exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), startsWith(\"NOPERM \"));\n      assertThat(e.getMessage(), containsString(\" has no permissions to run the 'ping' command\"));\n    }\n\n    jedis2.close();\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void aclDryRun() {\n    jedis.aclSetUser(USER_NAME, \"nopass\", \"allkeys\", \"+set\", \"-get\");\n\n    assertEquals(\"OK\", jedis.aclDryRun(USER_NAME, \"SET\", \"key\", \"value\"));\n    assertThat(jedis.aclDryRun(USER_NAME, \"GET\", \"key\"),\n        endsWith(\" has no permissions to run the 'get' command\"));\n\n    assertEquals(\"OK\", jedis.aclDryRun(USER_NAME,\n        new CommandArguments(Protocol.Command.SET).key(\"ca-key\").add(\"value\")));\n    assertThat(jedis.aclDryRun(USER_NAME, new CommandArguments(Protocol.Command.GET).key(\"ca-key\")),\n        endsWith(\" has no permissions to run the 'get' command\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void aclDryRunBinary() {\n    byte[] username = USER_NAME.getBytes();\n\n    jedis.aclSetUser(username, \"nopass\".getBytes(), \"allkeys\".getBytes(), \"+set\".getBytes(), \"-get\".getBytes());\n\n    assertArrayEquals(\"OK\".getBytes(), jedis.aclDryRunBinary(username,\n        \"SET\".getBytes(), \"key\".getBytes(), \"value\".getBytes()));\n    assertThat(new String(jedis.aclDryRunBinary(username, \"GET\".getBytes(), \"key\".getBytes())),\n        endsWith(\" has no permissions to run the 'get' command\"));\n\n    assertArrayEquals(\"OK\".getBytes(), jedis.aclDryRunBinary(username,\n        new CommandArguments(Protocol.Command.SET).key(\"ca-key\").add(\"value\")));\n    assertThat(new String(jedis.aclDryRunBinary(username,\n        new CommandArguments(Protocol.Command.GET).key(\"ca-key\"))),\n        endsWith(\" has no permissions to run the 'get' command\"));\n  }\n\n  @Test\n  public void aclDelUser() {\n    String statusSetUser = jedis.aclSetUser(USER_NAME);\n    assertEquals(\"OK\", statusSetUser);\n    int before = jedis.aclList().size();\n    assertEquals(1L, jedis.aclDelUser(USER_NAME));\n    int after = jedis.aclList().size();\n    assertEquals(before - 1, after);\n  }\n\n  @Test\n  public void basicPermissionsTest() {\n    // create a user with login permissions\n    jedis.aclSetUser(USER_NAME, \">\" + USER_PASSWORD);\n\n    // users are not able to access any command\n    jedis.aclSetUser(USER_NAME, \"on\", \"+acl\");\n\n    // connect with this new user and try to get/set keys\n    Jedis jedis2 = new Jedis();\n    jedis2.auth(USER_NAME, USER_PASSWORD);\n\n    try {\n      jedis2.set(\"foo\", \"bar\");\n      fail(\"Should throw a NOPERM exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), startsWith(\"NOPERM \"));\n      assertThat(e.getMessage(), containsString(\" has no permissions to run the 'set' command\"));\n    }\n\n    // change permissions of the user\n    // by default users are not able to access any key\n    jedis.aclSetUser(USER_NAME, \"+set\");\n\n    jedis2.close();\n    jedis2.auth(USER_NAME, USER_PASSWORD);\n\n    final List<String> nopermKeys = Arrays.asList(\"NOPERM No permissions to access a key\",\n        \"NOPERM this user has no permissions to access one of the keys used as arguments\");\n\n    try {\n      jedis2.set(\"foo\", \"bar\");\n      fail(\"Should throw a NOPERM exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), Matchers.isIn(nopermKeys));\n    }\n\n    // allow user to access a subset of the key\n    jedis.aclSetUser(USER_NAME, \"allcommands\", \"~foo:*\", \"~bar:*\"); // TODO : a DSL\n\n    // create key foo, bar and zap\n    jedis2.set(\"foo:1\", \"a\");\n\n    jedis2.set(\"bar:2\", \"b\");\n\n    try {\n      jedis2.set(\"zap:3\", \"c\");\n      fail(\"Should throw a NOPERM exception\");\n    } catch (JedisAccessControlException e) {\n      assertThat(e.getMessage(), Matchers.isIn(nopermKeys));\n    }\n  }\n\n  @Test\n  public void aclCatTest() {\n    List<String> categories = jedis.aclCat();\n    assertFalse(categories.isEmpty());\n\n    // test binary\n    List<byte[]> categoriesBinary = jedis.aclCatBinary();\n    assertFalse(categories.isEmpty());\n    assertEquals(categories.size(), categoriesBinary.size());\n\n    // test commands in a category\n    assertFalse(jedis.aclCat(\"scripting\").isEmpty());\n\n    try {\n      jedis.aclCat(\"testcategory\");\n      fail(\"Should throw a ERR exception\");\n    } catch (Exception e) {\n      assertEquals(\"ERR Unknown category 'testcategory'\", e.getMessage());\n    }\n  }\n\n  @Test\n  public void aclLogTest() {\n    jedis.aclLogReset();\n    assertTrue(filterByClientId(jedis.aclLog(), jedis.clientId()).isEmpty());\n\n    // create new user and cconnect\n    jedis.aclSetUser(USER_ANTIREZ, \">foo\", \"on\", \"+set\", \"~object:1234\");\n    jedis.aclSetUser(USER_ANTIREZ, \"+eval\", \"+multi\", \"+exec\");\n    jedis.auth(USER_ANTIREZ, \"foo\");\n\n    // generate an error (antirez user does not have the permission to access foo)\n    try {\n      jedis.get(\"foo\");\n      fail(\"Should have thrown an JedisAccessControlException: user does not have the permission to get(\\\"foo\\\")\");\n    } catch (JedisAccessControlException e) {\n    }\n\n    // test the ACL Log\n    jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n\n    List<AccessControlLogEntry> aclEntries = jedis.aclLog();\n    // At the same time other clients can generate other log entries\n    // Filter only the entries generated by this client\n    List<AccessControlLogEntry> clientAclEntries = filterByClientId(aclEntries, jedis.clientId());\n\n    assertEquals(1, clientAclEntries.size(), \"Number of log messages \");\n    assertEquals(1, clientAclEntries.get(0).getCount());\n    assertEquals(USER_ANTIREZ, clientAclEntries.get(0).getUsername());\n    assertEquals(\"toplevel\", clientAclEntries.get(0).getContext());\n    assertEquals(\"command\", clientAclEntries.get(0).getReason());\n    assertEquals(\"get\", clientAclEntries.get(0).getObject());\n\n    // Capture similar event\n    jedis.aclLogReset();\n    assertTrue(filterByClientId(jedis.aclLog(), jedis.clientId()).isEmpty());\n\n    jedis.auth(USER_ANTIREZ, \"foo\");\n\n    for (int i = 0; i < 10; i++) {\n      // generate an error (antirez user does not have the permission to access foo)\n      try {\n        jedis.get(\"foo\");\n        fail(\"Should have thrown an JedisAccessControlException: user does not have the permission to get(\\\"foo\\\")\");\n      } catch (JedisAccessControlException e) {\n      }\n    }\n\n    // test the ACL Log\n    jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n    clientAclEntries = filterByClientId(jedis.aclLog(), jedis.clientId());\n    assertEquals(1, clientAclEntries.size(), \"Number of log messages \");\n    assertEquals(10, clientAclEntries.get(0).getCount());\n    assertEquals(\"get\", clientAclEntries.get(0).getObject());\n\n    // Generate another type of error\n    jedis.auth(USER_ANTIREZ, \"foo\");\n    try {\n      jedis.set(\"somekeynotallowed\", \"1234\");\n      fail(\"Should have thrown an JedisAccessControlException: user does not have the permission to set(\\\"somekeynotallowed\\\", \\\"1234\\\")\");\n    } catch (JedisAccessControlException e) {\n    }\n\n    // test the ACL Log\n    jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n    clientAclEntries = filterByClientId(jedis.aclLog(), jedis.clientId());\n    assertEquals( 2, clientAclEntries.size(), \"Number of log messages \");\n    assertEquals(1, clientAclEntries.get(0).getCount());\n    assertEquals(\"somekeynotallowed\", clientAclEntries.get(0).getObject());\n    assertEquals(\"key\", clientAclEntries.get(0).getReason());\n\n    jedis.aclLogReset();\n    assertTrue(filterByClientId(jedis.aclLog(), jedis.clientId()).isEmpty());\n\n    jedis.auth(USER_ANTIREZ, \"foo\");\n    Transaction t = jedis.multi();\n    t.incr(\"foo\");\n    try {\n      t.exec();\n      fail(\"Should have thrown an JedisAccessControlException: user does not have the permission to incr(\\\"foo\\\")\");\n    } catch (Exception e) {\n    }\n    t.close();\n\n    jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n    clientAclEntries = filterByClientId(jedis.aclLog(), jedis.clientId());\n    assertEquals( 1, clientAclEntries.size(), \"Number of log messages \");\n    assertEquals(1, clientAclEntries.get(0).getCount());\n    assertEquals(\"multi\", clientAclEntries.get(0).getContext());\n    assertEquals(\"incr\", clientAclEntries.get(0).getObject());\n\n    // ACL LOG can accept a numerical argument to show less entries\n    jedis.auth(USER_ANTIREZ, \"foo\");\n    for (int i = 0; i < 5; i++) {\n      try {\n        jedis.incr(\"foo\");\n        fail(\"Should have thrown an JedisAccessControlException: user does not have the permission to incr(\\\"foo\\\")\");\n      } catch (JedisAccessControlException e) {\n      }\n    }\n    try {\n      jedis.set(\"foo-2\", \"bar\");\n      fail(\"Should have thrown an JedisAccessControlException: user does not have the permission to set(\\\"foo-2\\\", \\\"bar\\\")\");\n    } catch (JedisAccessControlException e) {\n    }\n\n    jedis.auth(endpoint.getUsername(), endpoint.getPassword());\n    assertEquals( 3, filterByClientId(jedis.aclLog(), jedis.clientId()).size(), \"Number of log messages \");\n    assertEquals( 2, jedis.aclLog(2).size(), \"Number of log messages \");\n\n    // Binary tests\n    assertEquals( 3, filterBinaryByClientId(jedis.aclLogBinary(), jedis.clientId()).size(), \"Number of log messages \");\n    assertEquals( 2, jedis.aclLogBinary(2).size(), \"Number of log messages \");\n\n    // RESET\n    String status = jedis.aclLogReset();\n    assertEquals(status, \"OK\");\n\n    jedis.aclDelUser(USER_ANTIREZ);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\", message = \"Starting with Redis version 7.2.0: Added entry ID, timestamp created, and timestamp last updated.\")\n  public void aclLogWithEntryID() {\n    jedis.aclLogReset();\n    try {\n      jedis.auth(\"wronguser\", \"wrongpass\");\n      fail(\"wrong user should not passed\");\n    } catch (JedisAccessControlException e) {\n    }\n\n    List<AccessControlLogEntry> aclEntries = filterByClientId(jedis.aclLog(), jedis.clientId());\n    assertEquals( 1, aclEntries.size(), \"Number of log messages \");\n    assertEquals(1, aclEntries.get(0).getCount());\n    assertEquals(\"wronguser\", aclEntries.get(0).getUsername());\n    assertEquals(\"toplevel\", aclEntries.get(0).getContext());\n    assertEquals(\"auth\", aclEntries.get(0).getReason());\n    assertEquals(\"AUTH\", aclEntries.get(0).getObject());\n    assertTrue(aclEntries.get(0).getEntryId() >= 0);\n    assertTrue(aclEntries.get(0).getTimestampCreated() > 0);\n    assertEquals(aclEntries.get(0).getTimestampCreated(), aclEntries.get(0).getTimestampLastUpdated());\n\n    // RESET\n    String status = jedis.aclLogReset();\n    assertEquals(status, \"OK\");\n  }\n\n  @Test\n  public void aclGenPass() {\n    assertNotNull(jedis.aclGenPass());\n\n    // bit length case\n    assertNotNull(jedis.aclGenPassBinary(16));\n    assertNotNull(jedis.aclGenPassBinary(32));\n  }\n\n  @Test\n  public void aclGenPassBinary() {\n    assertNotNull(jedis.aclGenPassBinary());\n\n    // bit length case\n    assertNotNull(jedis.aclGenPassBinary(16));\n    assertNotNull(jedis.aclGenPassBinary(32));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Redis 6.2.x skips [&]>\")\n  public void aclBinaryCommandsTest() {\n    jedis.aclSetUser(USER_NAME.getBytes());\n    assertNotNull(jedis.aclGetUser(USER_NAME));\n\n    assertEquals(1L, jedis.aclDelUser(USER_NAME.getBytes()));\n\n    jedis.aclSetUser(USER_NAME.getBytes(), \"reset\".getBytes(), \"+@all\".getBytes(), \"~*\".getBytes(),\n      \"-@string\".getBytes(), \"+incr\".getBytes(), \"-debug\".getBytes(), \"+debug|digest\".getBytes(),\n            \"resetchannels\".getBytes(), \"&testchannel:*\".getBytes());\n\n    AccessControlUser userInfo = jedis.aclGetUser(USER_NAME.getBytes());\n\n    assertThat(userInfo.getCommands(), containsString(\"+@all\"));\n    assertThat(userInfo.getCommands(), containsString(\"-@string\"));\n    assertThat(userInfo.getCommands(), containsString(\"+debug|digest\"));\n    assertEquals(\"&testchannel:*\", userInfo.getChannels());\n\n    jedis.aclDelUser(USER_NAME.getBytes());\n\n    jedis.aclSetUser(\"TEST_USER\".getBytes());\n    jedis.aclSetUser(\"ANOTHER_TEST_USER\".getBytes());\n    jedis.aclSetUser(\"MORE_TEST_USERS\".getBytes());\n    assertEquals(3L, jedis.aclDelUser(\n            \"TEST_USER\".getBytes(),\n            \"ANOTHER_TEST_USER\".getBytes(),\n            \"MORE_TEST_USERS\".getBytes()));\n  }\n\n  @Test\n  public void aclLoadTest() {\n    try {\n      jedis.aclLoad();\n      fail(\"Should throw a JedisDataException: ERR This Redis instance is not configured to use an ACL file...\");\n    } catch (JedisDataException e) {\n      assertThat(e.getMessage(), startsWith(\"ERR This Redis instance is not configured to use an ACL file.\"));\n    }\n\n    // TODO test with ACL file\n  }\n\n  @Test\n  public void aclSaveTest() {\n    try {\n      jedis.aclSave();\n      fail(\"Should throw a JedisDataException: ERR This Redis instance is not configured to use an ACL file...\");\n    } catch (JedisDataException e) {\n      assertThat(e.getMessage(), startsWith(\"ERR This Redis instance is not configured to use an ACL file.\"));\n    }\n\n    // TODO test with ACL file\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/AllKindOfValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.Protocol.Command.BLPOP;\nimport static redis.clients.jedis.Protocol.Command.HGETALL;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.LRANGE;\nimport static redis.clients.jedis.Protocol.Command.PING;\nimport static redis.clients.jedis.Protocol.Command.RPUSH;\nimport static redis.clients.jedis.Protocol.Command.SET;\nimport static redis.clients.jedis.Protocol.Command.XINFO;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\n\nimport java.time.Duration;\nimport java.util.*;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.util.*;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.SetParams;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class AllKindOfValuesCommandsTest extends JedisCommandsTestBase {\n\n  private static final long TIME_SKEW = Duration.ofMillis(5).toMillis();\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x0A };\n  final byte[] bfoo2 = { 0x01, 0x02, 0x03, 0x04, 0x0B };\n  final byte[] bfoo3 = { 0x01, 0x02, 0x03, 0x04, 0x0C };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n\n  final byte[] bfoobar = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bfoostar = { 0x01, 0x02, 0x03, 0x04, '*' };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  final byte[] bnx = { 0x6E, 0x78 };\n  final byte[] bex = { 0x65, 0x78 };\n  final int expireSeconds = 2;\n\n  private static EndpointConfig lfuEndpoint;\n\n  @BeforeAll\n  public static void prepareLfuEndpoint() {\n    lfuEndpoint = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n  }\n\n  public AllKindOfValuesCommandsTest(RedisProtocol redisProtocol) {\n    super(redisProtocol);\n  }\n\n  @Test\n  public void ping() {\n    String status = jedis.ping();\n    assertEquals(\"PONG\", status);\n  }\n\n  @Test\n  public void pingWithMessage() {\n    String argument = \"message\";\n    assertEquals(argument, jedis.ping(argument));\n\n    assertArrayEquals(bfoobar, jedis.ping(bfoobar));\n  }\n\n  @Test\n  public void exists() {\n    String status = jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.set(bfoo, bbar);\n    assertEquals(\"OK\", status);\n\n    assertTrue(jedis.exists(\"foo\"));\n\n    assertTrue(jedis.exists(bfoo));\n\n    assertEquals(1L, jedis.del(\"foo\"));\n\n    assertEquals(1L, jedis.del(bfoo));\n\n    assertFalse(jedis.exists(\"foo\"));\n\n    assertFalse(jedis.exists(bfoo));\n  }\n\n  @Test\n  public void existsMany() {\n    String status = jedis.set(\"foo1\", \"bar1\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.set(\"foo2\", \"bar2\");\n    assertEquals(\"OK\", status);\n\n    assertEquals(2L, jedis.exists(\"foo1\", \"foo2\"));\n\n    assertEquals(1L, jedis.del(\"foo1\"));\n\n    assertEquals(1L, jedis.exists(\"foo1\", \"foo2\"));\n  }\n\n  @Test\n  public void del() {\n    jedis.set(\"foo1\", \"bar1\");\n    jedis.set(\"foo2\", \"bar2\");\n    jedis.set(\"foo3\", \"bar3\");\n\n    assertEquals(3L, jedis.del(\"foo1\", \"foo2\", \"foo3\"));\n\n    assertFalse(jedis.exists(\"foo1\"));\n    assertFalse(jedis.exists(\"foo2\"));\n    assertFalse(jedis.exists(\"foo3\"));\n\n    jedis.set(\"foo1\", \"bar1\");\n\n    assertEquals(1L, jedis.del(\"foo1\", \"foo2\"));\n\n    assertEquals(0L, jedis.del(\"foo1\", \"foo2\"));\n\n    // Binary ...\n    jedis.set(bfoo1, bbar1);\n    jedis.set(bfoo2, bbar2);\n    jedis.set(bfoo3, bbar3);\n\n    assertEquals(3L, jedis.del(bfoo1, bfoo2, bfoo3));\n\n    assertFalse(jedis.exists(bfoo1));\n    assertFalse(jedis.exists(bfoo2));\n    assertFalse(jedis.exists(bfoo3));\n\n    jedis.set(bfoo1, bbar1);\n\n    assertEquals(1, jedis.del(bfoo1, bfoo2));\n\n    assertEquals(0, jedis.del(bfoo1, bfoo2));\n  }\n\n  @Test\n  public void unlink() {\n    jedis.set(\"foo1\", \"bar1\");\n    jedis.set(\"foo2\", \"bar2\");\n    jedis.set(\"foo3\", \"bar3\");\n\n    assertEquals(3, jedis.unlink(\"foo1\", \"foo2\", \"foo3\"));\n\n    assertEquals(0, jedis.exists(\"foo1\", \"foo2\", \"foo3\"));\n\n    jedis.set(\"foo1\", \"bar1\");\n\n    assertEquals(1, jedis.unlink(\"foo1\", \"foo2\"));\n\n    assertEquals(0, jedis.unlink(\"foo1\", \"foo2\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.unlink(\"foo\"));\n    assertFalse(jedis.exists(\"foo\"));\n\n    // Binary\n    jedis.set(bfoo1, bbar1);\n    jedis.set(bfoo2, bbar2);\n    jedis.set(bfoo3, bbar3);\n\n    assertEquals(3, jedis.unlink(bfoo1, bfoo2, bfoo3));\n\n    assertEquals(0, jedis.exists(bfoo1, bfoo2, bfoo3));\n\n    jedis.set(bfoo1, bbar1);\n\n    assertEquals(1, jedis.unlink(bfoo1, bfoo2));\n\n    assertEquals(0, jedis.unlink(bfoo1, bfoo2));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.unlink(bfoo));\n    assertFalse(jedis.exists(bfoo));\n  }\n\n  @Test\n  public void type() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"string\", jedis.type(\"foo\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(\"string\", jedis.type(bfoo));\n  }\n\n  @Test\n  public void keys() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.set(\"foobar\", \"bar\");\n\n    Set<String> keys = jedis.keys(\"foo*\");\n    AssertUtil.assertCollectionContains(keys, \"foo\");\n    AssertUtil.assertCollectionContains(keys, \"foobar\");\n\n    assertEquals(Collections.emptySet(), jedis.keys(\"bar*\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    jedis.set(bfoobar, bbar);\n\n    Set<byte[]> bkeys = jedis.keys(bfoostar);\n    AssertUtil.assertByteArrayCollectionContains(bkeys, bfoo);\n    AssertUtil.assertByteArrayCollectionContains(bkeys, bfoobar);\n\n    assertEquals(Collections.emptySet(), jedis.keys(bbarstar));\n  }\n\n  @Test\n  public void randomKey() {\n    assertNull(jedis.randomKey());\n\n    jedis.set(\"foo\", \"bar\");\n\n    assertEquals(\"foo\", jedis.randomKey());\n\n    jedis.set(\"bar\", \"foo\");\n\n    String randomkey = jedis.randomKey();\n    assertTrue(randomkey.equals(\"foo\") || randomkey.equals(\"bar\"));\n\n    // Binary\n    jedis.del(\"foo\");\n    jedis.del(\"bar\");\n    assertNull(jedis.randomKey());\n\n    jedis.set(bfoo, bbar);\n\n    assertArrayEquals(bfoo, jedis.randomBinaryKey());\n\n    jedis.set(bbar, bfoo);\n\n    byte[] randomBkey = jedis.randomBinaryKey();\n    assertTrue(Arrays.equals(randomBkey, bfoo) || Arrays.equals(randomBkey, bbar));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void rename() {\n    jedis.set(\"foo\", \"bar\");\n    String status = jedis.rename(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    assertNull(jedis.get(\"foo\"));\n\n    assertEquals(\"bar\", jedis.get(\"bar\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    String bstatus = jedis.rename(bfoo, bbar);\n    assertEquals(\"OK\", bstatus);\n\n    assertNull(jedis.get(bfoo));\n\n    assertArrayEquals(bbar, jedis.get(bbar));\n  }\n\n  @Test\n  public void renameOldAndNewAreTheSame() {\n    assertEquals(\"OK\", jedis.set(\"foo\", \"bar\"));\n    assertEquals(\"OK\", jedis.rename(\"foo\", \"foo\"));\n\n    // Binary\n    assertEquals(\"OK\", jedis.set(bfoo, bbar));\n    assertEquals(\"OK\", jedis.rename(bfoo, bfoo));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void renamenx() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.renamenx(\"foo\", \"bar\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(0, jedis.renamenx(\"foo\", \"bar\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.renamenx(bfoo, bbar));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(0, jedis.renamenx(bfoo, bbar));\n\n  }\n\n  @Test\n  public void dbSize() {\n    assertEquals(0, jedis.dbSize());\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.dbSize());\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(2, jedis.dbSize());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void expire() {\n    assertEquals(0, jedis.expire(\"foo\", 20L));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.expire(\"foo\", 20L));\n    assertEquals(0, jedis.expire(\"foo\", 20L, ExpiryOption.NX));\n\n    // Binary\n    assertEquals(0, jedis.expire(bfoo, 20L));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.expire(bfoo, 20L));\n    assertEquals(0, jedis.expire(bfoo, 20L, ExpiryOption.NX));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void expireAt() {\n    long unixTime = (System.currentTimeMillis() / 1000L) + 20;\n\n    assertEquals(0, jedis.expireAt(\"foo\", unixTime));\n\n    jedis.set(\"foo\", \"bar\");\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    assertEquals(1, jedis.expireAt(\"foo\", unixTime));\n    assertEquals(1, jedis.expireAt(\"foo\", unixTime, ExpiryOption.XX));\n\n    // Binary\n    assertEquals(0, jedis.expireAt(bfoo, unixTime));\n\n    jedis.set(bfoo, bbar);\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    assertEquals(1, jedis.expireAt(bfoo, unixTime));\n    assertEquals(1, jedis.expireAt(bfoo, unixTime, ExpiryOption.XX));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void expireTime() {\n    long unixTime;\n\n    jedis.set(\"foo\", \"bar\");\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    jedis.expireAt(\"foo\", unixTime);\n    assertEquals(unixTime, jedis.expireTime(\"foo\"), 0.0001);\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    jedis.expireAt(bfoo, unixTime);\n    assertEquals(unixTime, jedis.expireTime(bfoo), 0.0001);\n  }\n\n  @Test\n  public void ttl() {\n    assertEquals(-2, jedis.ttl(\"foo\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(-1, jedis.ttl(\"foo\"));\n\n    jedis.expire(\"foo\", 20);\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl >= 0 && ttl <= 20);\n\n    // Binary\n    assertEquals(-2, jedis.ttl(bfoo));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(-1, jedis.ttl(bfoo));\n\n    jedis.expire(bfoo, 20);\n    long bttl = jedis.ttl(bfoo);\n    assertTrue(bttl >= 0 && bttl <= 20);\n  }\n\n  @Test\n  public void touch() throws Exception {\n    assertEquals(0, jedis.touch(\"foo1\", \"foo2\", \"foo3\"));\n\n    jedis.set(\"foo1\", \"bar1\");\n\n    Thread.sleep(1100); // little over 1 sec\n    assertTrue(jedis.objectIdletime(\"foo1\") > 0);\n\n    assertEquals(1, jedis.touch(\"foo1\"));\n    assertEquals(0L, jedis.objectIdletime(\"foo1\").longValue());\n\n    assertEquals(1, jedis.touch(\"foo1\", \"foo2\", \"foo3\"));\n\n    jedis.set(\"foo2\", \"bar2\");\n\n    jedis.set(\"foo3\", \"bar3\");\n\n    assertEquals(3, jedis.touch(\"foo1\", \"foo2\", \"foo3\"));\n\n    // Binary\n    assertEquals(0, jedis.touch(bfoo1, bfoo2, bfoo3));\n\n    jedis.set(bfoo1, bbar1);\n\n    Thread.sleep(1100); // little over 1 sec\n    assertTrue(jedis.objectIdletime(bfoo1) > 0);\n\n    assertEquals(1, jedis.touch(bfoo1));\n    assertEquals(0L, jedis.objectIdletime(bfoo1).longValue());\n\n    assertEquals(1, jedis.touch(bfoo1, bfoo2, bfoo3));\n\n    jedis.set(bfoo2, bbar2);\n\n    jedis.set(bfoo3, bbar3);\n\n    assertEquals(3, jedis.touch(bfoo1, bfoo2, bfoo3));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void select() {\n    jedis.set(\"foo\", \"bar\");\n    String status = jedis.select(1);\n    assertEquals(\"OK\", status);\n    assertNull(jedis.get(\"foo\"));\n    status = jedis.select(0);\n    assertEquals(\"OK\", status);\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    // Binary\n    jedis.set(bfoo, bbar);\n    String bstatus = jedis.select(1);\n    assertEquals(\"OK\", bstatus);\n    assertNull(jedis.get(bfoo));\n    bstatus = jedis.select(0);\n    assertEquals(\"OK\", bstatus);\n    assertArrayEquals(bbar, jedis.get(bfoo));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void getDB() {\n    assertEquals(0, jedis.getDB());\n    jedis.select(1);\n    assertEquals(1, jedis.getDB());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void move() {\n    assertEquals(0, jedis.move(\"foo\", 1));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.move(\"foo\", 1));\n    assertNull(jedis.get(\"foo\"));\n\n    jedis.select(1);\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n\n    // Binary\n    jedis.select(0);\n    assertEquals(0, jedis.move(bfoo, 1));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.move(bfoo, 1));\n    assertNull(jedis.get(bfoo));\n\n    jedis.select(1);\n    assertArrayEquals(bbar, jedis.get(bfoo));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void swapDB() {\n    jedis.set(\"foo1\", \"bar1\");\n    jedis.select(1);\n    assertNull(jedis.get(\"foo1\"));\n    jedis.set(\"foo2\", \"bar2\");\n    String status = jedis.swapDB(0, 1);\n    assertEquals(\"OK\", status);\n    assertEquals(\"bar1\", jedis.get(\"foo1\"));\n    assertNull(jedis.get(\"foo2\"));\n    jedis.select(0);\n    assertNull(jedis.get(\"foo1\"));\n    assertEquals(\"bar2\", jedis.get(\"foo2\"));\n\n    // Binary\n    jedis.set(bfoo1, bbar1);\n    jedis.select(1);\n    assertArrayEquals(null, jedis.get(bfoo1));\n    jedis.set(bfoo2, bbar2);\n    status = jedis.swapDB(0, 1);\n    assertEquals(\"OK\", status);\n    assertArrayEquals(bbar1, jedis.get(bfoo1));\n    assertArrayEquals(null, jedis.get(bfoo2));\n    jedis.select(0);\n    assertArrayEquals(null, jedis.get(bfoo1));\n    assertArrayEquals(bbar2, jedis.get(bfoo2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void flushDB() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.dbSize());\n    jedis.set(\"bar\", \"foo\");\n    jedis.move(\"bar\", 1);\n    String status = jedis.flushDB();\n    assertEquals(\"OK\", status);\n    assertEquals(0, jedis.dbSize());\n    jedis.select(1);\n    assertEquals(1, jedis.dbSize());\n    assertEquals(\"OK\", jedis.flushDB(FlushMode.SYNC));\n    assertEquals(0, jedis.dbSize());\n\n    // Binary\n    jedis.select(0);\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.dbSize());\n    jedis.set(bbar, bfoo);\n    jedis.move(bbar, 1);\n    String bstatus = jedis.flushDB();\n    assertEquals(\"OK\", bstatus);\n    assertEquals(0, jedis.dbSize());\n    jedis.select(1);\n    assertEquals(1, jedis.dbSize());\n    assertEquals(\"OK\", jedis.flushDB(FlushMode.ASYNC));\n    assertEquals(0, jedis.dbSize());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void flushAll() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.dbSize());\n    jedis.set(\"bar\", \"foo\");\n    jedis.move(\"bar\", 1);\n    String status = jedis.flushAll();\n    assertEquals(\"OK\", status);\n    assertEquals(0, jedis.dbSize());\n    jedis.select(1);\n    assertEquals(0, jedis.dbSize());\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.dbSize());\n    assertEquals(\"OK\", jedis.flushAll(FlushMode.SYNC));\n    assertEquals(0, jedis.dbSize());\n\n    // Binary\n    jedis.select(0);\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.dbSize());\n    jedis.set(bbar, bfoo);\n    jedis.move(bbar, 1);\n    String bstatus = jedis.flushAll();\n    assertEquals(\"OK\", bstatus);\n    assertEquals(0, jedis.dbSize());\n    jedis.select(1);\n    assertEquals(0, jedis.dbSize());\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.dbSize());\n    assertEquals(\"OK\", jedis.flushAll(FlushMode.ASYNC));\n    assertEquals(0, jedis.dbSize());\n  }\n\n  @Test\n  public void persist() {\n    jedis.setex(\"foo\", 60 * 60, \"bar\");\n    assertTrue(jedis.ttl(\"foo\") > 0);\n    assertEquals(1, jedis.persist(\"foo\"));\n    assertEquals(-1, jedis.ttl(\"foo\"));\n\n    // Binary\n    jedis.setex(bfoo, 60 * 60, bbar);\n    assertTrue(jedis.ttl(bfoo) > 0);\n    assertEquals(1, jedis.persist(bfoo));\n    assertEquals(-1, jedis.ttl(bfoo));\n  }\n\n  @Test\n  public void echo() {\n    String result = jedis.echo(\"hello world\");\n    assertEquals(\"hello world\", result);\n\n    // Binary\n    byte[] bresult = jedis.echo(SafeEncoder.encode(\"hello world\"));\n    assertArrayEquals(SafeEncoder.encode(\"hello world\"), bresult);\n  }\n\n  @Test\n  public void dumpAndRestore() {\n    jedis.set(\"foo1\", \"bar\");\n    byte[] sv = jedis.dump(\"foo1\");\n    jedis.restore(\"foo2\", 0, sv);\n    assertEquals(\"bar\", jedis.get(\"foo2\"));\n\n    jedis.set(bfoo1, bbar);\n    sv = jedis.dump(bfoo1);\n    jedis.restore(bfoo2, 0, sv);\n    assertArrayEquals(bbar, jedis.get(bfoo2));\n  }\n\n  @Test\n  @Disabled(value = \"TODO: Regression in 8.0-M02 discarding restore idle time.\")\n  public void restoreParams() {\n    // take a separate instance\n    Jedis jedis2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500);\n    jedis2.auth(endpoint.getPassword());\n    jedis2.flushAll();\n\n    jedis2.set(\"foo\", \"bar\");\n    jedis.set(\"from\", \"a\");\n    byte[] serialized = jedis.dump(\"from\");\n\n    try {\n      jedis2.restore(\"foo\", 0, serialized);\n      fail(\"Simple restore on a existing key should fail\");\n    } catch (JedisDataException e) {\n      // should be here\n    }\n    try {\n      jedis2.restore(\"foo\", 0, serialized, RestoreParams.restoreParams());\n      fail(\"Simple restore on a existing key should fail\");\n    } catch (JedisDataException e) {\n      // should be here\n    }\n    assertEquals(\"bar\", jedis2.get(\"foo\"));\n\n    jedis2.restore(\"foo\", 1000, serialized, RestoreParams.restoreParams().replace());\n    assertEquals(\"a\", jedis2.get(\"foo\"));\n    assertTrue(jedis2.pttl(\"foo\") <= 1000);\n\n    jedis2.restore(\"bar\", System.currentTimeMillis() + 1000, serialized, RestoreParams.restoreParams().replace().absTtl());\n    assertThat(jedis2.pttl(\"bar\"), Matchers.lessThanOrEqualTo(1000l + TIME_SKEW));\n\n\n    jedis2.restore(\"bar1\", 1000, serialized, RestoreParams.restoreParams().replace().idleTime(1000));\n    assertEquals(1000, jedis2.objectIdletime(\"bar1\").longValue());\n    jedis2.close();\n\n    Jedis lfuJedis = new Jedis(lfuEndpoint.getHostAndPort(), lfuEndpoint.getClientConfigBuilder().timeoutMillis(500).build());;\n    lfuJedis.restore(\"bar1\", 1000, serialized, RestoreParams.restoreParams().replace().frequency(90));\n    assertEquals(90, lfuJedis.objectFreq(\"bar1\").longValue());\n    lfuJedis.close();\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void pexpire() {\n    assertEquals(0, jedis.pexpire(\"foo\", 10000));\n\n    jedis.set(\"foo1\", \"bar1\");\n    assertEquals(1, jedis.pexpire(\"foo1\", 10000));\n\n    jedis.set(\"foo2\", \"bar2\");\n    assertEquals(1, jedis.pexpire(\"foo2\", 200000000000L));\n    assertEquals(0, jedis.pexpire(\"foo2\", 10000000, ExpiryOption.NX));\n    assertEquals(1, jedis.pexpire(\"foo2\", 10000000, ExpiryOption.XX));\n    assertEquals(0, jedis.pexpire(\"foo2\", 10000, ExpiryOption.GT));\n    assertEquals(1, jedis.pexpire(\"foo2\", 10000, ExpiryOption.LT));\n\n    long pttl = jedis.pttl(\"foo2\");\n    assertTrue(pttl > 100L);\n\n    // Binary\n    assertEquals(0, jedis.pexpire(bfoo, 10000));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.pexpire(bfoo, 10000));\n    assertEquals(0, jedis.pexpire(bfoo, 10000, ExpiryOption.NX));\n  }\n\n  @Test\n  public void pexpireAt() {\n    long unixTime = (System.currentTimeMillis()) + 10000;\n\n    assertEquals(0, jedis.pexpireAt(\"foo\", unixTime));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.pexpireAt(\"foo\", unixTime));\n\n    // Binary\n    assertEquals(0, jedis.pexpireAt(bfoo, unixTime));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.pexpireAt(bfoo, unixTime));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void pexpireTime() {\n    long unixTime = (System.currentTimeMillis()) + 10000;\n\n    jedis.set(\"foo\", \"bar\");\n    jedis.pexpireAt(\"foo\", unixTime);\n    assertEquals(unixTime, jedis.pexpireTime(\"foo\"), 0.0001);\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    jedis.pexpireAt(bfoo, unixTime);\n    assertEquals(unixTime, jedis.pexpireTime(bfoo), 0.0001);\n  }\n\n  @Test\n  public void pttl() {\n    assertEquals(-2, jedis.pttl(\"foo\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(-1, jedis.pttl(\"foo\"));\n\n    jedis.pexpire(\"foo\", 20000);\n    long pttl = jedis.pttl(\"foo\");\n    assertTrue(pttl >= 0 && pttl <= 20000);\n\n    // Binary\n    assertEquals(-2, jedis.pttl(bfoo));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(-1, jedis.pttl(bfoo));\n\n    jedis.pexpire(bfoo, 20000);\n    pttl = jedis.pttl(bfoo);\n    assertTrue(pttl >= 0 && pttl <= 20000);\n  }\n\n  @Test\n  public void psetex() {\n    long pttl;\n\n    jedis.psetex(\"foo\", 200000000000L, \"bar\");\n    pttl = jedis.pttl(\"foo\");\n    assertTrue(pttl > 100000000000L);\n\n    // Binary\n    jedis.psetex(bfoo, 200000000000L, bbar);\n    pttl = jedis.pttl(bfoo);\n    assertTrue(pttl > 100000000000L);\n  }\n\n  @Test\n  public void scan() {\n    jedis.set(\"b\", \"b\");\n    jedis.set(\"a\", \"a\");\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    ScanResult<byte[]> bResult = jedis.scan(SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void scanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.set(\"b\", \"b\");\n    jedis.set(\"a\", \"a\");\n    jedis.set(\"aa\", \"aa\");\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.match(bfoostar);\n\n    jedis.set(bfoo1, bbar);\n    jedis.set(bfoo2, bbar);\n    jedis.set(bfoo3, bbar);\n\n    ScanResult<byte[]> bResult = jedis.scan(SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void scanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.set(\"a\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.set(bfoo1, bbar);\n    jedis.set(bfoo2, bbar);\n    jedis.set(bfoo3, bbar);\n\n    ScanResult<byte[]> bResult = jedis.scan(SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void scanType() {\n    ScanParams noParams = new ScanParams();\n    ScanParams pagingParams = new ScanParams().count(4);\n\n    jedis.set(\"a\", \"a\");\n    jedis.hset(\"b\", \"b\", \"b\");\n    jedis.set(\"c\", \"c\");\n    jedis.sadd(\"d\", \"d\");\n    jedis.set(\"e\", \"e\");\n    jedis.zadd(\"f\", 0d, \"f\");\n    jedis.set(\"g\", \"g\");\n\n    // string\n    ScanResult<String> scanResult;\n\n    scanResult = jedis.scan(SCAN_POINTER_START, pagingParams, \"string\");\n    assertFalse(scanResult.isCompleteIteration());\n    int page1Count = scanResult.getResult().size();\n    scanResult = jedis.scan(scanResult.getCursor(), pagingParams, \"string\");\n    assertTrue(scanResult.isCompleteIteration());\n    int page2Count = scanResult.getResult().size();\n    assertEquals(4, page1Count + page2Count);\n\n\n    scanResult = jedis.scan(SCAN_POINTER_START, noParams, \"hash\");\n    assertEquals(Collections.singletonList(\"b\"), scanResult.getResult());\n    scanResult = jedis.scan(SCAN_POINTER_START, noParams, \"set\");\n    assertEquals(Collections.singletonList(\"d\"), scanResult.getResult());\n    scanResult = jedis.scan(SCAN_POINTER_START, noParams, \"zset\");\n    assertEquals(Collections.singletonList(\"f\"), scanResult.getResult());\n\n    // binary\n    final byte[] string = \"string\".getBytes();\n    final byte[] hash = \"hash\".getBytes();\n    final byte[] set = \"set\".getBytes();\n    final byte[] zset = \"zset\".getBytes();\n\n    ScanResult<byte[]> binaryResult;\n\n    jedis.set(\"a\", \"a\");\n    jedis.hset(\"b\", \"b\", \"b\");\n    jedis.set(\"c\", \"c\");\n    jedis.sadd(\"d\", \"d\");\n    jedis.set(\"e\", \"e\");\n    jedis.zadd(\"f\", 0d, \"f\");\n    jedis.set(\"g\", \"g\");\n\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, pagingParams, string);\n    assertFalse(binaryResult.isCompleteIteration());\n    page1Count = binaryResult.getResult().size();\n    binaryResult = jedis.scan(binaryResult.getCursorAsBytes(), pagingParams, string);\n    assertTrue(binaryResult.isCompleteIteration());\n    page2Count = binaryResult.getResult().size();\n    assertEquals(4, page1Count + page2Count);\n\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, noParams, hash);\n    AssertUtil.assertByteArrayListEquals(Collections.singletonList(new byte[]{98}), binaryResult.getResult());\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, noParams, set);\n    AssertUtil.assertByteArrayListEquals(Collections.singletonList(new byte[]{100}), binaryResult.getResult());\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, noParams, zset);\n    AssertUtil.assertByteArrayListEquals(Collections.singletonList(new byte[]{102}), binaryResult.getResult());\n  }\n\n  @Test\n  public void scanIsCompleteIteration() {\n    for (int i = 0; i < 100; i++) {\n      jedis.set(\"a\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START);\n    // note: in theory Redis would be allowed to already return all results on the 1st scan,\n    // but in practice this never happens for data sets greater than a few tens\n    // see: https://redis.io/commands/scan#number-of-elements-returned-at-every-scan-call\n    assertFalse(result.isCompleteIteration());\n\n    result = scanCompletely(result.getCursor());\n\n    assertNotNull(result);\n    assertTrue(result.isCompleteIteration());\n  }\n\n  private ScanResult<String> scanCompletely(String cursor) {\n    ScanResult<String> scanResult;\n    do {\n      scanResult = jedis.scan(cursor);\n      cursor = scanResult.getCursor();\n    } while (!SCAN_POINTER_START.equals(scanResult.getCursor()));\n\n    return scanResult;\n  }\n\n  @Test\n  public void setNxExAndGet() {\n    assertEquals(\"OK\", jedis.set(\"hello\", \"world\", SetParams.setParams().nx().ex(expireSeconds)));\n    assertEquals(\"world\", jedis.get(\"hello\"));\n\n    assertNull(jedis.set(\"hello\", \"bar\", SetParams.setParams().nx().ex(expireSeconds)));\n    assertEquals(\"world\", jedis.get(\"hello\"));\n\n    long ttl = jedis.ttl(\"hello\");\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n\n    // binary\n    byte[] bworld = { 0x77, 0x6F, 0x72, 0x6C, 0x64 };\n    byte[] bhello = { 0x68, 0x65, 0x6C, 0x6C, 0x6F };\n\n    assertEquals(\"OK\", jedis.set(bworld, bhello, SetParams.setParams().nx().ex(expireSeconds)));\n    assertArrayEquals(bhello, jedis.get(bworld));\n\n    assertNull(jedis.set(bworld, bbar, SetParams.setParams().nx().ex(expireSeconds)));\n    assertArrayEquals(bhello, jedis.get(bworld));\n\n    long bttl = jedis.ttl(bworld);\n    assertTrue(bttl > 0 && bttl <= expireSeconds);\n  }\n\n  @Test\n  public void setGetOptionTest() {\n    assertEquals(\"OK\", jedis.set(\"hello\", \"world\"));\n\n    // GET old value\n    assertEquals(\"world\", jedis.setGet(\"hello\", \"jedis\"));\n\n    assertEquals(\"jedis\", jedis.get(\"hello\"));\n\n    // GET null value\n    assertNull(jedis.setGet(\"key\", \"value\"));\n  }\n\n  @Test\n  public void setGet() {\n    assertEquals(\"OK\", jedis.set(\"hello\", \"world\"));\n\n    // GET old value\n    assertEquals(\"world\", jedis.setGet(\"hello\", \"jedis\", SetParams.setParams()));\n\n    assertEquals(\"jedis\", jedis.get(\"hello\"));\n\n    // GET null value\n    assertNull(jedis.setGet(\"key\", \"value\", SetParams.setParams()));\n  }\n\n  @Test\n  public void sendCommandTest() {\n    Object obj = jedis.sendCommand(SET, \"x\", \"1\");\n    String returnValue = SafeEncoder.encode((byte[]) obj);\n    assertEquals(\"OK\", returnValue);\n    obj = jedis.sendCommand(GET, \"x\");\n    returnValue = SafeEncoder.encode((byte[]) obj);\n    assertEquals(\"1\", returnValue);\n\n    jedis.sendCommand(RPUSH, \"foo\", \"a\");\n    jedis.sendCommand(RPUSH, \"foo\", \"b\");\n    jedis.sendCommand(RPUSH, \"foo\", \"c\");\n\n    obj = jedis.sendCommand(LRANGE, \"foo\", \"0\", \"2\");\n    List<byte[]> list = (List<byte[]>) obj;\n    List<byte[]> expected = new ArrayList<>(3);\n    expected.add(\"a\".getBytes());\n    expected.add(\"b\".getBytes());\n    expected.add(\"c\".getBytes());\n    for (int i = 0; i < 3; i++)\n      assertArrayEquals(expected.get(i), list.get(i));\n\n    assertEquals(\"PONG\", SafeEncoder.encode((byte[]) jedis.sendCommand(PING)));\n  }\n\n  @Test\n  public void sendBlockingCommandTest() {\n    assertNull(jedis.sendBlockingCommand(BLPOP, \"foo\", Long.toString(1L)));\n\n    jedis.sendCommand(RPUSH, \"foo\", \"bar\");\n    assertEquals(Arrays.asList(\"foo\", \"bar\"),\n      SafeEncoder.encodeObject(jedis.sendBlockingCommand(BLPOP, \"foo\", Long.toString(1L))));\n\n    assertNull(jedis.sendBlockingCommand(BLPOP, \"foo\", Long.toString(1L)));\n  }\n\n  @Test\n  public void encodeCompleteResponsePing() {\n    assertEquals(\"PONG\", SafeEncoder.encodeObject(jedis.sendCommand(PING)));\n  }\n\n  @Test\n  public void encodeCompleteResponseHgetall() {\n    Assumptions.assumeFalse(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entries = new HashMap<>();\n    entries.put(\"foo\", \"bar\");\n    entries.put(\"foo2\", \"bar2\");\n    jedis.hset(\"hash:test:encode\", entries);\n\n    List encodeObj = (List) SafeEncoder.encodeObject(jedis.sendCommand(HGETALL, \"hash:test:encode\"));\n\n    assertEquals(4, encodeObj.size());\n    entries.forEach((k, v) -> {\n      assertThat((Iterable<String>) encodeObj, Matchers.hasItem(k));\n      assertEquals(v, findValueFromMapAsList(encodeObj, k));\n    });\n  }\n\n  @Test\n  public void encodeCompleteResponseHgetallResp3() {\n    Assumptions.assumeTrue(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entries = new HashMap<>();\n    entries.put(\"foo\", \"bar\");\n    entries.put(\"foo2\", \"bar2\");\n    jedis.hset(\"hash:test:encode\", entries);\n\n    List<KeyValue> encodeObj = (List<KeyValue>) SafeEncoder.encodeObject(jedis.sendCommand(HGETALL, \"hash:test:encode\"));\n\n    assertEquals(2, encodeObj.size());\n    encodeObj.forEach(kv -> {\n      assertThat(entries, Matchers.hasEntry(kv.getKey(), kv.getValue()));\n    });\n  }\n\n  @Test\n  public void encodeCompleteResponseXinfoStream() {\n    Assumptions.assumeFalse(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entry = new HashMap<>();\n    entry.put(\"foo\", \"bar\");\n    StreamEntryID entryID = jedis.xadd(\"mystream\", StreamEntryID.NEW_ENTRY, entry);\n    jedis.xgroupCreate(\"mystream\", \"mygroup\", null, false);\n\n    Object obj = jedis.sendCommand(XINFO, \"STREAM\", \"mystream\");\n\n    List encodeObj = (List) SafeEncoder.encodeObject(obj);\n\n    assertThat(encodeObj.size(), Matchers.greaterThanOrEqualTo(14));\n    assertEquals( 0, encodeObj.size() % 2, \"must have even number of elements\"); // must be even\n\n    assertEquals(1L, findValueFromMapAsList(encodeObj, \"length\"));\n    assertEquals(entryID.toString(), findValueFromMapAsList(encodeObj, \"last-generated-id\"));\n\n    List<String> entryAsList = new ArrayList<>(2);\n    entryAsList.add(\"foo\");\n    entryAsList.add(\"bar\");\n\n    assertEquals(entryAsList, ((List) findValueFromMapAsList(encodeObj, \"first-entry\")).get(1));\n    assertEquals(entryAsList, ((List) findValueFromMapAsList(encodeObj, \"last-entry\")).get(1));\n  }\n\n  @Test\n  public void encodeCompleteResponseXinfoStreamResp3() {\n    Assumptions.assumeTrue(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entry = new HashMap<>();\n    entry.put(\"foo\", \"bar\");\n    StreamEntryID entryID = jedis.xadd(\"mystream\", StreamEntryID.NEW_ENTRY, entry);\n    jedis.xgroupCreate(\"mystream\", \"mygroup\", null, false);\n\n    Object obj = jedis.sendCommand(XINFO, \"STREAM\", \"mystream\");\n\n    List<KeyValue> encodeObj = (List<KeyValue>) SafeEncoder.encodeObject(obj);\n\n    assertThat(encodeObj.size(), Matchers.greaterThanOrEqualTo(7));\n\n    assertEquals(1L, findValueFromMapAsKeyValueList(encodeObj, \"length\"));\n    assertEquals(entryID.toString(), findValueFromMapAsKeyValueList(encodeObj, \"last-generated-id\"));\n\n    List<String> entryAsList = new ArrayList<>(2);\n    entryAsList.add(\"foo\");\n    entryAsList.add(\"bar\");\n\n    assertEquals(entryAsList, ((List) findValueFromMapAsKeyValueList(encodeObj, \"first-entry\")).get(1));\n    assertEquals(entryAsList, ((List) findValueFromMapAsKeyValueList(encodeObj, \"last-entry\")).get(1));\n  }\n\n  private Object findValueFromMapAsList(List list, Object key) {\n    for (int i = 0; i < list.size(); i += 2) {\n      if (key.equals(list.get(i))) {\n        return list.get(i + 1);\n      }\n    }\n    return null;\n  }\n\n  private Object findValueFromMapAsKeyValueList(List<KeyValue> list, Object key) {\n    for (KeyValue kv : list) {\n      if (key.equals(kv.getKey())) {\n        return kv.getValue();\n      }\n    }\n    return null;\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void copy() {\n    assertFalse(jedis.copy(\"unknown\", \"foo\", false));\n\n    jedis.set(\"foo1\", \"bar\");\n    assertTrue(jedis.copy(\"foo1\", \"foo2\", false));\n    assertEquals(\"bar\", jedis.get(\"foo2\"));\n\n    // with destinationDb\n    assertTrue(jedis.copy(\"foo1\", \"foo3\", 2, false));\n    jedis.select(2);\n    assertEquals(\"bar\", jedis.get(\"foo3\"));\n    jedis.select(0); // getting back to original db, for next tests\n\n    // replace\n    jedis.set(\"foo1\", \"bar1\");\n    assertTrue(jedis.copy(\"foo1\", \"foo2\", true));\n    assertEquals(\"bar1\", jedis.get(\"foo2\"));\n\n    // Binary\n    assertFalse(jedis.copy(bfoobar, bfoo, false));\n\n    jedis.set(bfoo1, bbar);\n    assertTrue(jedis.copy(bfoo1, bfoo2, false));\n    assertArrayEquals(bbar, jedis.get(bfoo2));\n\n    // with destinationDb\n    assertTrue(jedis.copy(bfoo1, bfoo3, 3, false));\n    jedis.select(3);\n    assertArrayEquals(bbar, jedis.get(bfoo3));\n    jedis.select(0); // getting back to original db, for next tests\n\n    // replace\n    jedis.set(bfoo1, bbar1);\n    assertTrue(jedis.copy(bfoo1, bfoo2, true));\n    assertArrayEquals(bbar1, jedis.get(bfoo2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void reset() {\n    // response test\n    String status = jedis.reset();\n    assertEquals(\"RESET\", status);\n\n    // auth reset\n    String counter = \"counter\";\n    Exception ex1 = assertThrows(JedisDataException.class, () -> {\n      jedis.set(counter, \"1\");\n    });\n    assertEquals(\"NOAUTH Authentication required.\", ex1.getMessage());\n\n    // multi reset\n    jedis.auth(endpoint.getPassword());\n    jedis.set(counter, \"1\");\n\n    Transaction trans = jedis.multi();\n    trans.incr(counter);\n    jedis.reset();\n\n    Exception ex2 = assertThrows(JedisDataException.class, trans::exec);\n    assertEquals(\"EXECABORT Transaction discarded because of: NOAUTH Authentication required.\", ex2.getMessage());\n\n    jedis.auth(endpoint.getPassword());\n    assertEquals(\"1\", jedis.get(counter));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void set_ex_ifeq_then_delex() {\n    String k = \"k:set-ex-ifeq\";\n    // Initial set with EX\n    assertEquals(\"OK\", jedis.set(k, \"v1\", SetParams.setParams().ex(100)));\n    assertTrue(jedis.ttl(k) > 0);\n\n    // Conditional update with IFEQ + EX\n    assertEquals(\"OK\", jedis.set(k, \"v2\",\n        SetParams.setParams().ex(200).condition(CompareCondition.valueEq(\"v1\"))));\n    assertEquals(\"v2\", jedis.get(k));\n    assertTrue(jedis.ttl(k) > 100);\n\n    // Delete with DELEX using value condition\n    assertEquals(0, jedis.delex(k, CompareCondition.valueEq(\"wrong\")));\n    assertEquals(1, jedis.delex(k, CompareCondition.valueEq(\"v2\")));\n    assertFalse(jedis.exists(k));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void set_exAt_ifne_then_delex() {\n    String k = \"k:set-exat-ifne\";\n    long expiryTimestamp = (System.currentTimeMillis() / 1000) + 300;\n\n    // Initial set\n    jedis.set(k, \"v1\");\n\n    // Conditional update with IFNE + EXAT\n    assertEquals(\"OK\", jedis.set(k, \"v2\",\n        SetParams.setParams().exAt(expiryTimestamp).condition(CompareCondition.valueNe(\"v2\"))));\n    assertEquals(\"v2\", jedis.get(k));\n    assertTrue(jedis.ttl(k) > 200);\n\n    // Delete with DELEX using value condition\n    assertEquals(0, jedis.delex(k, CompareCondition.valueNe(\"v2\")));\n    assertEquals(1, jedis.delex(k, CompareCondition.valueNe(\"wrong\")));\n    assertFalse(jedis.exists(k));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void setGet_px_ifdne_then_delex() {\n    String k = \"k:setget-px-ifdne\";\n    String wrongKey = \"wrong\";\n    // Initial set\n    jedis.set(k, \"A\");\n    jedis.set(wrongKey, \"wrong\");\n    String digestBefore = jedis.digestKey(k);\n    String digestWrong = jedis.digestKey(wrongKey); // digest for different value\n\n    // Conditional setGet with IFDNE + PX (digest not equal)\n    assertEquals(\"A\", jedis.setGet(k, \"B\",\n        SetParams.setParams().px(100000).condition(CompareCondition.digestNe(digestWrong))));\n    assertEquals(\"B\", jedis.get(k));\n    assertTrue(jedis.pttl(k) > 90000);\n\n    // Delete with DELEX using digest condition\n    String digestAfter = jedis.digestKey(k);\n    assertEquals(0, jedis.delex(k, CompareCondition.digestNe(digestAfter)));\n    assertEquals(1, jedis.delex(k, CompareCondition.digestNe(digestBefore)));\n    assertFalse(jedis.exists(k));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void setGet_pxAt_ifdeq_then_delex() {\n    String k = \"k:setget-pxat-ifdeq\";\n    long expiryTimestampMs = System.currentTimeMillis() + 300000;\n\n    // Initial set\n    jedis.set(k, \"X\");\n    String digestX = jedis.digestKey(k);\n\n    // Conditional setGet with IFDEQ + PXAT (digest equal)\n    assertEquals(\"X\", jedis.setGet(k, \"Y\", SetParams.setParams().pxAt(expiryTimestampMs)\n        .condition(CompareCondition.digestEq(digestX))));\n    assertEquals(\"Y\", jedis.get(k));\n    assertTrue(jedis.pttl(k) > 200000);\n\n    // Delete with DELEX using digest condition\n    String digestY = jedis.digestKey(k);\n    assertEquals(0, jedis.delex(k, CompareCondition.digestEq(digestX)));\n    assertEquals(1, jedis.delex(k, CompareCondition.digestEq(digestY)));\n    assertFalse(jedis.exists(k));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/BinaryValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.Protocol.Command.BLPOP;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.LRANGE;\nimport static redis.clients.jedis.Protocol.Command.RPUSH;\nimport static redis.clients.jedis.Protocol.Command.SET;\nimport static redis.clients.jedis.params.SetParams.setParams;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class BinaryValuesCommandsTest extends JedisCommandsTestBase {\n  byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  byte[] bxx = { 0x78, 0x78 };\n  byte[] bnx = { 0x6E, 0x78 };\n  byte[] bex = { 0x65, 0x78 };\n  byte[] bpx = { 0x70, 0x78 };\n  int expireSeconds = 2;\n  long expireMillis = expireSeconds * 1000;\n  byte[] binaryValue;\n\n  public BinaryValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  public void startUp() {\n    StringBuilder sb = new StringBuilder();\n\n    for (int n = 0; n < 1000; n++) {\n      sb.append(\"A\");\n    }\n\n    binaryValue = sb.toString().getBytes();\n  }\n\n  @Test\n  public void setAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setNxExAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setIfNotExistAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue));\n    // nx should fail if value exists\n    assertNull(jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setIfExistAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue));\n    // nx should fail if value exists\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().xx().ex(expireSeconds)));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setFailIfNotExistAndGet() {\n    // xx should fail if value does NOT exists\n    assertNull(jedis.set(bfoo, binaryValue, setParams().xx().ex(expireSeconds)));\n  }\n\n  @Test\n  public void setAndExpireMillis() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().px(expireMillis)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void setAndExpire() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void setAndKeepttl() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().keepttl()));\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().keepTtl()));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(0 < ttl && ttl <= expireSeconds);\n    jedis.set(bfoo, binaryValue);\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl < 0);\n  }\n\n  @Test\n  public void setAndPxat() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue,\n      setParams().nx().pxAt(System.currentTimeMillis() + expireMillis)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void setAndExat() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue,\n      setParams().nx().exAt(System.currentTimeMillis() / 1000 + expireSeconds)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void getSet() {\n    assertNull(jedis.getSet(bfoo, binaryValue));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n  }\n\n  @Test\n  public void getDel() {\n    assertEquals(\"OK\", jedis.set(bfoo, bbar));\n\n    assertArrayEquals(bbar, jedis.getDel(bfoo));\n\n    assertNull(jedis.get(bfoo));\n  }\n\n  @Test\n  public void getEx() {\n    assertNull(jedis.getEx(bfoo, GetExParams.getExParams().ex(1)));\n    jedis.set(bfoo, bbar);\n\n    assertArrayEquals(bbar, jedis.getEx(bfoo, GetExParams.getExParams().ex(10)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= 10);\n\n    assertArrayEquals(bbar, jedis.getEx(bfoo, GetExParams.getExParams().px(20000l)));\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 10 && ttl <= 20);\n\n    assertArrayEquals(bbar,\n      jedis.getEx(bfoo, GetExParams.getExParams().exAt(System.currentTimeMillis() / 1000 + 30)));\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 20 && ttl <= 30);\n\n    assertArrayEquals(bbar,\n      jedis.getEx(bfoo, GetExParams.getExParams().pxAt(System.currentTimeMillis() + 40000l)));\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 30 && ttl <= 40);\n\n    assertArrayEquals(bbar, jedis.getEx(bfoo, GetExParams.getExParams().persist()));\n    assertEquals(-1L, jedis.ttl(bfoo));\n  }\n\n  @Test\n  public void mget() {\n    List<byte[]> values = jedis.mget(bfoo, bbar);\n    List<byte[]> expected = new ArrayList<>();\n    expected.add(null);\n    expected.add(null);\n\n    assertByteArrayListEquals(expected, values);\n\n    jedis.set(bfoo, binaryValue);\n\n    expected = new ArrayList<>();\n    expected.add(binaryValue);\n    expected.add(null);\n    assertByteArrayListEquals(expected, jedis.mget(bfoo, bbar));\n\n    jedis.set(bbar, bfoo);\n\n    expected = new ArrayList<>();\n    expected.add(binaryValue);\n    expected.add(bfoo);\n    assertByteArrayListEquals(expected, jedis.mget(bfoo, bbar));\n  }\n\n  @Test\n  public void setnx() {\n    assertEquals(1, jedis.setnx(bfoo, binaryValue));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertEquals(0, jedis.setnx(bfoo, bbar));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n  }\n\n  @Test\n  public void setex() {\n    assertEquals(\"OK\", jedis.setex(bfoo, 20, binaryValue));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= 20);\n  }\n\n  @Test\n  public void mset() {\n    assertEquals(\"OK\", jedis.mset(bfoo, binaryValue, bbar, bfoo));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n    assertArrayEquals(bfoo, jedis.get(bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void msetnx() {\n    assertEquals(1, jedis.msetnx(bfoo, binaryValue, bbar, bfoo));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n    assertArrayEquals(bfoo, jedis.get(bbar));\n\n    assertEquals(0, jedis.msetnx(bfoo, bbar, \"bar2\".getBytes(), \"foo2\".getBytes()));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n    assertArrayEquals(bfoo, jedis.get(bbar));\n  }\n\n  @Test\n  public void incr() {\n    assertEquals(1, jedis.incr(bfoo));\n    assertEquals(2, jedis.incr(bfoo));\n  }\n\n  @Test\n  public void incrWrongValue() {\n    Assertions.assertThrows(JedisDataException.class, () -> {\n      jedis.set(bfoo, binaryValue);\n      jedis.incr(bfoo);\n    });\n  }\n\n  @Test\n  public void incrBy() {\n    assertEquals(2, jedis.incrBy(bfoo, 2));\n    assertEquals(4, jedis.incrBy(bfoo, 2));\n  }\n\n  @Test\n  public void incrByWrongValue() {\n    Assertions.assertThrows(JedisDataException.class, () -> {\n      jedis.set(bfoo, binaryValue);\n      jedis.incrBy(bfoo, 2);\n    });\n  }\n\n  @Test\n  public void incrByFloat() {\n    assertEquals(10.5, jedis.incrByFloat(bfoo, 10.5), 0.0);\n    assertEquals(10.6, jedis.incrByFloat(bfoo, 0.1), 0.0);\n  }\n\n  @Test\n  public void decr() {\n    assertEquals(-1, jedis.decr(bfoo));\n    assertEquals(-2, jedis.decr(bfoo));\n  }\n\n  @Test\n  public void decrWrongValue() {\n    Assertions.assertThrows(JedisDataException.class, () -> {\n      jedis.set(bfoo, binaryValue);\n      jedis.decr(bfoo);\n    });\n  }\n\n  @Test\n  public void decrBy() {\n    assertEquals(-2, jedis.decrBy(bfoo, 2));\n    assertEquals(-4, jedis.decrBy(bfoo, 2));\n  }\n\n  @Test\n  public void decrByWrongValue() {\n    Assertions.assertThrows(JedisDataException.class, () -> {\n      jedis.set(bfoo, binaryValue);\n      jedis.decrBy(bfoo, 2);\n    });\n  }\n\n  @Test\n  public void append() {\n    byte[] first512 = new byte[512];\n    System.arraycopy(binaryValue, 0, first512, 0, 512);\n    assertEquals(512, jedis.append(bfoo, first512));\n    assertArrayEquals(first512, jedis.get(bfoo));\n\n    byte[] rest = new byte[binaryValue.length - 512];\n    System.arraycopy(binaryValue, 512, rest, 0, binaryValue.length - 512);\n    assertEquals(binaryValue.length, jedis.append(bfoo, rest));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void substr() {\n    jedis.set(bfoo, binaryValue);\n\n    byte[] first512 = new byte[512];\n    System.arraycopy(binaryValue, 0, first512, 0, 512);\n    byte[] rfirst512 = jedis.substr(bfoo, 0, 511);\n    assertArrayEquals(first512, rfirst512);\n\n    byte[] last512 = new byte[512];\n    System.arraycopy(binaryValue, binaryValue.length - 512, last512, 0, 512);\n    assertArrayEquals(last512, jedis.substr(bfoo, -512, -1));\n\n    assertArrayEquals(binaryValue, jedis.substr(bfoo, 0, -1));\n\n    assertArrayEquals(last512, jedis.substr(bfoo, binaryValue.length - 512, 100000));\n  }\n\n  @Test\n  public void strlen() {\n    jedis.set(bfoo, binaryValue);\n    assertEquals(binaryValue.length, jedis.strlen(bfoo));\n  }\n\n  @Test\n  public void setGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, bbar));\n\n    // GET old value\n    assertArrayEquals(bbar, jedis.setGet(bfoo, binaryValue));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    // GET null value\n    assertNull(jedis.setGet(bbar, bfoo));\n  }\n\n  @Test\n  public void sendCommandTest() {\n    Object obj = jedis.sendCommand(SET, \"x\".getBytes(), \"1\".getBytes());\n    String returnValue = SafeEncoder.encode((byte[]) obj);\n    assertEquals(\"OK\", returnValue);\n    obj = jedis.sendCommand(GET, \"x\".getBytes());\n    returnValue = SafeEncoder.encode((byte[]) obj);\n    assertEquals(\"1\", returnValue);\n\n    jedis.sendCommand(RPUSH, \"foo\".getBytes(), \"a\".getBytes());\n    jedis.sendCommand(RPUSH, \"foo\".getBytes(), \"b\".getBytes());\n    jedis.sendCommand(RPUSH, \"foo\".getBytes(), \"c\".getBytes());\n\n    obj = jedis.sendCommand(LRANGE, \"foo\".getBytes(), \"0\".getBytes(), \"2\".getBytes());\n    List<byte[]> list = (List<byte[]>) obj;\n    List<byte[]> expected = new ArrayList<>(3);\n    expected.add(\"a\".getBytes());\n    expected.add(\"b\".getBytes());\n    expected.add(\"c\".getBytes());\n    for (int i = 0; i < 3; i++)\n      assertArrayEquals(expected.get(i), list.get(i));\n  }\n\n  @Test\n  public void sendBlockingCommandTest() {\n    assertNull(jedis.sendBlockingCommand(BLPOP, bfoo, Protocol.toByteArray(1L)));\n\n    jedis.sendCommand(RPUSH, bfoo, bbar);\n    List<byte[]> blpop = (List<byte[]>) jedis.sendBlockingCommand(BLPOP, bfoo,\n      Protocol.toByteArray(1L));\n    assertEquals(2, blpop.size());\n    assertArrayEquals(bfoo, blpop.get(0));\n    assertArrayEquals(bbar, blpop.get(1));\n\n    assertNull(jedis.sendBlockingCommand(BLPOP, bfoo, Protocol.toByteArray(1L)));\n  }\n\n  // MSETEX NX + expiration matrix (binary)\n  static Stream<Arguments> msetexNxArgsProvider() {\n    return java.util.stream.Stream.of(Arguments.of(\"EX\", new MSetExParams().nx().ex(5)),\n      Arguments.of(\"PX\", new MSetExParams().nx().px(5000)),\n      Arguments.of(\"EXAT\", new MSetExParams().nx().exAt(System.currentTimeMillis() / 1000 + 5)),\n      Arguments.of(\"PXAT\", new MSetExParams().nx().pxAt(System.currentTimeMillis() + 5000)),\n      Arguments.of(\"KEEPTTL\", new MSetExParams().nx().keepTtl()));\n  }\n\n  @ParameterizedTest(name = \"MSETEX NX + {0} (binary)\")\n  @MethodSource(\"msetexNxArgsProvider\")\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetexNx_binary_parametrized(String optionLabel, MSetExParams params) {\n    byte[] k1 = \"{t}msetex:jb:k1\".getBytes();\n    byte[] k2 = \"{t}msetex:jb:k2\".getBytes();\n\n    boolean result = jedis.msetex(params, k1, \"v1\".getBytes(), k2, \"v2\".getBytes());\n    assertTrue(result);\n\n    long ttl = jedis.ttl(k1);\n    if (\"KEEPTTL\".equals(optionLabel)) {\n      assertEquals(-1L, ttl);\n    } else {\n      assertTrue(ttl > 0L);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/BitCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.BitPosParams;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class BitCommandsTest extends JedisCommandsTestBase {\n\n  public BitCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void setAndgetbit() {\n    assertFalse(jedis.setbit(\"foo\", 0, true));\n\n    assertTrue(jedis.getbit(\"foo\", 0));\n\n    // Binary\n    assertFalse(jedis.setbit(\"bfoo\".getBytes(), 0, true));\n\n    assertTrue(jedis.getbit(\"bfoo\".getBytes(), 0));\n  }\n\n  @Test\n  public void bitpos() {\n    String foo = \"foo\";\n\n    jedis.set(foo, String.valueOf(0));\n    //  string \"0\" with bits: 0011 0000\n\n    jedis.setbit(foo, 3, true);\n    jedis.setbit(foo, 7, true);\n    jedis.setbit(foo, 13, true);\n    jedis.setbit(foo, 39, true);\n\n    /*\n     * bit:  00110001 / 00000100 / 00000000 / 00000000 / 00000001\n     * byte: 0          1          2          3          4\n     */\n    long offset = jedis.bitpos(foo, true);\n    assertEquals(2, offset);\n    offset = jedis.bitpos(foo, false);\n    assertEquals(0, offset);\n\n    offset = jedis.bitpos(foo, true, new BitPosParams(1));\n    assertEquals(13, offset);\n    offset = jedis.bitpos(foo, false, new BitPosParams(1));\n    assertEquals(8, offset);\n\n    offset = jedis.bitpos(foo, true, new BitPosParams(2, 3));\n    assertEquals(-1, offset);\n    offset = jedis.bitpos(foo, false, new BitPosParams(2, 3));\n    assertEquals(16, offset);\n\n    offset = jedis.bitpos(foo, true, new BitPosParams(3, 4));\n    assertEquals(39, offset);\n  }\n\n  @Test\n  public void bitposBinary() {\n    // binary\n    byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n\n    jedis.set(bfoo, Protocol.toByteArray(0));\n    // bits: 0011 0000\n\n    jedis.setbit(bfoo, 3, true);\n    jedis.setbit(bfoo, 7, true);\n    jedis.setbit(bfoo, 13, true);\n    jedis.setbit(bfoo, 39, true);\n\n    /*\n     * bit:  00110001 / 00000100 / 00000000 / 00000000 / 00000001\n     * byte: 0          1          2          3          4\n     */\n    long offset = jedis.bitpos(bfoo, true);\n    assertEquals(2, offset);\n    offset = jedis.bitpos(bfoo, false);\n    assertEquals(0, offset);\n\n    offset = jedis.bitpos(bfoo, true, new BitPosParams(1));\n    assertEquals(13, offset);\n    offset = jedis.bitpos(bfoo, false, new BitPosParams(1));\n    assertEquals(8, offset);\n\n    offset = jedis.bitpos(bfoo, true, new BitPosParams(2, 3));\n    assertEquals(-1, offset);\n    offset = jedis.bitpos(bfoo, false, new BitPosParams(2, 3));\n    assertEquals(16, offset);\n\n    offset = jedis.bitpos(bfoo, true, new BitPosParams(3, 4));\n    assertEquals(39, offset);\n  }\n\n  @Test\n  public void bitposWithNoMatchingBitExist() {\n    String foo = \"foo\";\n\n    jedis.set(foo, String.valueOf(0));\n    for (int idx = 0; idx < 8; idx++) {\n      jedis.setbit(foo, idx, true);\n    }\n\n    /*\n     * bit:  11111111\n     * byte: 0\n     */\n    long offset = jedis.bitpos(foo, false);\n    // offset should be last index + 1\n    assertEquals(8, offset);\n  }\n\n  @Test\n  public void bitposWithNoMatchingBitExistWithinRange() {\n    String foo = \"foo\";\n\n    jedis.set(foo, String.valueOf(0));\n    for (int idx = 0; idx < 8 * 5; idx++) {\n      jedis.setbit(foo, idx, true);\n    }\n\n    /*\n     * bit:  11111111 / 11111111 / 11111111 / 11111111 / 11111111\n     * byte: 0          1          2          3          4\n     */\n    long offset = jedis.bitpos(foo, false, new BitPosParams(2, 3));\n    // offset should be -1\n    assertEquals(-1, offset);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"7.0.0 Added the BYTE|BIT option.\")\n  public void bitposModifier() {\n    jedis.set(\"mykey\", \"\\\\x00\\\\xff\\\\xf0\");\n    assertEquals(0, jedis.bitpos(\"mykey\", false));\n    assertEquals(1, jedis.bitpos(\"mykey\", true));\n    assertEquals(1, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams()));\n    assertEquals(18, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(2)));\n    assertEquals(18, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(2).end(-1)));\n    assertEquals(18, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(2).end(-1)\n        .modifier(BitCountOption.BYTE)));\n    assertEquals(9, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(7).end(15)\n        .modifier(BitCountOption.BIT)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void setAndgetrange() {\n    jedis.set(\"key1\", \"Hello World\");\n    assertEquals(11, jedis.setrange(\"key1\", 6, \"Jedis\"));\n\n    assertEquals(\"Hello Jedis\", jedis.get(\"key1\"));\n\n    assertEquals(\"Hello\", jedis.getrange(\"key1\", 0, 4));\n    assertEquals(\"Jedis\", jedis.getrange(\"key1\", 6, 11));\n  }\n\n  @Test\n  public void bitCount() {\n    jedis.setbit(\"foo\", 16, true);\n    jedis.setbit(\"foo\", 24, true);\n    jedis.setbit(\"foo\", 40, true);\n    jedis.setbit(\"foo\", 56, true);\n\n    assertEquals(4, (long) jedis.bitcount(\"foo\"));\n    assertEquals(4, (long) jedis.bitcount(\"foo\".getBytes()));\n\n    assertEquals(3, (long) jedis.bitcount(\"foo\", 2L, 5L));\n    assertEquals(3, (long) jedis.bitcount(\"foo\".getBytes(), 2L, 5L));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void bitCountModifier() {\n    jedis.setbit(\"foo\", 16, true);\n    jedis.setbit(\"foo\", 24, true);\n    jedis.setbit(\"foo\", 40, true);\n    jedis.setbit(\"foo\", 56, true);\n\n    assertEquals(3, (long) jedis.bitcount(\"foo\", 2L, 5L, BitCountOption.BYTE));\n    assertEquals(3, (long) jedis.bitcount(\"foo\".getBytes(), 2L, 5L, BitCountOption.BYTE));\n\n    assertEquals(0, (long) jedis.bitcount(\"foo\", 2L, 5L, BitCountOption.BIT));\n    assertEquals(0, (long) jedis.bitcount(\"foo\".getBytes(), 2L, 5L, BitCountOption.BIT));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOp() {\n    jedis.set(\"key1\", \"\\u0060\");\n    jedis.set(\"key2\", \"\\u0044\");\n\n    jedis.bitop(BitOP.AND, \"resultAnd\", \"key1\", \"key2\");\n    String resultAnd = jedis.get(\"resultAnd\");\n    assertEquals(\"\\u0040\", resultAnd);\n\n    jedis.bitop(BitOP.OR, \"resultOr\", \"key1\", \"key2\");\n    String resultOr = jedis.get(\"resultOr\");\n    assertEquals(\"\\u0064\", resultOr);\n\n    jedis.bitop(BitOP.XOR, \"resultXor\", \"key1\", \"key2\");\n    String resultXor = jedis.get(\"resultXor\");\n    assertEquals(\"\\u0024\", resultXor);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpNot() {\n    jedis.setbit(\"key\", 0, true);\n    jedis.setbit(\"key\", 4, true);\n\n    jedis.bitop(BitOP.NOT, \"resultNot\", \"key\");\n    String resultNot = jedis.get(\"resultNot\");\n    assertEquals(\"\\u0077\", resultNot);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpBinary() {\n    byte[] dest = {0x0};\n    byte[] key1 = {0x1};\n    byte[] key2 = {0x2};\n\n    jedis.set(key1, new byte[]{0x6});\n    jedis.set(key2, new byte[]{0x3});\n\n    jedis.bitop(BitOP.AND, dest, key1, key2);\n    assertArrayEquals(new byte[]{0x2}, jedis.get(dest));\n\n    jedis.bitop(BitOP.OR, dest, key1, key2);\n    assertArrayEquals(new byte[]{0x7}, jedis.get(dest));\n\n    jedis.bitop(BitOP.XOR, dest, key1, key2);\n    assertArrayEquals(new byte[]{0x5}, jedis.get(dest));\n\n    jedis.setbit(key1, 0, true);\n    jedis.bitop(BitOP.NOT, dest, key1);\n    assertArrayEquals(new byte[]{0x79}, jedis.get(dest));\n  }\n\n  @Test\n  public void bitOpNotMultiSourceShouldFail() {\n    Assertions.assertThrows(JedisDataException.class, () -> {\n      jedis.bitop(BitOP.NOT, \"dest\", \"src1\", \"src2\");\n    });\n  }\n\n  @Test\n  public void testBitfield() {\n    List<Long> responses = jedis.bitfield(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n  }\n\n  @Test\n  public void testBitfieldReadonly() {\n    List<Long> responses = jedis.bitfield(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n\n    List<Long> responses2 = jedis.bitfieldReadonly(\"mykey\", \"GET\", \"i5\", \"100\");\n    assertEquals(1L, responses2.get(0).longValue());\n\n    try {\n      jedis.bitfieldReadonly(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n      fail(\"Readonly command shouldn't allow INCRBY\");\n    } catch (JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void testBinaryBitfield() {\n    List<Long> responses = jedis.bitfield(SafeEncoder.encode(\"mykey\"),\n      SafeEncoder.encode(\"INCRBY\"), SafeEncoder.encode(\"i5\"), SafeEncoder.encode(\"100\"),\n      SafeEncoder.encode(\"1\"), SafeEncoder.encode(\"GET\"), SafeEncoder.encode(\"u4\"),\n      SafeEncoder.encode(\"0\"));\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n  }\n\n  @Test\n  public void testBinaryBitfieldReadonly() {\n    List<Long> responses = jedis.bitfield(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n\n    List<Long> responses2 = jedis.bitfieldReadonly(SafeEncoder.encode(\"mykey\"),\n      SafeEncoder.encode(\"GET\"), SafeEncoder.encode(\"i5\"), SafeEncoder.encode(\"100\"));\n    assertEquals(1L, responses2.get(0).longValue());\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpDiff() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0b00000111 }; // bits 0,1,2 set\n    byte[] key2 = new byte[] { (byte) 0b00000010 }; // bit 1 set\n    byte[] key3 = new byte[] { (byte) 0b00000100 }; // bit 2 set\n    String destKey = \"resultDiff\";\n\n    // Set keys using byte arrays\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // DIFF(key1, key2, key3) = key1 AND NOT(key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key2 OR key3) = 11111001 (all bits except 1,2 set)\n    // key1 AND NOT(key2 OR key3) = 00000001 (only bit 0 set)\n    jedis.bitop(BitOP.DIFF, destKey, \"key1\", \"key2\", \"key3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000001 (only bit 0 set)\n    byte[] expectedBytes = new byte[] { (byte) 0b00000001 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpDiff1() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"resultDiff1\";\n\n    // Set keys using byte arrays\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // DIFF1(key1, key2, key3) = NOT(key1) AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key1) = 11111000 (all bits except 0,1,2 set)\n    // NOT(key1) AND (key2 OR key3) = 00000000 (no bits set)\n    jedis.bitop(BitOP.DIFF1, destKey, \"key1\", \"key2\", \"key3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000000 (no bits set)\n    byte[] expectedBytes = new byte[] { (byte) 0x00 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpAndor() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"resultAndor\";\n\n    // Set keys using byte arrays\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // ANDOR(key1, key2, key3) = key1 AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // key1 AND (key2 OR key3) = 00000110 (bits 1,2 set)\n    jedis.bitop(BitOP.ANDOR, destKey, \"key1\", \"key2\", \"key3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000110 (bits 1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x06 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpOne() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x01 }; // 00000001 - bit 0 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"resultOne\";\n\n    // Set keys using byte arrays\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // ONE(key1, key2, key3) = bits set in exactly one of the inputs\n    // key1 = 00000001 (bit 0 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // Result = 00000111 (bits 0,1,2 set - each in exactly one input)\n    jedis.bitop(BitOP.ONE, destKey, \"key1\", \"key2\", \"key3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000111 (bits 0,1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x07 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpDiffBinary() {\n    byte[] key1 = { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"resultDiffBinary\".getBytes();\n\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // DIFF(key1, key2, key3) = key1 AND NOT(key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key2 OR key3) = 11111001 (all bits except 1,2 set)\n    // key1 AND NOT(key2 OR key3) = 00000001 (only bit 0 set)\n    jedis.bitop(BitOP.DIFF, dest, \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes());\n\n    // Expected result: 00000001 (only bit 0 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x01 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpDiff1Binary() {\n    byte[] key1 = { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"resultDiff1Binary\".getBytes();\n\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // DIFF1(key1, key2, key3) = NOT(key1) AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key1) = 11111000 (all bits except 0,1,2 set)\n    // NOT(key1) AND (key2 OR key3) = 00000000 (no bits set)\n    jedis.bitop(BitOP.DIFF1, dest, \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes());\n\n    // Expected result: 00000000 (no bits set)\n    byte[] expectedBytes = new byte[] { (byte) 0x00 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpAndorBinary() {\n    byte[] key1 = { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"resultAndorBinary\".getBytes();\n\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // ANDOR(key1, key2, key3) = key1 AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // key1 AND (key2 OR key3) = 00000110 (bits 1,2 set)\n    jedis.bitop(BitOP.ANDOR, dest, \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes());\n\n    // Expected result: 00000110 (bits 1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x06 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpOneBinary() {\n    byte[] key1 = { (byte) 0x01 }; // 00000001 - bit 0 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"resultOneBinary\".getBytes();\n\n    jedis.set(\"key1\".getBytes(), key1);\n    jedis.set(\"key2\".getBytes(), key2);\n    jedis.set(\"key3\".getBytes(), key3);\n\n    // ONE(key1, key2, key3) = bits set in exactly one of the inputs\n    // key1 = 00000001 (bit 0 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // Result = 00000111 (bits 0,1,2 set - each in exactly one input)\n    jedis.bitop(BitOP.ONE, dest, \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes());\n\n    // Expected result: 00000111 (bits 0,1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x07 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiffSingleSourceShouldFail() {\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.DIFF, \"dest\", \"src1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff1SingleSourceShouldFail() {\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.DIFF1, \"dest\", \"src1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpAndorSingleSourceShouldFail() {\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.ANDOR, \"dest\", \"src1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiffBinarySingleSourceShouldFail() {\n    byte[] dest = \"dest\".getBytes();\n    byte[] src1 = \"src1\".getBytes();\n\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.DIFF, dest, src1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff1BinarySingleSourceShouldFail() {\n    byte[] dest = \"dest\".getBytes();\n    byte[] src1 = \"src1\".getBytes();\n\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.DIFF1, dest, src1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpAndorBinarySingleSourceShouldFail() {\n    byte[] dest = \"dest\".getBytes();\n    byte[] src1 = \"src1\".getBytes();\n\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.ANDOR, dest, src1));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClientCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.params.ClientKillParams.SkipMe;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\n\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ClientAttributeOption;\nimport redis.clients.jedis.args.ClientType;\nimport redis.clients.jedis.args.UnblockType;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.params.ClientKillParams;\nimport redis.clients.jedis.resps.TrackingInfo;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ClientCommandsTest extends JedisCommandsTestBase {\n\n  private final String clientName = \"fancy_jedis_name\";\n  private final Pattern pattern = Pattern.compile(\"\\\\bname=\" + clientName + \"\\\\b\");\n\n  private Jedis client;\n\n  public ClientCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n    client = new Jedis(endpoint.getHost(), endpoint.getPort(), 500);\n    client.auth(endpoint.getPassword());\n    client.clientSetname(clientName);\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    client.close();\n    super.tearDown();\n  }\n\n  @Test\n  public void nameString() {\n    String name = \"string\";\n    client.clientSetname(name);\n    assertEquals(name, client.clientGetname());\n  }\n\n  @Test\n  public void nameBinary() {\n    byte[] name = \"binary\".getBytes();\n    client.clientSetname(name);\n    assertArrayEquals(name, client.clientGetnameBinary());\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.2.0\")\n  public void clientSetInfoCommand() {\n    String libName = \"Jedis::A-Redis-Java-library\";\n    String libVersion = \"999.999.999\";\n    assertEquals(\"OK\", client.clientSetInfo(ClientAttributeOption.LIB_NAME, libName));\n    assertEquals(\"OK\", client.clientSetInfo(ClientAttributeOption.LIB_VER, libVersion));\n    String info = client.clientInfo();\n    assertTrue(info.contains(\"lib-name=\" + libName));\n    assertTrue(info.contains(\"lib-ver=\" + libVersion));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.2.0\")\n  public void clientSetInfoCommandExpandLibName() {\n    String baseLibName = \"Jedis::A-Redis-Java-library\";\n    String upstreamDriver = \"spring-data-redis_v3.2.0\";\n    String expandedLibName = baseLibName + \"(\" + upstreamDriver + \")\";\n    String libVersion = \"999.999.999\";\n\n    assertEquals(\"OK\", client.clientSetInfo(ClientAttributeOption.LIB_NAME, expandedLibName));\n    assertEquals(\"OK\", client.clientSetInfo(ClientAttributeOption.LIB_VER, libVersion));\n    String info = client.clientInfo();\n    assertTrue(info.contains(\"lib-name=\" + expandedLibName));\n    assertTrue(info.contains(\"lib-ver=\" + libVersion));\n  }\n\n  @Test\n  public void clientId() {\n    long clientId = client.clientId();\n\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\bid=(\\\\d+)\\\\b\").matcher(info);\n    matcher.find();\n\n    assertEquals(clientId, Long.parseLong(matcher.group(1)));\n  }\n\n  @Test\n  public void clientIdmultipleConnection() {\n    try (Jedis client2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500)) {\n      client2.auth(endpoint.getPassword());\n      client2.clientSetname(\"fancy_jedis_another_name\");\n\n      // client-id is monotonically increasing\n      assertTrue(client.clientId() < client2.clientId());\n    }\n  }\n\n  @Test\n  public void clientIdReconnect() {\n    long clientIdInitial = client.clientId();\n    client.disconnect();\n    client.connect();\n    client.auth(endpoint.getPassword());\n    long clientIdAfterReconnect = client.clientId();\n\n    assertTrue(clientIdInitial < clientIdAfterReconnect);\n  }\n\n  @Test\n  public void clientUnblock() throws InterruptedException, TimeoutException {\n    long clientId = client.clientId();\n    assertEquals(0, jedis.clientUnblock(clientId, UnblockType.ERROR));\n    Future<?> future = Executors.newSingleThreadExecutor()\n        .submit(() -> client.brpop(100000, \"foo\"));\n\n    try {\n      // to make true command already executed\n      TimeUnit.MILLISECONDS.sleep(500);\n      assertEquals(1, jedis.clientUnblock(clientId, UnblockType.ERROR));\n      future.get(1, TimeUnit.SECONDS);\n    } catch (ExecutionException e) {\n      assertEquals(\n        \"redis.clients.jedis.exceptions.JedisDataException: UNBLOCKED client unblocked via CLIENT UNBLOCK\",\n        e.getMessage());\n    }\n  }\n\n  @Test\n  public void killIdString() {\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\bid=(\\\\d+)\\\\b\").matcher(info);\n    matcher.find();\n    String id = matcher.group(1);\n\n    assertEquals(1, jedis.clientKill(new ClientKillParams().id(id)));\n\n    assertDisconnected(client);\n  }\n\n  @Test\n  public void killIdBinary() {\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\bid=(\\\\d+)\\\\b\").matcher(info);\n    matcher.find();\n    byte[] id = matcher.group(1).getBytes();\n\n    assertEquals(1, jedis.clientKill(new ClientKillParams().id(id)));\n\n    assertDisconnected(client);\n  }\n\n  @Test\n  public void killTypeNormal() {\n    long clients = jedis.clientKill(new ClientKillParams().type(ClientType.NORMAL));\n    assertTrue(clients > 0);\n    assertDisconnected(client);\n  }\n\n  @Test\n  public void killSkipmeNo() {\n    jedis.clientKill(new ClientKillParams().type(ClientType.NORMAL).skipMe(SkipMe.NO));\n    assertDisconnected(client);\n    assertDisconnected(jedis);\n  }\n\n  @Test\n  public void killSkipmeYesNo() {\n    jedis.clientKill(new ClientKillParams().type(ClientType.NORMAL).skipMe(SkipMe.YES));\n    assertDisconnected(client);\n\n    ClientKillParams skipmeNo = new ClientKillParams().type(ClientType.NORMAL).skipMe(SkipMe.NO);\n    assertThat(jedis.clientKill(skipmeNo), greaterThanOrEqualTo(1L));\n    assertDisconnected(jedis);\n  }\n\n  @Test\n  public void killAddrString() {\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\baddr=(\\\\S+)\\\\b\").matcher(info);\n    matcher.find();\n    String addr = matcher.group(1);\n\n    assertEquals(1, jedis.clientKill(new ClientKillParams().addr(addr)));\n\n    assertDisconnected(client);\n  }\n\n  @Test\n  public void killAddrBinary() {\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\baddr=(\\\\S+)\\\\b\").matcher(info);\n    matcher.find();\n    String addr = matcher.group(1);\n\n    assertEquals(1, jedis.clientKill(new ClientKillParams().addr(addr)));\n\n    assertDisconnected(client);\n  }\n\n  @Test\n  public void killLAddr() {\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\bladdr=(\\\\S+)\\\\b\").matcher(info);\n    matcher.find();\n    String laddr = matcher.group(1);\n\n    long clients = jedis.clientKill(new ClientKillParams().laddr(laddr));\n    assertTrue(clients >= 1);\n\n    assertDisconnected(client);\n  }\n\n  @Test\n  public void killAddrIpPort() {\n    String info = findInClientList();\n    Matcher matcher = Pattern.compile(\"\\\\baddr=(\\\\S+)\\\\b\").matcher(info);\n    matcher.find();\n    String addr = matcher.group(1);\n    int lastColon = addr.lastIndexOf(\":\");\n    String[] hp = new String[] { addr.substring(0, lastColon), addr.substring(lastColon + 1) };\n\n    assertEquals(1, jedis.clientKill(new ClientKillParams().addr(hp[0], Integer.parseInt(hp[1]))));\n\n    assertDisconnected(client);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void killUser() {\n    client.aclSetUser(\"test_kill\", \"on\", \"+acl\", \">password1\");\n    try (Jedis client2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500)) {\n      client2.auth(\"test_kill\", \"password1\");\n\n      assertEquals(1, jedis.clientKill(new ClientKillParams().user(\"test_kill\")));\n      assertDisconnected(client2);\n    } finally {\n      jedis.aclDelUser(\"test_kill\");\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"MAXAGE (since Redis 7.4)\")\n  public void killMaxAge() throws InterruptedException {\n    long maxAge = 2;\n\n    // sleep twice the maxAge, to be sure\n    Thread.sleep(maxAge * 2 * 1000);\n\n    try (Jedis client2 = new Jedis(endpoint.getHost(), endpoint.getPort(), 500)) {\n      client2.auth(endpoint.getPassword());\n\n      long killedClients = jedis.clientKill(new ClientKillParams().maxAge(maxAge));\n\n      // The reality is that some tests leak clients, so we can't assert\n      // on the exact number of killed clients.\n      assertTrue(killedClients > 0);\n\n      assertDisconnected(client);\n      assertConnected(client2);\n    }\n  }\n\n  @Test\n  public void clientInfo() {\n    String info = client.clientInfo();\n    assertNotNull(info);\n    assertEquals(1, info.split(\"\\n\").length);\n    assertTrue(info.contains(clientName));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clientListWithClientId() {\n    long id = client.clientId();\n    String listInfo = jedis.clientList(id);\n    assertNotNull(listInfo);\n    assertTrue(listInfo.contains(clientName));\n  }\n\n  @Test\n  public void listWithType() {\n    assertTrue(client.clientList(ClientType.NORMAL).split(\"\\\\n\").length > 1);\n    assertEquals(0, client.clientList(ClientType.MASTER).length());\n    assertEquals(1, client.clientList(ClientType.SLAVE).split(\"\\\\n\").length);\n    if (!TestEnvUtil.getTestEnvProvider().equals(TestEnvUtil.ENV_REDIS_ENTERPRISE)) {\n      assertEquals(1, client.clientList(ClientType.REPLICA).split(\"\\\\n\").length);\n    }\n    assertEquals(1, client.clientList(ClientType.PUBSUB).split(\"\\\\n\").length);\n  }\n\n  @Test\n  public void trackingInfo() {\n    TrackingInfo trackingInfo = client.clientTrackingInfo();\n\n    assertEquals(1, trackingInfo.getFlags().size());\n    assertEquals(-1, trackingInfo.getRedirect());\n    assertEquals(0, trackingInfo.getPrefixes().size());\n  }\n\n  @Test\n  public void trackingInfoResp3() {\n    Jedis clientResp3 = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().protocol(RedisProtocol.RESP3).build());\n    TrackingInfo trackingInfo = clientResp3.clientTrackingInfo();\n\n    assertEquals(1, trackingInfo.getFlags().size());\n    assertEquals(-1, trackingInfo.getRedirect());\n    assertEquals(0, trackingInfo.getPrefixes().size());\n  }\n\n  private void assertDisconnected(Jedis j) {\n    try {\n      j.ping();\n      fail(\"Jedis connection should be disconnected\");\n    } catch (JedisConnectionException jce) {\n      // should be here\n    }\n  }\n\n  private void assertConnected(Jedis j) {\n    assertEquals(\"PONG\", j.ping());\n  }\n\n  private String findInClientList() {\n    for (String clientInfo : jedis.clientList().split(\"\\n\")) {\n      if (pattern.matcher(clientInfo).find()) {\n        return clientInfo;\n      }\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterBinaryValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport static redis.clients.jedis.Protocol.Command.*;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.util.SafeEncoder;\n\npublic class ClusterBinaryValuesCommandsTest extends ClusterJedisCommandsTestBase {\n\n  @Test\n  public void nullKeys() {\n    String foo = \"foo\";\n\n    try {\n      cluster.exists((String) null);\n      fail();\n    } catch (NullPointerException e) {\n      // expected\n    }\n\n    try {\n      cluster.exists(foo, null);\n      fail();\n    } catch (NullPointerException e) {\n      // expected\n    }\n\n    try {\n      cluster.exists(null, foo);\n      fail();\n    } catch (NullPointerException e) {\n      // expected\n    }\n  }\n\n  @Test\n  public void testBinaryGetAndSet() {\n    byte[] byteKey = \"foo\".getBytes();\n    byte[] byteValue = \"2\".getBytes();\n    cluster.set(byteKey, byteValue);\n    assertArrayEquals(byteValue, cluster.get(byteKey));\n  }\n\n  @Test\n  public void testIncr() {\n    byte[] byteKey = \"foo\".getBytes();\n    byte[] byteValue = \"2\".getBytes();\n    cluster.set(byteKey, byteValue);\n    cluster.incr(byteKey);\n    assertArrayEquals(\"3\".getBytes(), cluster.get(byteKey));\n  }\n\n  @Test\n  public void testSadd() {\n    byte[] byteKey = \"languages\".getBytes();\n    byte[] firstLanguage = \"java\".getBytes();\n    byte[] secondLanguage = \"python\".getBytes();\n    byte[][] listLanguages = { firstLanguage, secondLanguage };\n    cluster.sadd(byteKey, listLanguages);\n    Set<byte[]> setLanguages = cluster.smembers(byteKey);\n    List<String> languages = new ArrayList<>();\n    for (byte[] language : setLanguages) {\n      languages.add(new String(language));\n    }\n    assertTrue(languages.contains(\"java\"));\n    assertTrue(languages.contains(\"python\"));\n  }\n\n  @Test\n  public void testHmset() {\n    byte[] key = \"jedis\".getBytes();\n    byte[] field = \"language\".getBytes();\n    byte[] value = \"java\".getBytes();\n    HashMap<byte[], byte[]> map = new HashMap();\n    map.put(field, value);\n    cluster.hmset(key, map);\n    List<byte[]> listResults = cluster.hmget(key, field);\n    for (byte[] result : listResults) {\n      assertArrayEquals(value, result);\n    }\n  }\n\n  @Test\n  public void testRpush() {\n    byte[] value1 = \"value1\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n    byte[] key = \"key1\".getBytes();\n    cluster.del(key);\n    cluster.rpush(key, value1);\n    cluster.rpush(key, value2);\n    assertEquals(2, (long) cluster.llen(key));\n  }\n\n  @Test\n  public void georadiusStoreBinary() {\n      // prepare datas\n      Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<byte[], GeoCoordinate>();\n      bcoordinateMap.put(\"Palermo\".getBytes(), new GeoCoordinate(13.361389, 38.115556));\n      bcoordinateMap.put(\"Catania\".getBytes(), new GeoCoordinate(15.087269, 37.502669));\n      cluster.geoadd(\"{Sicily}\".getBytes(), bcoordinateMap);\n\n      long size = cluster.georadiusStore(\"{Sicily}\".getBytes(), 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"{Sicily}Store\"));\n      assertEquals(2, size);\n      List<byte[]> bexpected = new ArrayList<byte[]>();\n      bexpected.add(\"Palermo\".getBytes());\n      bexpected.add(\"Catania\".getBytes());\n      assertByteArrayListEquals(bexpected, cluster.zrange(\"{Sicily}Store\".getBytes(), 0, -1));\n  }\n\n  @Test\n  public void testKeys() {\n    assertEquals(0, cluster.keys(\"{f}o*\".getBytes()).size());\n    cluster.set(\"{f}oo1\".getBytes(), \"bar\".getBytes());\n    cluster.set(\"{f}oo2\".getBytes(), \"bar\".getBytes());\n    cluster.set(\"{f}oo3\".getBytes(), \"bar\".getBytes());\n    assertEquals(3, cluster.keys(\"{f}o*\".getBytes()).size());\n  }\n\n  @Test\n  public void testBinaryGeneralCommand() {\n    byte[] key = \"x\".getBytes();\n    byte[] value = \"1\".getBytes();\n    cluster.sendCommand(\"z\".getBytes(), SET, key, value);\n    cluster.sendCommand(\"y\".getBytes(), INCR, key);\n    Object returnObj = cluster.sendCommand(\"w\".getBytes(), GET, key);\n    assertEquals(\"2\", SafeEncoder.encode((byte[]) returnObj));\n  }\n\n  @Test\n  public void testGeneralCommand() {\n    cluster.sendCommand(\"z\", SET, \"x\", \"1\");\n    cluster.sendCommand(\"y\", INCR, \"x\");\n    Object returnObj = cluster.sendCommand(\"w\", GET, \"x\");\n    assertEquals(\"2\", SafeEncoder.encode((byte[]) returnObj));\n  }\n\n  @Test\n  public void testKeysBroadcastAndAggregation() {\n    // Use keys without hash tags - they will be distributed across different hash slots\n    // These keys intentionally don't use hash tags like {key} to ensure distribution\n    String key1 = \"testkey_alpha\";\n    String key2 = \"testkey_beta\";\n    String key3 = \"testkey_gamma\";\n\n    // Initially, no keys should match the pattern\n    assertEquals(0, cluster.keys(\"testkey_*\").size());\n\n    // Set multiple keys that will be distributed across different hash slots\n    cluster.set(key1, \"value1\");\n    cluster.set(key2, \"value2\");\n    cluster.set(key3, \"value3\");\n\n    // Call keys with a pattern that should match all three keys\n    // This triggers broadcasting to all shards and aggregation of results\n    Set<String> matchedKeys = cluster.keys(\"testkey_*\");\n\n    // Verify all keys are returned, demonstrating proper cross-shard aggregation\n    assertEquals(3, matchedKeys.size());\n    assertTrue(matchedKeys.contains(key1));\n    assertTrue(matchedKeys.contains(key2));\n    assertTrue(matchedKeys.contains(key3));\n\n    // Test with a more specific pattern that matches only some keys\n    Set<String> alphaKeys = cluster.keys(\"testkey_a*\");\n    assertEquals(1, alphaKeys.size());\n    assertTrue(alphaKeys.contains(key1));\n\n    // Test binary version with keys distributed across different slots\n    byte[] bkey1 = \"binkey_one\".getBytes();\n    byte[] bkey2 = \"binkey_two\".getBytes();\n    byte[] bkey3 = \"binkey_three\".getBytes();\n\n    cluster.set(bkey1, \"bvalue1\".getBytes());\n    cluster.set(bkey2, \"bvalue2\".getBytes());\n    cluster.set(bkey3, \"bvalue3\".getBytes());\n\n    // Verify binary KEYS also broadcasts and aggregates correctly\n    Set<byte[]> binaryMatchedKeys = cluster.keys(\"binkey_*\".getBytes());\n    assertEquals(3, binaryMatchedKeys.size());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\n\n\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.args.ClusterResetType;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.resps.ClusterShardInfo;\nimport redis.clients.jedis.resps.ClusterShardNodeInfo;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport redis.clients.jedis.util.JedisClusterTestUtil;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n\n@Tag(\"integration\")\npublic class ClusterCommandsTest {\n\n  private static EndpointConfig endpoint;\n\n  private static Jedis node1;\n  private static Jedis node2;\n\n  private static HostAndPort nodeInfo1;\n  private static HostAndPort nodeInfo2;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"cluster-unbound\"));\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-unbound\");\n    nodeInfo1 = endpoint.getHostsAndPorts().get(0);\n    nodeInfo2 = endpoint.getHostsAndPorts().get(1);\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    node1 = new Jedis(nodeInfo1);\n    node1.auth(endpoint.getPassword());\n    node1.flushAll();\n\n    node2 = new Jedis(nodeInfo2);\n    node2.auth(endpoint.getPassword());\n    node2.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    node1.disconnect();\n    node2.disconnect();\n  }\n\n  @AfterAll\n  public static void resetRedisAfter() {\n    if (endpoint != null) {\n      removeSlots();\n    }\n  }\n\n  public static void removeSlots() {\n    if (endpoint == null)\n      return;\n\n    try (Jedis node = new Jedis(nodeInfo1)) {\n      node.auth(endpoint.getPassword());\n      node.clusterReset(ClusterResetType.SOFT);\n    }\n    try (Jedis node = new Jedis(nodeInfo2)) {\n      node.auth(endpoint.getPassword());\n      node.clusterReset(ClusterResetType.SOFT);\n    }\n  }\n\n  @Test\n  public void testClusterSoftReset() {\n    node1.clusterMeet(nodeInfo2.getHost(), nodeInfo2.getPort());\n    assertTrue(node1.clusterNodes().split(\"\\n\").length > 1);\n    node1.clusterReset(ClusterResetType.SOFT);\n    assertEquals(1, node1.clusterNodes().split(\"\\n\").length);\n  }\n\n  @Test\n  public void testClusterHardReset() {\n    String nodeId = JedisClusterTestUtil.getNodeId(node1.clusterNodes());\n    node1.clusterReset(ClusterResetType.HARD);\n    String newNodeId = JedisClusterTestUtil.getNodeId(node1.clusterNodes());\n    assertNotEquals(nodeId, newNodeId);\n  }\n\n  @Test\n  public void clusterSetSlotImporting() {\n    node2.clusterAddSlots(6000);\n    String[] nodes = node1.clusterNodes().split(\"\\n\");\n    String nodeId = nodes[0].split(\" \")[0];\n    String status = node1.clusterSetSlotImporting(6000, nodeId);\n    assertEquals(\"OK\", status);\n    node2.clusterDelSlots(6000);\n  }\n\n  @Test\n  public void clusterNodes() {\n    String nodes = node1.clusterNodes();\n    assertTrue(nodes.split(\"\\n\").length > 0);\n  }\n//\n//  @Test\n//  public void clusterMeet() {\n//    String status = node1.clusterMeet(\"127.0.0.1\", nodeInfo2.getPort());\n//    assertEquals(\"OK\", status);\n//  }\n\n  @Test\n  public void clusterAddSlotsAndDelSlots() {\n    assertEquals(\"OK\", node1.clusterAddSlots(1, 2, 3, 4, 5));\n    assertEquals(\"OK\", node1.clusterDelSlots(1, 2, 3, 4, 5));\n  }\n\n  @Test\n  public void clusterInfo() {\n    String info = node1.clusterInfo();\n    assertNotNull(info);\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void addAndDelSlotsRange() {\n    // test add\n    assertEquals(\"OK\", node1.clusterAddSlotsRange(100, 105));\n    String clusterNodes = node1.clusterNodes();\n    assertTrue(clusterNodes.contains(\"connected 100-105\"));\n\n    assertEquals(\"OK\", node1.clusterAddSlotsRange(110, 120));\n    clusterNodes = node1.clusterNodes();\n    assertTrue(clusterNodes.contains(\"connected 100-105 110-120\"));\n\n    // test del\n    assertEquals(\"OK\", node1.clusterDelSlotsRange(100, 105));\n    clusterNodes = node1.clusterNodes();\n    assertTrue(clusterNodes.contains(\"connected 110-120\"));\n\n    assertEquals(\"OK\", node1.clusterDelSlotsRange(110, 120));\n  }\n\n  @Test\n  public void clusterGetKeysInSlot() {\n    node1.clusterAddSlots(500);\n    List<String> keys = node1.clusterGetKeysInSlot(500, 1);\n    assertEquals(0, keys.size());\n    node1.clusterDelSlots(500);\n  }\n\n  @Test\n  public void clusterGetKeysInSlotBinary() {\n    node1.clusterAddSlots(501);\n    List<byte[]> keys = node1.clusterGetKeysInSlotBinary(501, 1);\n    assertEquals(0, keys.size());\n    node1.clusterDelSlots(501);\n  }\n\n  @Test\n  public void clusterSetSlotNode() {\n    String[] nodes = node1.clusterNodes().split(\"\\n\");\n    String nodeId = nodes[0].split(\" \")[0];\n    String status = node1.clusterSetSlotNode(10000, nodeId);\n    assertEquals(\"OK\", status);\n  }\n\n  @Test\n  public void clusterSetSlotMigrating() {\n    node1.clusterAddSlots(5000);\n    String[] nodes = node1.clusterNodes().split(\"\\n\");\n    String nodeId = nodes[0].split(\" \")[0];\n    String status = node1.clusterSetSlotMigrating(5000, nodeId);\n    assertEquals(\"OK\", status);\n    node1.clusterDelSlots(5000);\n  }\n\n  @Test\n  public void clusterSlots() {\n    // please see cluster slot output format from below commit\n    // @see: https://github.com/antirez/redis/commit/e14829de3025ffb0d3294e5e5a1553afd9f10b60\n    assertEquals(\"OK\", node1.clusterAddSlots(3000, 3001, 3002));\n\n    List<Object> slots = node1.clusterSlots();\n    assertNotNull(slots);\n    assertTrue(slots.size() > 0);\n\n    for (Object slotInfoObj : slots) {\n      assertNotNull(slotInfoObj);\n      List<Object> slotInfo = (List<Object>) slotInfoObj;\n      assertTrue(slotInfo.size() >= 2);\n\n      assertInstanceOf(Long.class, slotInfo.get(0));\n      assertInstanceOf(Long.class, slotInfo.get(1));\n\n      if (slotInfo.size() > 2) {\n        // assigned slots\n        assertInstanceOf(List.class, slotInfo.get(2));\n      }\n    }\n    node1.clusterDelSlots(3000, 3001, 3002);\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void clusterShards() {\n    assertEquals(\"OK\", node1.clusterAddSlots(3100, 3101, 3102, 3105));\n\n    List<ClusterShardInfo> shards = node1.clusterShards();\n    assertNotNull(shards);\n    assertTrue(shards.size() > 0);\n\n    for (ClusterShardInfo shardInfo : shards) {\n      assertNotNull(shardInfo);\n\n      assertTrue(shardInfo.getSlots().size() > 1);\n      for (List<Long> slotRange : shardInfo.getSlots()) {\n        assertEquals(2, slotRange.size());\n      }\n\n      for (ClusterShardNodeInfo nodeInfo : shardInfo.getNodes()) {\n        assertNotNull(nodeInfo.getId());\n        assertNotNull(nodeInfo.getEndpoint());\n        assertNotNull(nodeInfo.getIp());\n        assertNull(nodeInfo.getHostname());\n        assertNotNull(nodeInfo.getPort());\n        assertNotNull(nodeInfo.getRole());\n        assertNotNull(nodeInfo.getReplicationOffset());\n        assertNotNull(nodeInfo.getHealth());\n      }\n    }\n    node1.clusterDelSlots(3100, 3101, 3102, 3105);\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void clusterLinks() throws InterruptedException {\n    List<Map<String, Object>> links = node1.clusterLinks();\n    assertNotNull(links);\n    assertEquals(0, links.size());\n  }\n\n  @Test\n  public void testClusterKeySlot() {\n    // It assumes JedisClusterCRC16 is correctly implemented\n    assertEquals(JedisClusterCRC16.getSlot(\"{user1000}.following\"),\n      node1.clusterKeySlot(\"{user1000}.following\"));\n    assertEquals(JedisClusterCRC16.getSlot(\"foo{bar}{zap}\"),\n        node1.clusterKeySlot(\"foo{bar}{zap}\"));\n    assertEquals(JedisClusterCRC16.getSlot(\"foo{}{bar}\"),\n        node1.clusterKeySlot(\"foo{}{bar}\"));\n    assertEquals(JedisClusterCRC16.getSlot(\"foo{{bar}}zap\"),\n        node1.clusterKeySlot(\"foo{{bar}}zap\"));\n  }\n\n  @Test\n  public void clusterCountFailureReports() {\n    assertEquals(0, node1.clusterCountFailureReports(node1.clusterMyId()));\n  }\n\n  @Test\n  public void clusterMyId() {\n    MatcherAssert.assertThat(node1.clusterMyId(), Matchers.not(Matchers.isEmptyOrNullString()));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.2.0\")\n  public void clusterMyShardId() {\n    MatcherAssert.assertThat(node1.clusterMyShardId(), Matchers.not(Matchers.isEmptyOrNullString()));\n  }\n\n  @Test\n  public void testClusterEpoch() {\n    try {\n      assertEquals(\"OK\", node1.clusterSetConfigEpoch(1));\n    } catch (JedisDataException jde) {\n      assertEquals(\"ERR The user can assign a config epoch only when the node does not know any other node.\", jde.getMessage());\n    }\n  }\n\n  @Test\n  public void ClusterBumpEpoch() {\n    MatcherAssert.assertThat(node1.clusterBumpEpoch(), Matchers.matchesPattern(\"^BUMPED|STILL [0-9]+$\"));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterHotkeysCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.args.HotkeysMetric;\nimport redis.clients.jedis.params.HotkeysParams;\nimport redis.clients.jedis.resps.HotkeysInfo;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * Tests that HOTKEYS commands are not supported in cluster mode.\n * <p>\n * The HOTKEYS command is a node-local operation that tracks hot keys on a single Redis instance. In\n * a Redis Cluster, keys are distributed across multiple nodes, and there is no built-in mechanism\n * to aggregate hotkeys data across all nodes. Therefore, HOTKEYS commands are intentionally\n * disabled in cluster mode to avoid confusion and incorrect results.\n * <p>\n * Users who need hotkeys functionality in a cluster environment should connect directly to\n * individual nodes and run HOTKEYS commands on each node separately.\n */\n@Tag(\"integration\")\n@EnabledOnCommand(\"HOTKEYS\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\npublic class ClusterHotkeysCommandsTest extends ClusterJedisCommandsTestBase {\n\n  @Test\n  public void hotkeysStartNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class,\n      () -> cluster.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU)));\n  }\n\n  @Test\n  public void hotkeysStopNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class, () -> cluster.hotkeysStop());\n  }\n\n  @Test\n  public void hotkeysResetNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class, () -> cluster.hotkeysReset());\n  }\n\n  @Test\n  public void hotkeysGetNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class, () -> cluster.hotkeysGet());\n  }\n\n  // Test slots - consecutive slots (server groups them as a range) and a non-consecutive slot\n  private static final int SLOT_0 = 0;\n  private static final int SLOT_1 = 1;\n  private static final int SLOT_2 = 2;\n  private static final int SLOT_100 = 100; // Non-consecutive slot to test multiple ranges\n\n  // Keys with hash tags that hash to slots 0, 1, 2\n  // The hash tag content was found by iterating JedisClusterCRC16.getSlot()\n  private static final String KEY_SLOT_0 = \"key{3560}\"; // {3560} hashes to slot 0\n  private static final String KEY_SLOT_1 = \"key{22179}\"; // {22179} hashes to slot 1\n  private static final String KEY_SLOT_2 = \"key{48756}\"; // {48756} hashes to slot 2\n\n  /**\n   * Tests HOTKEYS with SLOTS parameter by connecting directly to a single cluster node using a\n   * standalone RedisClient. Verifies all response fields are correctly parsed.\n   */\n  @Test\n  public void hotkeysWithSlotsOnSingleClusterNode() {\n    // Verify our pre-computed keys hash to the expected slots\n    assertEquals(SLOT_0, JedisClusterCRC16.getSlot(KEY_SLOT_0));\n    assertEquals(SLOT_1, JedisClusterCRC16.getSlot(KEY_SLOT_1));\n    assertEquals(SLOT_2, JedisClusterCRC16.getSlot(KEY_SLOT_2));\n\n    HostAndPort nodeHostAndPort = endpoint.getHostsAndPorts().get(0);\n\n    try (RedisClient client = RedisClient.builder().hostAndPort(nodeHostAndPort)\n        .clientConfig(endpoint.getClientConfigBuilder().build()).build()) {\n\n      // Clean up any previous state\n      client.hotkeysStop();\n      client.hotkeysReset();\n\n      // Start hotkeys tracking with consecutive slots (0, 1, 2) and a non-consecutive slot (100)\n      // Server should group consecutive slots into a range and keep non-consecutive as single slot\n      String result = client\n          .hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU, HotkeysMetric.NET)\n              .sample(2).slots(SLOT_0, SLOT_1, SLOT_2, SLOT_100));\n      assertEquals(\"OK\", result);\n\n      // Generate traffic on keys that hash to slots 0, 1, 2\n      String[] keys = { KEY_SLOT_0, KEY_SLOT_1, KEY_SLOT_2 };\n      for (int i = 0; i < 50; i++) {\n        for (String key : keys) {\n          client.set(key, \"value\" + i);\n          client.get(key);\n        }\n      }\n\n      HotkeysInfo info = client.hotkeysGet();\n      assertNotNull(info);\n\n      // Verify tracking state\n      assertTrue(info.isTrackingActive());\n      assertEquals(2, info.getSampleRatio());\n\n      // Verify selected slots - should have 2 entries:\n      // 1. Range [0, 2] for consecutive slots 0, 1, 2\n      // 2. Single slot [100] for non-consecutive slot\n      List<int[]> selectedSlots = info.getSelectedSlots();\n      assertNotNull(selectedSlots);\n      assertEquals(2, selectedSlots.size());\n      // First entry: range [0, 2]\n      assertEquals(2, selectedSlots.get(0).length);\n      assertEquals(SLOT_0, selectedSlots.get(0)[0]);\n      assertEquals(SLOT_2, selectedSlots.get(0)[1]);\n      // Second entry: single slot [100]\n      assertEquals(1, selectedSlots.get(1).length);\n      assertEquals(SLOT_100, selectedSlots.get(1)[0]);\n\n      // Verify slot-specific CPU metrics (only present when SLOTS is used)\n      assertNotNull(info.getSampledCommandSelectedSlotsUs());\n      assertThat(info.getSampledCommandSelectedSlotsUs(), greaterThan(0L));\n      assertNotNull(info.getAllCommandsSelectedSlotsUs());\n      assertThat(info.getAllCommandsSelectedSlotsUs(), greaterThan(0L));\n      assertThat(info.getAllCommandsAllSlotsUs(), greaterThan(0L));\n\n      // Verify slot-specific network bytes metrics\n      assertNotNull(info.getNetBytesSampledCommandsSelectedSlots());\n      assertThat(info.getNetBytesSampledCommandsSelectedSlots(), greaterThan(0L));\n      assertNotNull(info.getNetBytesAllCommandsSelectedSlots());\n      assertThat(info.getNetBytesAllCommandsSelectedSlots(), greaterThan(0L));\n      assertThat(info.getNetBytesAllCommandsAllSlots(), greaterThan(0L));\n\n      // Verify timing fields\n      assertThat(info.getCollectionStartTimeUnixMs(), greaterThan(0L));\n      assertThat(info.getCollectionDurationMs(), greaterThanOrEqualTo(0L));\n      assertThat(info.getTotalCpuTimeUserMs(), greaterThanOrEqualTo(0L));\n      assertThat(info.getTotalCpuTimeSysMs(), greaterThanOrEqualTo(0L));\n      assertThat(info.getTotalNetBytes(), greaterThan(0L));\n\n      // Verify key metrics maps contain our 3 keys with values > 0\n      Map<String, Long> byCpuTimeUs = info.getByCpuTimeUs();\n      assertNotNull(byCpuTimeUs);\n      assertEquals(3, byCpuTimeUs.size());\n      assertTrue(byCpuTimeUs.containsKey(KEY_SLOT_0));\n      assertTrue(byCpuTimeUs.containsKey(KEY_SLOT_1));\n      assertTrue(byCpuTimeUs.containsKey(KEY_SLOT_2));\n      assertThat(byCpuTimeUs.get(KEY_SLOT_0), greaterThan(0L));\n      assertThat(byCpuTimeUs.get(KEY_SLOT_1), greaterThan(0L));\n      assertThat(byCpuTimeUs.get(KEY_SLOT_2), greaterThan(0L));\n\n      Map<String, Long> byNetBytes = info.getByNetBytes();\n      assertNotNull(byNetBytes);\n      assertEquals(3, byNetBytes.size());\n      assertTrue(byNetBytes.containsKey(KEY_SLOT_0));\n      assertTrue(byNetBytes.containsKey(KEY_SLOT_1));\n      assertTrue(byNetBytes.containsKey(KEY_SLOT_2));\n      assertThat(byNetBytes.get(KEY_SLOT_0), greaterThan(0L));\n      assertThat(byNetBytes.get(KEY_SLOT_1), greaterThan(0L));\n      assertThat(byNetBytes.get(KEY_SLOT_2), greaterThan(0L));\n\n      client.hotkeysStop();\n      client.hotkeysReset();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterJedisCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport java.util.HashSet;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisClusterClient;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@Tag(\"integration\")\npublic abstract class ClusterJedisCommandsTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  protected RedisClusterClient cluster;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"cluster-stable\"));\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      () -> Endpoints.getRedisEndpoint(\"cluster-stable\"));\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-stable\");\n  }\n\n  @BeforeEach\n  public void setUp() {\n    cluster = RedisClusterClient.builder()\n        .nodes(new HashSet<>(endpoint.getHostsAndPorts()))\n        .clientConfig(endpoint.getClientConfigBuilder().build())\n        .build();\n    cluster.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (cluster != null) {\n      cluster.flushAll();\n      cluster.close();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterScriptingCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.exceptions.JedisBroadcastException;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n\n@Tag(\"integration\")\npublic class ClusterScriptingCommandsTest extends ClusterJedisCommandsTestBase {\n\n  @Test\n  public void testJedisClusterException() {\n    String script = \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2],ARGV[3]}\";\n    List<String> keys = new ArrayList<>();\n    keys.add(\"key1\");\n    keys.add(\"key2\");\n    List<String> args = new ArrayList<>();\n    args.add(\"first\");\n    args.add(\"second\");\n    args.add(\"third\");\n\n    assertThrows(JedisClusterOperationException.class, () -> {\n      cluster.eval(script, keys, args);\n    });\n  }\n\n  @Test\n  public void testEval2() {\n    String script = \"return redis.call('set',KEYS[1],'bar')\";\n    int numKeys = 1;\n    String[] args = { \"foo\" };\n    cluster.eval(script, numKeys, args);\n    assertEquals(\"bar\", cluster.get(\"foo\"));\n  }\n\n  @Test\n  public void testScriptLoadAndScriptExists() {\n    String sha1 = cluster.scriptLoad(\"return redis.call('get','foo')\", \"key1\");\n    assertTrue(cluster.scriptExists(sha1, \"key1\"));\n  }\n\n  @Test\n  public void testEvalsha() {\n    String sha1 = cluster.scriptLoad(\"return 10\", \"key1\");\n    Object o = cluster.evalsha(sha1, 1, \"key1\");\n    assertEquals(\"10\", o.toString());\n  }\n\n  @Test\n  public void testJedisClusterException2() {\n    byte[] script = \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2],ARGV[3]}\".getBytes();\n    List<byte[]> keys = new ArrayList<byte[]>();\n    keys.add(\"key1\".getBytes());\n    keys.add(\"key2\".getBytes());\n    List<byte[]> args = new ArrayList<byte[]>();\n    args.add(\"first\".getBytes());\n    args.add(\"second\".getBytes());\n    args.add(\"third\".getBytes());\n\n    assertThrows( JedisClusterOperationException.class, ()-> cluster.eval(script, keys, args));\n\n  }\n\n  @Test\n  public void testBinaryEval() {\n    byte[] script = \"return redis.call('set',KEYS[1],'bar')\".getBytes();\n    byte[] args = \"foo\".getBytes();\n    cluster.eval(script, 1, args);\n    assertEquals(\"bar\", cluster.get(\"foo\"));\n  }\n\n  @Test\n  public void testBinaryScriptFlush() {\n    byte[] byteKey = \"key1\".getBytes();\n    cluster.scriptLoad(\"return redis.call('get','foo')\".getBytes(), byteKey);\n    assertEquals(\"OK\", cluster.scriptFlush(byteKey));\n    assertEquals(\"OK\", cluster.scriptFlush(byteKey, FlushMode.SYNC));\n  }\n\n  @Test\n  public void testBinaryScriptKill() {\n    byte[] byteKey = \"key1\".getBytes();\n    assertThrows(JedisDataException.class, ()-> cluster.scriptKill(byteKey));\n  }\n\n  @Test\n  public void testBinaryScriptExists() {\n    byte[] byteKey = \"key1\".getBytes();\n    byte[] sha1 = cluster.scriptLoad(\"return redis.call('get','foo')\".getBytes(), byteKey);\n    byte[][] arraySha1 = { sha1 };\n    assertEquals(Collections.singletonList(Boolean.TRUE), cluster.scriptExists(byteKey, arraySha1));\n  }\n\n  @Test\n  public void broadcast() {\n\n    String script_1 = \"return 'jedis'\";\n    String sha1_1 = cluster.scriptLoad(script_1);\n\n    String script_2 = \"return 79\";\n    String sha1_2 = cluster.scriptLoad(script_2);\n\n    assertEquals(Arrays.asList(true, true), cluster.scriptExists(Arrays.asList(sha1_1, sha1_2)));\n\n    cluster.scriptFlush();\n\n    assertEquals(Arrays.asList(false, false), cluster.scriptExists(Arrays.asList(sha1_1, sha1_2)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void broadcastWithError() {\n\n    JedisBroadcastException error = assertThrows(JedisBroadcastException.class, () -> cluster.functionDelete(\"xyz\"));\n\n    Map<HostAndPort, Object> replies = error.getReplies();\n    assertEquals(3, replies.size());\n    replies.values().forEach(r -> {\n      assertSame(JedisDataException.class, r.getClass());\n      assertEquals(\"ERR Library not found\", ((JedisDataException) r).getMessage());\n    });\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterShardedPublishSubscribeCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.BinaryJedisShardedPubSub;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisShardedPubSub;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport redis.clients.jedis.util.SafeEncoder;\n\n@SinceRedisVersion(value = \"7.0.0\", message = \"Sharded Pub/Sub\")\n@Tag(\"integration\")\npublic class ClusterShardedPublishSubscribeCommandsTest extends ClusterJedisCommandsTestBase {\n\n  private void publishOne(final String channel, final String message) {\n    Thread t = new Thread(() -> cluster.spublish(channel, message));\n    t.start();\n  }\n\n  @Test\n  public void subscribe() throws InterruptedException {\n    cluster.ssubscribe(new JedisShardedPubSub() {\n      @Override public void onSMessage(String channel, String message) {\n        assertEquals(\"foo\", channel);\n        assertEquals(\"exit\", message);\n        sunsubscribe();\n      }\n\n      @Override public void onSSubscribe(String channel, int subscribedChannels) {\n        assertEquals(\"foo\", channel);\n        assertEquals(1, subscribedChannels);\n\n        // now that I'm subscribed... publish\n        publishOne(\"foo\", \"exit\");\n      }\n\n      @Override public void onSUnsubscribe(String channel, int subscribedChannels) {\n        assertEquals(\"foo\", channel);\n        assertEquals(0, subscribedChannels);\n      }\n    }, \"foo\");\n  }\n\n  @Test\n  public void subscribeMany() {\n    cluster.ssubscribe(new JedisShardedPubSub() {\n      @Override public void onSMessage(String channel, String message) {\n        sunsubscribe(channel);\n      }\n\n      @Override public void onSSubscribe(String channel, int subscribedChannels) {\n        publishOne(channel, \"exit\");\n      }\n\n    }, \"{foo}\", \"{foo}bar\");\n  }\n\n  @Test\n  public void pubSubChannels() {\n    cluster.ssubscribe(new JedisShardedPubSub() {\n      private int count = 0;\n\n      @Override public void onSSubscribe(String channel, int subscribedChannels) {\n        count++;\n        // All channels are subscribed\n        if (count == 3) {\n          try (Connection conn = cluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(\"testchan\"));\n              Jedis jedis = new Jedis(conn)) {\n            assertThat(jedis.pubsubShardChannels(),\n                hasItems(\"{testchan}1\", \"{testchan}2\", \"{testchan}3\"));\n          }\n          sunsubscribe();\n        }\n      }\n    }, \"{testchan}1\", \"{testchan}2\", \"{testchan}3\");\n  }\n\n  @Test\n  public void pubSubChannelsWithPattern() {\n    cluster.ssubscribe(new JedisShardedPubSub() {\n      private int count = 0;\n\n      @Override public void onSSubscribe(String channel, int subscribedChannels) {\n        count++;\n        // All channels are subscribed\n        if (count == 3) {\n          try (Connection conn = cluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(\"testchan\"));\n              Jedis otherJedis = new Jedis(conn)) {\n            assertThat(otherJedis.pubsubShardChannels(\"*testchan*\"),\n                hasItems(\"{testchan}1\", \"{testchan}2\", \"{testchan}3\"));\n          }\n          sunsubscribe();\n        }\n      }\n    }, \"{testchan}1\", \"{testchan}2\", \"{testchan}3\");\n  }\n\n  @Test\n  public void pubSubNumSub() {\n    final Map<String, Long> expectedNumSub = new HashMap<>();\n    expectedNumSub.put(\"{testchannel}1\", 1L);\n    expectedNumSub.put(\"{testchannel}2\", 1L);\n\n    cluster.ssubscribe(new JedisShardedPubSub() {\n      private int count = 0;\n\n      @Override public void onSSubscribe(String channel, int subscribedChannels) {\n        count++;\n        if (count == 2) {\n          try (Connection conn = cluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(\"testchannel\"));\n              Jedis otherJedis = new Jedis(conn)) {\n            Map<String, Long> numSub = otherJedis.pubsubShardNumSub(\"{testchannel}1\", \"{testchannel}2\");\n            assertEquals(expectedNumSub, numSub);\n          }\n          sunsubscribe();\n        }\n      }\n    }, \"{testchannel}1\", \"{testchannel}2\");\n  }\n\n  @Test\n  public void binarySubscribe() {\n    cluster.ssubscribe(new BinaryJedisShardedPubSub() {\n      @Override public void onSMessage(byte[] channel, byte[] message) {\n        assertArrayEquals(SafeEncoder.encode(\"foo\"), channel);\n        assertArrayEquals(SafeEncoder.encode(\"exit\"), message);\n        sunsubscribe();\n      }\n\n      @Override public void onSSubscribe(byte[] channel, int subscribedChannels) {\n        assertArrayEquals(SafeEncoder.encode(\"foo\"), channel);\n        assertEquals(1, subscribedChannels);\n        publishOne(SafeEncoder.encode(channel), \"exit\");\n      }\n\n      @Override public void onSUnsubscribe(byte[] channel, int subscribedChannels) {\n        assertArrayEquals(SafeEncoder.encode(\"foo\"), channel);\n        assertEquals(0, subscribedChannels);\n      }\n    }, SafeEncoder.encode(\"foo\"));\n  }\n\n  @Test\n  public void binarySubscribeMany() {\n    cluster.ssubscribe(new BinaryJedisShardedPubSub() {\n      @Override public void onSMessage(byte[] channel, byte[] message) {\n        sunsubscribe(channel);\n      }\n\n      @Override public void onSSubscribe(byte[] channel, int subscribedChannels) {\n        publishOne(SafeEncoder.encode(channel), \"exit\");\n      }\n    }, SafeEncoder.encode(\"{foo}\"), SafeEncoder.encode(\"{foo}bar\"));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ClusterValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.ScanIteration;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\npublic class ClusterValuesCommandsTest extends ClusterJedisCommandsTestBase {\n\n  @Test\n  public void nullKeys() {\n    byte[] bfoo = new byte[]{0x0b, 0x0f, 0x00, 0x00};\n\n    try {\n      cluster.exists((byte[]) null);\n      fail();\n    } catch (NullPointerException e) {\n      // expected\n    }\n\n    try {\n      cluster.exists(bfoo, null);\n      fail();\n    } catch (NullPointerException e) {\n      // expected\n    }\n\n    try {\n      cluster.exists(null, bfoo);\n      fail();\n    } catch (NullPointerException e) {\n      // expected\n    }\n  }\n\n  @Test\n  public void testHincrByFloat() {\n    Double value = cluster.hincrByFloat(\"foo\", \"bar\", 1.5d);\n    assertEquals((Double) 1.5d, value);\n    value = cluster.hincrByFloat(\"foo\", \"bar\", -1.5d);\n    assertEquals((Double) 0d, value);\n    value = cluster.hincrByFloat(\"foo\", \"bar\", -10.7d);\n    assertEquals(Double.valueOf(-10.7d), value);\n  }\n\n  @Test\n  public void georadiusStore() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<String, GeoCoordinate>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    cluster.geoadd(\"{Sicily}\", coordinateMap);\n\n    long size = cluster.georadiusStore(\"{Sicily}\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"{Sicily}Store\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"Palermo\");\n    expected.add(\"Catania\");\n    assertEquals(expected, cluster.zrange(\"{Sicily}Store\", 0, -1));\n  }\n\n  private void publishOne(final String channel, final String message) {\n    Thread t = new Thread(new Runnable() {\n      public void run() {\n        try {\n          cluster.publish(channel, message);\n        } catch (Exception ex) {\n        }\n      }\n    });\n    t.start();\n  }\n\n  @Test\n  public void subscribe() throws InterruptedException {\n    cluster.subscribe(new JedisPubSub() {\n      public void onMessage(String channel, String message) {\n        assertEquals(\"foo\", channel);\n        assertEquals(\"exit\", message);\n        unsubscribe();\n      }\n\n      public void onSubscribe(String channel, int subscribedChannels) {\n        assertEquals(\"foo\", channel);\n        assertEquals(1, subscribedChannels);\n\n        // now that I'm subscribed... publish\n        publishOne(\"foo\", \"exit\");\n      }\n\n      public void onUnsubscribe(String channel, int subscribedChannels) {\n        assertEquals(\"foo\", channel);\n        assertEquals(0, subscribedChannels);\n      }\n    }, \"foo\");\n  }\n\n  @Test\n  public void rawPingBroadcast() {\n    String reply = cluster.broadcastCommand(\n        new CommandObject<>(new CommandArguments(Protocol.Command.PING), BuilderFactory.STRING));\n    assertEquals(\"PONG\", reply);\n  }\n\n  @Test\n  public void pingBroadcast() {\n    assertEquals(\"PONG\", cluster.ping());\n  }\n\n  @Test\n  public void info() {\n    String info = cluster.info();\n    assertThat(info, notNullValue());\n\n    info = cluster.info(\"server\");\n    assertThat(info, notNullValue());\n  }\n\n  @Test\n  public void flushAllBroadcast() {\n    assertNull(cluster.get(\"foo\"));\n    assertEquals(\"OK\", cluster.set(\"foo\", \"bar\"));\n    assertEquals(\"bar\", cluster.get(\"foo\"));\n    cluster.flushAll();\n    assertNull(cluster.get(\"foo\"));\n  }\n\n  @Test\n  public void scanIteration() {\n    Set<String> allIn = new HashSet<>(26 * 26);\n    char[] arr = new char[2];\n    for (int i = 0; i < 26; i++) {\n      arr[0] = (char) ('a' + i);\n      for (int j = 0; j < 26; j++) {\n        arr[1] = (char) ('a' + j);\n        String str = new String(arr);\n        cluster.incr(str);\n        allIn.add(str);\n      }\n    }\n\n    Set<String> allScan = new HashSet<>();\n    ScanIteration scan = cluster.scanIteration(10, \"*\");\n    while (!scan.isIterationCompleted()) {\n      ScanResult<String> batch = scan.nextBatch();\n      allScan.addAll(batch.getResult());\n    }\n    assertEquals(allIn, allScan);\n\n    Set<String> allTypeScan = new HashSet<>();\n    ScanIteration typeScan = cluster.scanIteration(10, \"*\", \"string\");\n    while (!typeScan.isIterationCompleted()) {\n      ScanResult<String> batch = typeScan.nextBatch();\n      allTypeScan.addAll(batch.getResult());\n    }\n    assertEquals(allIn, allTypeScan);\n  }\n\n  @Test\n  public void scanIterationCollect() {\n    Set<String> allIn = new HashSet<>(26 * 26);\n    char[] arr = new char[2];\n    for (int i = 0; i < 26; i++) {\n      arr[0] = (char) ('a' + i);\n      for (int j = 0; j < 26; j++) {\n        arr[1] = (char) ('a' + j);\n        String str = new String(arr);\n        cluster.incr(str);\n        allIn.add(str);\n      }\n    }\n\n    assertEquals(allIn, cluster.scanIteration(100, \"*\").collect(new HashSet<>(26 * 26)));\n  }\n\n  @Test\n  public void dbSizeAggregation() {\n    // Set some keys across the cluster (different hash slots)\n    cluster.set(\"key1\", \"value1\");\n    cluster.set(\"key2\", \"value2\");\n    cluster.set(\"key3\", \"value3\");\n\n    // dbSize should return sum of keys across all shards\n    long dbSize = cluster.dbSize();\n    assertTrue(dbSize >= 3);\n  }\n\n  @Test\n  public void msetCrossShard() {\n    // MSET with keys on different shards (MULTI_SHARD policy)\n    // Using keys without hash tags to distribute across shards\n    assertEquals(\"OK\", cluster.mset(\"mset_key_a\", \"value_a\", \"mset_key_b\", \"value_b\", \"mset_key_c\", \"value_c\"));\n\n    // Verify all keys were set\n    assertEquals(\"value_a\", cluster.get(\"mset_key_a\"));\n    assertEquals(\"value_b\", cluster.get(\"mset_key_b\"));\n    assertEquals(\"value_c\", cluster.get(\"mset_key_c\"));\n  }\n\n  @Test\n  public void scriptExistsAggregation() {\n    String script = \"return 1\";\n    String sampleKey = \"testKey\";\n\n    // Load a script to get its SHA1\n    String sha1 = cluster.scriptLoad(script, sampleKey);\n\n    // Verify it exists (single SHA1 check - returns Boolean, aggregated via AGG_LOGICAL_AND)\n    assertTrue(cluster.scriptExists(sha1, sampleKey));\n\n    // Test with multiple SHA1s - one exists, one doesn't\n    String unknownSha1 = \"0000000000000000000000000000000000000000\";\n    List<Boolean> results = cluster.scriptExists(sampleKey, sha1, unknownSha1);\n    assertEquals(2, results.size());\n    assertTrue(results.get(0));  // Known script exists\n    assertFalse(results.get(1)); // Unknown script doesn't exist\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"8.2.0\", message = \"CLUSTER SLOT-STATS requires Redis 8.2 or later\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clusterSlotStatsAggregation() {\n    // Set some keys across the cluster to ensure slots have data\n    cluster.set(\"key1\", \"value1\");\n    cluster.set(\"key2\", \"value2\");\n    cluster.set(\"key3\", \"value3\");\n\n    // Use broadcastCommand to send to all shards and aggregate with DEFAULT policy\n    // CLUSTER SLOT-STATS SLOTSRANGE returns a list (array) of slot statistics from each shard\n    // Each element is [slot_number, {key-count: N, cpu-usec: N, ...}]\n    // The DEFAULT response policy should concatenate lists from all nodes\n    List<Object> result = cluster.broadcastCommand(\n        new CommandObject<>(\n            new CommandArguments(Protocol.Command.CLUSTER).add(\"SLOT-STATS\").add(\"SLOTSRANGE\").add(0).add(16383),\n            BuilderFactory.RAW_OBJECT_LIST));\n\n    // Verify we got aggregated results from multiple shards\n    assertThat(result, notNullValue());\n\n    // The result should be a concatenated list containing slot statistics from all shards\n    // In a 3-shard cluster, each shard returns stats only for slots it owns\n    // so the merged list should contain entries from all 16384 slots\n    assertFalse(result.isEmpty(), \"Should have aggregated slot statistics from cluster nodes\");\n\n    // Verify the aggregated list contains entries from all shards\n    assertEquals(16384, result.size(), \"Aggregated list should contain slot statistics from multiple shards\");\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.2.0\", message = \"WAITAOF requires Redis 7.2 or later\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void waitAOFAggregation() {\n    // Set some keys across the cluster to ensure there's data to sync\n    cluster.set(\"key1\", \"value1\");\n    cluster.set(\"key2\", \"value2\");\n    cluster.set(\"key3\", \"value3\");\n\n    // Use broadcastCommand to send WAITAOF to all shards and aggregate with AGG_MIN policy\n    // WAITAOF returns a KeyValue<Long, Long> where:\n    //   - key = number of local AOF syncs\n    //   - value = number of replica AOF syncs\n    // The AGG_MIN response policy should return the minimum values across all shards\n    KeyValue<Long, Long> result = cluster.broadcastCommand(\n        new CommandObject<>(\n            new CommandArguments(Protocol.Command.WAITAOF).add(0).add(0).add(100),\n            BuilderFactory.LONG_LONG_PAIR));\n\n    // Verify we got aggregated results\n    assertThat(result, notNullValue());\n\n    // With numLocal=0 and numReplicas=0, the command should return immediately\n    // The minimum across all shards should be >= 0 for both values\n    assertTrue(result.getKey() >= 0, \"Local AOF sync count should be >= 0\");\n    assertTrue(result.getValue() >= 0, \"Replica AOF sync count should be >= 0\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ControlCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.args.ClientPauseMode;\nimport redis.clients.jedis.args.LatencyEvent;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.CommandListFilterByParams;\nimport redis.clients.jedis.params.LolwutParams;\nimport redis.clients.jedis.resps.CommandDocument;\nimport redis.clients.jedis.resps.CommandInfo;\nimport redis.clients.jedis.resps.LatencyHistoryInfo;\nimport redis.clients.jedis.resps.LatencyLatestInfo;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ControlCommandsTest extends JedisCommandsTestBase {\n\n  public ControlCommandsTest(RedisProtocol redisProtocol) {\n    super(redisProtocol);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void save() {\n    try {\n      String status = jedis.save();\n      assertEquals(\"OK\", status);\n    } catch (JedisDataException e) {\n      assertTrue(\"ERR Background save already in progress\".equalsIgnoreCase(e.getMessage()));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bgsave() {\n    try {\n      String status = jedis.bgsave();\n      assertEquals(\"Background saving started\", status);\n    } catch (JedisDataException e) {\n      assertTrue(\"ERR Background save already in progress\".equalsIgnoreCase(e.getMessage()));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bgsaveSchedule() {\n    Set<String> responses = new HashSet<>();\n    responses.add(\"OK\");\n    responses.add(\"Background saving scheduled\");\n    responses.add(\"Background saving started\");\n\n    String status = jedis.bgsaveSchedule();\n    assertTrue(responses.contains(status));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bgrewriteaof() {\n    String scheduled = \"Background append only file rewriting scheduled\";\n    String started = \"Background append only file rewriting started\";\n\n    String status = jedis.bgrewriteaof();\n\n    boolean ok = status.equals(scheduled) || status.equals(started);\n    assertTrue(ok);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lastsave() throws InterruptedException {\n    long saved = jedis.lastsave();\n    assertTrue(saved > 0);\n  }\n\n  @Test\n  public void info() {\n    String info = jedis.info();\n    assertNotNull(info);\n    info = jedis.info(\"server\");\n    assertNotNull(info);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void readonly() {\n    try {\n      jedis.readonly();\n    } catch (JedisDataException e) {\n      assertTrue(\"ERR This instance has cluster support disabled\".equalsIgnoreCase(e.getMessage()));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void readwrite() {\n    try {\n      jedis.readwrite();\n    } catch (JedisDataException e) {\n      assertTrue(\"ERR This instance has cluster support disabled\".equalsIgnoreCase(e.getMessage()));\n    }\n  }\n\n  @Test\n  public void roleMaster() {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n\n    try (Jedis master = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().build())) {\n\n      List<Object> role = master.role();\n      assertEquals(\"master\", role.get(0));\n      assertInstanceOf(Long.class, role.get(1));\n      assertInstanceOf(List.class, role.get(2));\n\n      // binary\n      List<Object> brole = master.roleBinary();\n      assertArrayEquals(\"master\".getBytes(), (byte[]) brole.get(0));\n      assertInstanceOf(Long.class, brole.get(1));\n      assertInstanceOf(List.class, brole.get(2));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void roleSlave() {\n    EndpointConfig primaryEndpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n    EndpointConfig secondaryEndpoint = Endpoints.getRedisEndpoint(\n        \"standalone4-replica-of-standalone1\");\n\n    try (Jedis slave = new Jedis(secondaryEndpoint.getHostAndPort(),\n        secondaryEndpoint.getClientConfigBuilder().build())) {\n\n      List<Object> role = slave.role();\n      assertEquals(\"slave\", role.get(0));\n      assertEquals((long) primaryEndpoint.getPort(), role.get(2));\n      assertEquals(\"connected\", role.get(3));\n      assertInstanceOf(Long.class, role.get(4));\n\n      // binary\n      List<Object> brole = slave.roleBinary();\n      assertArrayEquals(\"slave\".getBytes(), (byte[]) brole.get(0));\n      assertEquals((long) primaryEndpoint.getPort(), brole.get(2));\n      assertArrayEquals(\"connected\".getBytes(), (byte[]) brole.get(3));\n      assertInstanceOf(Long.class, brole.get(4));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void roleSentinel() {\n    try (Jedis sentinel = new Jedis(Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort())) {\n\n      List<Object> role = sentinel.role();\n      assertEquals(\"sentinel\", role.get(0));\n      assertInstanceOf(List.class, role.get(1));\n      AssertUtil.assertCollectionContains((List) role.get(1), \"mymaster\");\n\n      // binary\n      List<Object> brole = sentinel.roleBinary();\n      assertArrayEquals(\"sentinel\".getBytes(), (byte[]) brole.get(0));\n      assertInstanceOf(List.class, brole.get(1));\n      AssertUtil.assertByteArrayCollectionContains((List) brole.get(1), \"mymaster\".getBytes());\n    }\n  }\n\n  @Test\n  public void monitor() {\n    new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          // sleep 100ms to make sure that monitor thread runs first\n          Thread.sleep(100);\n        } catch (InterruptedException e) {\n        }\n        try (Jedis j = new Jedis(endpoint.getHostAndPort())) {\n          j.auth(endpoint.getPassword());\n          for (int i = 0; i < 5; i++) {\n            j.incr(\"foobared\");\n          }\n          j.disconnect();\n        }\n      }\n    }).start();\n\n    jedis.monitor(new JedisMonitor() {\n      private int count = 0;\n\n      @Override\n      public void onCommand(String command) {\n        if (command.contains(\"INCR\")) {\n          count++;\n        }\n        if (count == 5) {\n          client.disconnect();\n        }\n      }\n    });\n  }\n\n  @Test\n  public void configGet() {\n    Map<String, String> info = jedis.configGet(\"s*\"); // slowlog-max-len\n    assertNotNull(info);\n    assertFalse(info.isEmpty());\n//    assertTrue(info.size() % 2 == 0);\n    Map<byte[], byte[]> infoBinary = jedis.configGet(\"s*\".getBytes());\n    assertNotNull(infoBinary);\n    assertFalse(infoBinary.isEmpty());\n//    assertTrue(infoBinary.size() % 2 == 0);\n  }\n\n  @Test\n  public void configSet() {\n    Map<String, String> info = jedis.configGet(\"slowlog-max-len\");\n    String val = info.get(\"slowlog-max-len\");\n    assertNotNull(val);\n    assertEquals(\"OK\", jedis.configSet(\"slowlog-max-len\", \"200\"));\n    assertEquals(\"OK\", jedis.configSet(\"slowlog-max-len\", val));\n  }\n\n  @Test\n  public void configSetBinary() {\n    byte[] slowloglen = SafeEncoder.encode(\"slowlog-max-len\");\n    Map<byte[], byte[]> info = jedis.configGet(slowloglen);\n    byte[] memory = info.get(slowloglen);\n    assertNotNull(memory);\n    assertEquals(\"OK\", jedis.configSet(slowloglen, Protocol.toByteArray(200)));\n    assertEquals(\"OK\", jedis.configSet(slowloglen, memory));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Starting with Redis version 7.0.0: Added the ability to pass multiple pattern parameters in one call\")\n  public void configGetSetMulti() {\n    String[] params = new String[]{\"hash-max-listpack-entries\", \"set-max-intset-entries\", \"zset-max-listpack-entries\"};\n    Map<String, String> info = jedis.configGet(params);\n    assertEquals(3, info.size());\n    assertEquals(\"OK\", jedis.configSet(info));\n\n    byte[][] bparams = new byte[][]{SafeEncoder.encode(\"hash-max-listpack-entries\"),\n      SafeEncoder.encode(\"set-max-intset-entries\"), SafeEncoder.encode(\"zset-max-listpack-entries\")};\n    Map<byte[], byte[]> binfo = jedis.configGet(bparams);\n    assertEquals(3, binfo.size());\n    assertEquals(\"OK\", jedis.configSetBinary(binfo));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\n  public void waitReplicas() {\n    assertEquals(1, jedis.waitReplicas(1, 100));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.2.0\")\n  public void waitAof() {\n    assertEquals(KeyValue.of(0L, 0L), jedis.waitAOF(0L, 0L, 100L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clientPause() throws InterruptedException, ExecutionException {\n    ExecutorService executorService = Executors.newFixedThreadPool(2);\n    try (Jedis jedisToPause1 = createJedis(); Jedis jedisToPause2 = createJedis();) {\n\n      jedis.clientPause(1000L);\n\n      Future<Long> latency1 = executorService.submit(new Callable<Long>() {\n        @Override\n        public Long call() throws Exception {\n          long startMillis = System.currentTimeMillis();\n          assertEquals(\"PONG\", jedisToPause1.ping());\n          return System.currentTimeMillis() - startMillis;\n        }\n      });\n      Future<Long> latency2 = executorService.submit(new Callable<Long>() {\n        @Override\n        public Long call() throws Exception {\n          long startMillis = System.currentTimeMillis();\n          assertEquals(\"PONG\", jedisToPause2.ping());\n          return System.currentTimeMillis() - startMillis;\n        }\n      });\n\n      assertThat(latency1.get(), greaterThan(100L));\n      assertThat(latency2.get(), greaterThan(100L));\n\n    } finally {\n      executorService.shutdown();\n      if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {\n        executorService.shutdownNow();\n      }\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clientPauseAll() throws InterruptedException, ExecutionException {\n    ExecutorService executorService = Executors.newFixedThreadPool(1);\n    try (Jedis jedisPause = createJedis()) {\n\n      jedis.clientPause(1000L, ClientPauseMode.ALL);\n\n      Future<Long> latency = executorService.submit(new Callable<Long>() {\n        @Override\n        public Long call() throws Exception {\n          long startMillis = System.currentTimeMillis();\n          jedisPause.get(\"key\");\n          return System.currentTimeMillis() - startMillis;\n        }\n      });\n\n      assertThat(latency.get(), greaterThan(100L));\n\n    } finally {\n      executorService.shutdown();\n      if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {\n        executorService.shutdownNow();\n      }\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clientPauseWrite() throws InterruptedException, ExecutionException {\n    ExecutorService executorService = Executors.newFixedThreadPool(2);\n    try (Jedis jedisRead = createJedis(); Jedis jedisWrite = createJedis();) {\n\n      jedis.clientPause(1000L, ClientPauseMode.WRITE);\n\n      Future<Long> latencyRead = executorService.submit(new Callable<Long>() {\n        @Override\n        public Long call() throws Exception {\n          long startMillis = System.currentTimeMillis();\n          jedisRead.get(\"key\");\n          return System.currentTimeMillis() - startMillis;\n        }\n      });\n      Future<Long> latencyWrite = executorService.submit(new Callable<Long>() {\n        @Override\n        public Long call() throws Exception {\n          long startMillis = System.currentTimeMillis();\n          jedisWrite.set(\"key\", \"value\");\n          return System.currentTimeMillis() - startMillis;\n        }\n      });\n\n      assertThat(latencyRead.get(), Matchers.lessThan(100L));\n\n      assertThat(latencyWrite.get(), Matchers.greaterThan(100L));\n\n    } finally {\n      executorService.shutdown();\n      if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {\n        executorService.shutdownNow();\n      }\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clientUnpause() {\n    assertEquals(\"OK\", jedis.clientUnpause());\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void clientNoEvict() {\n    assertEquals(\"OK\", jedis.clientNoEvictOn());\n    assertEquals(\"OK\", jedis.clientNoEvictOff());\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.2.0\")\n  public void clientNoTouch() {\n    assertEquals(\"OK\", jedis.clientNoTouchOn());\n    assertEquals(\"OK\", jedis.clientNoTouchOff());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void memoryDoctorString() {\n    String memoryInfo = jedis.memoryDoctor();\n    assertNotNull(memoryInfo);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void memoryDoctorBinary() {\n    byte[] memoryInfo = jedis.memoryDoctorBinary();\n    assertNotNull(memoryInfo);\n  }\n\n  @Test\n  public void memoryUsageString() {\n    // Note: It has been recommended not to base MEMORY USAGE test on exact value, as the response\n    // may subject to be 'tuned' especially targeting a major Redis release.\n\n    jedis.set(\"foo\", \"bar\");\n    assertThat(jedis.memoryUsage(\"foo\"), greaterThan(20l));\n\n    jedis.lpush(\"foobar\", \"fo\", \"ba\", \"sha\");\n    assertThat(jedis.memoryUsage(\"foobar\", 2), greaterThan(36l));\n\n    assertNull(jedis.memoryUsage(\"roo\", 2));\n  }\n\n  @Test\n  public void memoryUsageBinary() {\n    // Note: It has been recommended not to base MEMORY USAGE test on exact value, as the response\n    // may subject to be 'tuned' especially targeting a major Redis release.\n\n    byte[] bfoo = {0x01, 0x02, 0x03, 0x04};\n    byte[] bbar = {0x05, 0x06, 0x07, 0x08};\n    byte[] bfoobar = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};\n\n    jedis.set(bfoo, bbar);\n    assertThat(jedis.memoryUsage(bfoo), greaterThan(20l));\n\n    jedis.lpush(bfoobar, new byte[]{0x01, 0x02}, new byte[]{0x05, 0x06}, new byte[]{0x00});\n    assertThat(jedis.memoryUsage(bfoobar, 2), greaterThanOrEqualTo(40l));\n\n    assertNull(jedis.memoryUsage(\"roo\", 2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void memoryPurge() {\n     String memoryPurge = jedis.memoryPurge();\n     assertNotNull(memoryPurge);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void memoryStats() {\n    Map<String, Object> stats = jedis.memoryStats();\n    assertNotNull(stats);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void latencyDoctor() {\n    String report = jedis.latencyDoctor();\n    assertNotNull(report);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void latencyLatest() {\n    Map<String, LatencyLatestInfo> report = jedis.latencyLatest();\n    assertNotNull(report);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void latencyHistoryFork() {\n    List<LatencyHistoryInfo> report = jedis.latencyHistory(LatencyEvent.FORK);\n    assertNotNull(report);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void latencyReset() {\n    assertTrue(jedis.latencyReset() >= 0);\n  }\n\n  @Test\n  public void commandCount() {\n    assertTrue(jedis.commandCount() > 100);\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void commandDocs() {\n    Map<String, CommandDocument> docs = jedis.commandDocs(\"SORT\", \"SET\");\n\n    CommandDocument sortDoc = docs.get(\"sort\");\n    assertEquals(\"generic\", sortDoc.getGroup());\n    MatcherAssert.assertThat(sortDoc.getSummary(), Matchers.isOneOf(\n        \"Sort the elements in a list, set or sorted set\",\n        \"Sorts the elements in a list, a set, or a sorted set, optionally storing the result.\"));\n    assertNull(sortDoc.getHistory());\n\n    CommandDocument setDoc = docs.get(\"set\");\n    assertEquals(\"1.0.0\", setDoc.getSince());\n    assertEquals(\"O(1)\", setDoc.getComplexity());\n    assertEquals(\"2.6.12: Added the `EX`, `PX`, `NX` and `XX` options.\", setDoc.getHistory().get(0));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void commandGetKeys() {\n    List<String> keys = jedis.commandGetKeys(\"SORT\", \"mylist\", \"ALPHA\", \"STORE\", \"outlist\");\n    assertEquals(2, keys.size());\n\n    List<KeyValue<String, List<String>>> keySandFlags = jedis.commandGetKeysAndFlags(\"SET\", \"k1\", \"v1\");\n    assertEquals(\"k1\", keySandFlags.get(0).getKey());\n    assertEquals(2, keySandFlags.get(0).getValue().size());\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void commandNoArgs() {\n    Map<String, CommandInfo> infos = jedis.command();\n\n    assertThat(infos.size(), greaterThan(0));\n\n    CommandInfo getInfo = infos.get(\"get\");\n    assertEquals(2, getInfo.getArity());\n    assertEquals(2, getInfo.getFlags().size());\n    assertEquals(1, getInfo.getFirstKey());\n    assertEquals(1, getInfo.getLastKey());\n    assertEquals(1, getInfo.getStep());\n\n    assertNull(infos.get(\"foo\")); // non-existing command\n\n    CommandInfo setInfo = infos.get(\"set\");\n    assertEquals(3, setInfo.getAclCategories().size());\n    assertEquals(0, setInfo.getTips().size());\n    assertEquals(0, setInfo.getSubcommands().size());\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void commandInfo() {\n    Map<String, CommandInfo> infos = jedis.commandInfo(\"GET\", \"foo\", \"SET\");\n\n    CommandInfo getInfo = infos.get(\"get\");\n    assertEquals(2, getInfo.getArity());\n    assertEquals(2, getInfo.getFlags().size());\n    assertEquals(1, getInfo.getFirstKey());\n    assertEquals(1, getInfo.getLastKey());\n    assertEquals(1, getInfo.getStep());\n\n    assertNull(infos.get(\"foo\")); // non-existing command\n\n    CommandInfo setInfo = infos.get(\"set\");\n    assertEquals(3, setInfo.getAclCategories().size());\n    assertEquals(0, setInfo.getTips().size());\n    assertEquals(0, setInfo.getSubcommands().size());\n  }\n\n  @Test // GitHub Issue #4020\n  @SinceRedisVersion(\"7.0.0\")\n  public void commandInfoAcl() {\n    Map<String, CommandInfo> infos = jedis.commandInfo(\"ACL\");\n    assertThat(infos, Matchers.aMapWithSize(1));\n\n    CommandInfo aclInfo = infos.get(\"acl\");\n    assertEquals(-2, aclInfo.getArity());\n    assertEquals(0, aclInfo.getFlags().size());\n    assertEquals(0, aclInfo.getFirstKey());\n    assertEquals(0, aclInfo.getLastKey());\n    assertEquals(0, aclInfo.getStep());\n    assertEquals(1, aclInfo.getAclCategories().size());\n    assertEquals(0, aclInfo.getTips().size());\n    assertThat(aclInfo.getSubcommands().size(), Matchers.greaterThanOrEqualTo(12));\n    aclInfo.getSubcommands().forEach((name, subcommand) -> {\n      assertThat(name, Matchers.startsWith(\"acl|\"));\n      assertNotNull(subcommand);\n      assertEquals(name, subcommand.getName());\n    });\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void commandList() {\n    List<String> commands = jedis.commandList();\n    assertTrue(commands.size() > 100);\n\n    commands = jedis.commandListFilterBy(CommandListFilterByParams.commandListFilterByParams().filterByModule(\"JSON\"));\n    assertEquals(0, commands.size()); // json module was not loaded\n\n    commands = jedis.commandListFilterBy(CommandListFilterByParams.commandListFilterByParams().filterByAclCat(\"admin\"));\n    assertTrue(commands.size() > 10);\n\n    commands = jedis.commandListFilterBy(CommandListFilterByParams.commandListFilterByParams().filterByPattern(\"a*\"));\n    assertTrue(commands.size() > 10);\n\n    assertThrows(IllegalArgumentException.class, () ->\n        jedis.commandListFilterBy(CommandListFilterByParams.commandListFilterByParams()));\n  }\n\n  @Test\n  public void lolwut() {\n    assertNotNull(jedis.lolwut());\n\n    assertNotNull(jedis.lolwut(new LolwutParams().version(5)));\n\n    assertNotNull(jedis.lolwut(new LolwutParams().version(5).optionalArguments()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/FailoverCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.FailoverParams;\nimport redis.clients.jedis.Endpoints;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\npublic class FailoverCommandsTest {\n\n  private static final int INVALID_PORT = 6000;\n\n  private static EndpointConfig node1;\n  private static EndpointConfig node2;\n\n  private HostAndPort masterAddress;\n  private HostAndPort replicaAddress;\n\n  @BeforeAll\n  public static void setUp() {\n    node1 = Endpoints.getRedisEndpoint(\"standalone9-failover\");\n    node2 = Endpoints.getRedisEndpoint(\"standalone10-replica-of-standalone9\");\n  }\n\n  @BeforeEach\n  public void prepare() {\n    String role1, role2;\n    try (Jedis jedis1 = new Jedis(node1.getHostAndPort(), node1.getClientConfigBuilder().build())) {\n      role1 = (String) jedis1.role().get(0);\n    }\n    try (Jedis jedis2 = new Jedis(node2.getHostAndPort(), node2.getClientConfigBuilder().build())) {\n      role2 = (String) jedis2.role().get(0);\n    }\n\n    if (\"master\".equals(role1) && \"slave\".equals(role2)) {\n      masterAddress = node1.getHostAndPort();\n      replicaAddress = node2.getHostAndPort();\n    } else if (\"master\".equals(role2) && \"slave\".equals(role1)) {\n      masterAddress = node2.getHostAndPort();\n      replicaAddress = node1.getHostAndPort();\n    } else {\n      fail();\n    }\n  }\n\n  @Test\n  public void failoverMaster() throws InterruptedException {\n    try (Jedis master = new Jedis(masterAddress)) {\n      assertEquals(\"OK\", master.failover());\n      Thread.sleep(250);\n//      assertEquals(\"slave\", master.role().get(0));\n      // Above test has a tendency to get stuck. So, doing following 'not so ideal' test.\n      if (\"slave\".equals(master.role().get(0))) {\n        // ok\n      } else {\n        // failover stuck\n      }\n    }\n  }\n\n  @Test\n  public void failoverReplica() {\n    try (Jedis replica = new Jedis(replicaAddress)) {\n      replica.failover();\n      fail(\"FAILOVER is not valid when server is a replica.\");\n    } catch(JedisDataException ex) {\n      assertEquals(\"ERR FAILOVER is not valid when server is a replica.\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void failoverForceWithoutToFailFast() {\n    try (Jedis master = new Jedis(masterAddress)) {\n      assertThrows(IllegalArgumentException.class, () -> master.failover(FailoverParams.failoverParams()\n          .timeout(100).force()));\n    }\n  }\n\n  @Test\n  public void failoverForceWithoutTimeoutFailFast() {\n    try (Jedis master = new Jedis(masterAddress)) {\n      assertThrows(IllegalArgumentException.class, ()-> master.failover(FailoverParams.failoverParams()\n          .to(new HostAndPort(\"127.0.0.1\", INVALID_PORT)).force()));\n    }\n  }\n\n  @Test\n  public void failoverToWrongPort() {\n    try (Jedis master = new Jedis(masterAddress)) {\n      master.failover(FailoverParams.failoverParams().to(\"127.0.0.1\", INVALID_PORT));\n      fail(\"FAILOVER target HOST and PORT is not a replica.\");\n    } catch(JedisDataException ex) {\n      assertEquals(\"ERR FAILOVER target HOST and PORT is not a replica.\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void failoverToWrongPortWithTimeout() {\n    try (Jedis master = new Jedis(masterAddress)) {\n      master.failover(FailoverParams.failoverParams().to(\"127.0.0.1\", INVALID_PORT).timeout(1000));\n      fail(\"FAILOVER target HOST and PORT is not a replica.\");\n    } catch(JedisDataException ex) {\n      assertEquals(\"ERR FAILOVER target HOST and PORT is not a replica.\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void abortMaster() {\n    try (Jedis master = new Jedis(masterAddress)) {\n      master.failoverAbort();\n    } catch(JedisDataException ex) {\n      assertEquals(\"ERR No failover in progress.\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void abortReplica() {\n    try (Jedis replica = new Jedis(replicaAddress)) {\n      replica.failoverAbort();\n    } catch(JedisDataException ex) {\n      assertEquals(\"ERR No failover in progress.\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/GeoCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.util.GeoCoordinateMatcher;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class GeoCommandsTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bA = { 0x0A };\n  final byte[] bB = { 0x0B };\n  final byte[] bC = { 0x0C };\n  final byte[] bD = { 0x0D };\n  final byte[] bNotexist = { 0x0F };\n\n  private static final double EPSILON = 1e-5;\n\n  public GeoCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void geoadd() {\n    assertEquals(1, jedis.geoadd(\"foo\", 1, 2, \"a\"));\n    assertEquals(0, jedis.geoadd(\"foo\", 2, 3, \"a\"));\n\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    coordinateMap.put(\"b\", new GeoCoordinate(2, 3));\n    coordinateMap.put(\"c\", new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(2, jedis.geoadd(\"foo\", coordinateMap));\n\n    // binary\n    assertEquals(1, jedis.geoadd(bfoo, 1, 2, bA));\n    assertEquals(0, jedis.geoadd(bfoo, 2, 3, bA));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    bcoordinateMap.put(bB, new GeoCoordinate(2, 3));\n    bcoordinateMap.put(bC, new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(2, jedis.geoadd(bfoo, bcoordinateMap));\n  }\n\n  @Test\n  public void geoaddWithParams() {\n    assertEquals(1, jedis.geoadd(\"foo\", 1, 2, \"a\"));\n\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    assertEquals(0, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().nx(), coordinateMap));\n    assertEquals(1, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().xx().ch(), coordinateMap));\n\n    coordinateMap.clear();\n    coordinateMap.put(\"b\", new GeoCoordinate(6, 7));\n    // never add elements.\n    assertEquals(0, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().xx(), coordinateMap));\n    assertEquals(1, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().nx(), coordinateMap));\n\n    // binary\n    assertEquals(1, jedis.geoadd(bfoo, 1, 2, bA));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    assertEquals(0, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().nx(), bcoordinateMap));\n    assertEquals(1, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().xx().ch(), bcoordinateMap));\n\n    bcoordinateMap.clear();\n    bcoordinateMap.put(bB, new GeoCoordinate(6, 7));\n    // never add elements.\n    assertEquals(0, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().xx(), bcoordinateMap));\n    assertEquals(1, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().nx(), bcoordinateMap));\n  }\n\n  @Test\n  public void geodist() {\n    prepareGeoData();\n\n    Double dist = jedis.geodist(\"foo\", \"a\", \"b\");\n    assertEquals(157149, dist.intValue());\n\n    dist = jedis.geodist(\"foo\", \"a\", \"b\", GeoUnit.KM);\n    assertEquals(157, dist.intValue());\n\n    dist = jedis.geodist(\"foo\", \"a\", \"b\", GeoUnit.MI);\n    assertEquals(97, dist.intValue());\n\n    dist = jedis.geodist(\"foo\", \"a\", \"b\", GeoUnit.FT);\n    assertEquals(515583, dist.intValue());\n\n    // binary\n    dist = jedis.geodist(bfoo, bA, bB);\n    assertEquals(157149, dist.intValue());\n\n    dist = jedis.geodist(bfoo, bA, bB, GeoUnit.KM);\n    assertEquals(157, dist.intValue());\n\n    dist = jedis.geodist(bfoo, bA, bB, GeoUnit.MI);\n    assertEquals(97, dist.intValue());\n\n    dist = jedis.geodist(bfoo, bA, bB, GeoUnit.FT);\n    assertEquals(515583, dist.intValue());\n  }\n\n  @Test\n  public void geohash() {\n    prepareGeoData();\n\n    List<String> hashes = jedis.geohash(\"foo\", \"a\", \"b\", \"notexist\");\n    assertEquals(3, hashes.size());\n    assertEquals(\"s0dnu20t9j0\", hashes.get(0));\n    assertEquals(\"s093jd0k720\", hashes.get(1));\n    assertNull(hashes.get(2));\n\n    // binary\n    List<byte[]> bhashes = jedis.geohash(bfoo, bA, bB, bNotexist);\n    assertEquals(3, bhashes.size());\n    assertArrayEquals(SafeEncoder.encode(\"s0dnu20t9j0\"), bhashes.get(0));\n    assertArrayEquals(SafeEncoder.encode(\"s093jd0k720\"), bhashes.get(1));\n    assertNull(bhashes.get(2));\n  }\n\n  @Test\n  public void geopos() {\n    prepareGeoData();\n\n    List<GeoCoordinate> coordinates = jedis.geopos(\"foo\", \"a\", \"b\", \"notexist\");\n    assertEquals(3, coordinates.size());\n    assertEquals(3.0, coordinates.get(0).getLongitude(), EPSILON);\n    assertEquals(4.0, coordinates.get(0).getLatitude(), EPSILON);\n    assertEquals(2.0, coordinates.get(1).getLongitude(), EPSILON);\n    assertEquals(3.0, coordinates.get(1).getLatitude(), EPSILON);\n    assertNull(coordinates.get(2));\n\n    List<GeoCoordinate> bcoordinates = jedis.geopos(bfoo, bA, bB, bNotexist);\n    assertEquals(3, bcoordinates.size());\n    assertEquals(3.0, bcoordinates.get(0).getLongitude(), EPSILON);\n    assertEquals(4.0, bcoordinates.get(0).getLatitude(), EPSILON);\n    assertEquals(2.0, bcoordinates.get(1).getLongitude(), EPSILON);\n    assertEquals(3.0, bcoordinates.get(1).getLatitude(), EPSILON);\n    assertNull(bcoordinates.get(2));\n  }\n\n  @Test\n  public void georadius() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily\", coordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortDescending());\n    assertEquals(2, members.size());\n    assertEquals(\"Catania\", members.get(1).getMemberByString());\n    assertEquals(\"Palermo\", members.get(0).getMemberByString());\n\n    // sort, count 1\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1).withCoord().withDist().withHash());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n    assertEquals(3479447370796909L, response.getRawScore());\n\n    // sort, count 1, with hash\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1).withHash());\n    assertEquals(1, members.size());\n    response = members.get(0);\n    assertEquals(3479447370796909L, response.getRawScore());\n\n    // sort, count 1, any\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n            .sortDescending().count(1, true));\n    assertEquals(1, members.size());\n    response = members.get(0);\n    assertTrue(coordinateMap.containsKey(response.getMemberByString()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusStore() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily\", coordinateMap);\n\n    long size = jedis.georadiusStore(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam(),\n      GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"Palermo\");\n    expected.add(\"Catania\");\n    assertEquals(expected, jedis.zrange(\"SicilyStore\", 0, -1));\n  }\n\n  @Test\n  public void georadiusReadonly() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily\", coordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertEquals(\"Catania\", members.get(0).getMemberByString());\n    assertEquals(\"Palermo\", members.get(1).getMemberByString());\n\n    // sort, count 1\n    members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void georadiusBinary() {\n    // prepare datas\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(bfoo, bcoordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bB, members.get(0).getMember());\n    assertArrayEquals(bA, members.get(1).getMember());\n\n    // sort, count 1\n    members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusStoreBinary() {\n    // prepare datas\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(bfoo, bcoordinateMap);\n\n    long size = jedis.georadiusStore(bfoo, 15, 37, 200, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam(),\n      GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n    assertEquals(2, size);\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    assertByteArrayListEquals(bexpected, jedis.zrange(\"SicilyStore\".getBytes(), 0, -1));\n  }\n\n  @Test\n  public void georadiusReadonlyBinary() {\n    // prepare datas\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(bfoo, bcoordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bB, members.get(0).getMember());\n    assertArrayEquals(bA, members.get(1).getMember());\n\n    // sort, count 1\n    members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void georadiusByMember() {\n    jedis.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMember(\"Sicily\", \"Agrigento\", 100,\n      GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMember(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM, GeoRadiusParam\n        .geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertEquals(\"Agrigento\", members.get(0).getMemberByString());\n    assertEquals(\"Palermo\", members.get(1).getMemberByString());\n\n    members = jedis.georadiusByMember(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM, GeoRadiusParam\n        .geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertEquals(\"Agrigento\", member.getMemberByString());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusByMemberStore() {\n    jedis.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    long size = jedis.georadiusByMemberStore(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam(),\n      GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"Agrigento\");\n    expected.add(\"Palermo\");\n    assertEquals(expected, jedis.zrange(\"SicilyStore\", 0, -1));\n  }\n\n  @Test\n  public void georadiusByMemberReadonly() {\n    jedis.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100,\n      GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertEquals(\"Agrigento\", members.get(0).getMemberByString());\n    assertEquals(\"Palermo\", members.get(1).getMemberByString());\n\n    members = jedis.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertEquals(\"Agrigento\", member.getMemberByString());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void georadiusByMemberBinary() {\n    jedis.geoadd(bfoo, 13.583333, 37.316667, bA);\n    jedis.geoadd(bfoo, 13.361389, 38.115556, bB);\n    jedis.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMember(bfoo, bA, 100, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMember(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bA, members.get(0).getMember());\n    assertArrayEquals(bB, members.get(1).getMember());\n\n    members = jedis.georadiusByMember(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertArrayEquals(bA, member.getMember());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusByMemberStoreBinary() {\n    jedis.geoadd(bfoo, 13.583333, 37.316667, bA);\n    jedis.geoadd(bfoo, 13.361389, 38.115556, bB);\n    jedis.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    assertEquals(2, jedis.georadiusByMemberStore(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\")));\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    assertByteArrayListEquals(bexpected, jedis.zrange(\"SicilyStore\".getBytes(), 0, -1));\n  }\n\n  @Test\n  public void georadiusByMemberReadonlyBinary() {\n    jedis.geoadd(bfoo, 13.583333, 37.316667, bA);\n    jedis.geoadd(bfoo, 13.361389, 38.115556, bB);\n    jedis.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bA, members.get(0).getMember());\n    assertArrayEquals(bB, members.get(1).getMember());\n\n    members = jedis.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertArrayEquals(bA, member.getMember());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void geosearch() {\n    jedis.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    jedis.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n    jedis.geoadd(\"barcelona\", 2.583333d, 41.316667d, \"place3\");\n\n    // FROMLONLAT and BYRADIUS\n    List<GeoRadiusResponse> members = jedis.geosearch(\"barcelona\",\n            new GeoCoordinate(2.191d,41.433d), 1000, GeoUnit.M);\n    assertEquals(1, members.size());\n    assertEquals(\"place1\", members.get(0).getMemberByString());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().byRadius(3000, GeoUnit.M)\n            .fromLonLat(2.191d,41.433d).desc());\n    assertEquals(2, members.size());\n    assertEquals(\"place2\", members.get(0).getMemberByString());\n\n    // FROMMEMBER and BYRADIUS\n    members = jedis.geosearch(\"barcelona\",\"place3\", 100, GeoUnit.KM);\n    assertEquals(3, members.size());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"place1\")\n            .byRadius(100, GeoUnit.KM).withDist().withCoord().withHash().count(2));\n\n    assertEquals(2, members.size());\n    assertEquals(\"place1\", members.get(0).getMemberByString());\n    GeoRadiusResponse res2 = members.get(1);\n    assertEquals(\"place2\", res2.getMemberByString());\n    assertEquals(3.0674157, res2.getDistance(), 5);\n    assertEquals(new GeoCoordinate(2.187376320362091, 41.40634178640635), res2.getCoordinate());\n\n    // FROMMEMBER and BYBOX\n    members = jedis.geosearch(\"barcelona\",\"place3\", 100, 100, GeoUnit.KM);\n    assertEquals(3, members.size());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"place3\")\n            .byBox(100, 100, GeoUnit.KM).asc().count(1, true));\n    assertEquals(1, members.size());\n\n    // FROMLONLAT and BYBOX\n    members = jedis.geosearch(\"barcelona\", new GeoCoordinate(2.191, 41.433),\n            1, 1, GeoUnit.KM);\n    assertEquals(1, members.size());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().byBox(1,1, GeoUnit.KM)\n            .fromLonLat(2.191, 41.433).withDist().withCoord());\n    assertEquals(1, members.size());\n    assertEquals(\"place1\", members.get(0).getMemberByString());\n    assertEquals(0.0881, members.get(0).getDistance(), 10);\n    assertThat(members.get(0).getCoordinate(),\n        GeoCoordinateMatcher.atCoordinates(2.19093829393386841, 41.43379028184083523));\n  }\n\n  @Test\n  public void geosearchNegative() {\n    // combine byradius and bybox\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam()\n          .byRadius(3000, GeoUnit.M)\n          .byBox(300, 300, GeoUnit.M));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n\n    // without byradius and without bybox\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"foobar\"));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n\n    // combine frommember and fromlonlat\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam()\n          .fromMember(\"foobar\")\n          .fromLonLat(10,10));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n\n    // without frommember and without fromlonlat\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam().byRadius(10, GeoUnit.MI));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void geosearchstore() {\n    jedis.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    jedis.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n    jedis.geoadd(\"barcelona\", 2.583333d, 41.316667d, \"place3\");\n\n    // FROMLONLAT and BYRADIUS\n    long members = jedis.geosearchStore(\"tel-aviv\", \"barcelona\", new GeoCoordinate(2.191d,41.433d),\n            1000, GeoUnit.M);\n    assertEquals(1, members);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"place1\");\n    assertEquals(expected, jedis.zrange(\"tel-aviv\", 0, -1));\n\n    members = jedis.geosearchStore(\"tel-aviv\",\"barcelona\", new GeoSearchParam()\n            .byRadius(3000, GeoUnit.M)\n            .fromLonLat(new GeoCoordinate(2.191d,41.433d)));\n    assertEquals(2, members);\n    assertEquals(2, members);\n\n    // FROMMEMBER and BYRADIUS\n    members = jedis.geosearchStore(\"tel-aviv\", \"barcelona\",\"place3\", 100, GeoUnit.KM);\n    assertEquals(3, members);\n\n    // FROMMEMBER and BYBOX\n    members = jedis.geosearchStore(\"tel-aviv\",\"barcelona\",\"place3\", 100, 100, GeoUnit.KM);\n    assertEquals(3, members);\n\n    // FROMLONLAT and BYBOX\n    members = jedis.geosearchStore(\"tel-aviv\",\"barcelona\", new GeoCoordinate(2.191, 41.433),\n            1, 1, GeoUnit.KM);\n    assertEquals(1, members);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void geosearchstoreWithdist() {\n    jedis.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    jedis.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n\n    long members = jedis.geosearchStoreStoreDist(\"tel-aviv\",\"barcelona\", new GeoSearchParam().byRadius(3000, GeoUnit.M)\n            .fromLonLat(2.191d,41.433d));\n\n    assertEquals(2, members);\n    assertEquals(88.05060698409301, jedis.zscore(\"tel-aviv\", \"place1\"), 5);\n  }\n\n  private void prepareGeoData() {\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    coordinateMap.put(\"b\", new GeoCoordinate(2, 3));\n    coordinateMap.put(\"c\", new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(3, jedis.geoadd(\"foo\", coordinateMap));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    bcoordinateMap.put(bB, new GeoCoordinate(2, 3));\n    bcoordinateMap.put(bC, new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(3, jedis.geoadd(bfoo, bcoordinateMap));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/HashesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static java.util.Arrays.asList;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.both;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.Pipeline;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.JedisByteHashMap;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class HashesCommandsTest extends JedisCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(() -> endpoint);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(() -> endpoint);\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] bbare = { 0x05, 0x06, 0x07, 0x08, 0x09 };\n  final byte[] bcare = { 0x09, 0x0A, 0x0B, 0x0C, 0x0D };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public HashesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void hset() {\n    assertEquals(1, jedis.hset(\"foo\", \"bar\", \"car\"));\n    assertEquals(0, jedis.hset(\"foo\", \"bar\", \"foo\"));\n\n    // Binary\n    assertEquals(1, jedis.hset(bfoo, bbar, bcar));\n    assertEquals(0, jedis.hset(bfoo, bbar, bfoo));\n  }\n\n  @Test\n  public void hget() {\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertNull(jedis.hget(\"bar\", \"foo\"));\n    assertNull(jedis.hget(\"foo\", \"car\"));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n\n    // Binary\n    jedis.hset(bfoo, bbar, bcar);\n    assertNull(jedis.hget(bbar, bfoo));\n    assertNull(jedis.hget(bfoo, bcar));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void hgetex() {\n    long seconds = 20;\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(\"car\"), jedis.hgetex(\"foo\", HGetExParams.hGetExParams().ex(seconds), \"bar\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(\"car\"), jedis.hgetex(\"foo\", HGetExParams.hGetExParams().persist(), \"bar\"));\n    assertEquals(jedis.httl(\"foo\", \"bar\").get(0), Long.valueOf(-1));\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(\"car\", \"care\"), jedis.hgetex(\"foo\", HGetExParams.hGetExParams().ex(seconds), \"bar\", \"bare\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(\"foo\", \"bare\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    // Binary\n    jedis.hset(bfoo, bbar, bcar);\n    assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    jedis.hset(bfoo, bbar, bcar);\n    assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().persist(), bbar));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0));\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbare, bcare);\n    assertByteArrayListEquals(asList(bcar, bcare), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar, bbare));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void hgetdel() {\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(\"car\"), jedis.hgetdel(\"foo\", \"bar\"));\n    assertNull(jedis.hget(\"foo\", \"bar\"));\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(\"car\", \"care\"), jedis.hgetdel(\"foo\", \"bar\", \"bare\"));\n    assertNull(jedis.hget(\"foo\", \"bar\"));\n    assertNull(jedis.hget(\"foo\", \"bare\"));\n\n    // Binary\n    jedis.hset(bfoo, bbar, bcar);\n    assertByteArrayListEquals(asList(bcar), jedis.hgetdel(bfoo, bbar));\n    assertNull(jedis.hget(bfoo, bbar));\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbare, bcare);\n    assertByteArrayListEquals(asList(bcar, bcare), jedis.hgetdel(bfoo, bbar, bbare));\n    assertNull(jedis.hget(bfoo, bbar));\n    assertNull(jedis.hget(bfoo, bbare));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void hsetex() {\n    long seconds = 20;\n    jedis.del(\"foo\");\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().ex(seconds).fnx(), \"bar\", \"car\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().keepTtl(), \"bar\", \"car\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().fxx(), \"bar\", \"car\"));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bar\").get(0));\n\n    HashMap<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"bare\", \"care\");\n    jedis.del(\"foo\");\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().ex(seconds).fnx(), hash));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(\"foo\", \"bare\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().keepTtl(), hash));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(\"foo\", \"bare\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().fxx(), hash));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bar\").get(0));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bare\").get(0));\n\n    // Binary\n    jedis.del(bfoo);\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bbar, bcar));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bbar, bcar));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bbar, bcar));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0));\n\n    HashMap<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bbare, bcare);\n\n    jedis.del(bfoo);\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bhash));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bhash));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bhash));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbare).get(0));\n  }\n\n  @Test\n  public void hsetnx() {\n    assertEquals(1, jedis.hsetnx(\"foo\", \"bar\", \"car\"));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n\n    assertEquals(0, jedis.hsetnx(\"foo\", \"bar\", \"foo\"));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n\n    assertEquals(1, jedis.hsetnx(\"foo\", \"car\", \"bar\"));\n    assertEquals(\"bar\", jedis.hget(\"foo\", \"car\"));\n\n    // Binary\n    assertEquals(1, jedis.hsetnx(bfoo, bbar, bcar));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n\n    assertEquals(0, jedis.hsetnx(bfoo, bbar, bfoo));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n\n    assertEquals(1, jedis.hsetnx(bfoo, bcar, bbar));\n    assertArrayEquals(bbar, jedis.hget(bfoo, bcar));\n  }\n\n  @Test\n  public void hmset() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    assertEquals(\"OK\", jedis.hmset(\"foo\", hash));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.hget(\"foo\", \"car\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    assertEquals(\"OK\", jedis.hmset(bfoo, bhash));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n    assertArrayEquals(bbar, jedis.hget(bfoo, bcar));\n  }\n\n  @Test\n  public void hsetVariadic() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    assertEquals(2, jedis.hset(\"foo\", hash));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.hget(\"foo\", \"car\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    assertEquals(2, jedis.hset(bfoo, bhash));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n    assertArrayEquals(bbar, jedis.hget(bfoo, bcar));\n  }\n\n  @Test\n  public void hmget() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    List<String> values = jedis.hmget(\"foo\", \"bar\", \"car\", \"foo\");\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"car\");\n    expected.add(\"bar\");\n    expected.add(null);\n\n    assertEquals(expected, values);\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    List<byte[]> bvalues = jedis.hmget(bfoo, bbar, bcar, bfoo);\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bcar);\n    bexpected.add(bbar);\n    bexpected.add(null);\n\n    AssertUtil.assertByteArrayListEquals(bexpected, bvalues);\n  }\n\n  @Test\n  public void hincrBy() {\n    assertEquals(1, jedis.hincrBy(\"foo\", \"bar\", 1));\n    assertEquals(0, jedis.hincrBy(\"foo\", \"bar\", -1));\n    assertEquals(-10, jedis.hincrBy(\"foo\", \"bar\", -10));\n\n    // Binary\n    assertEquals(1, jedis.hincrBy(bfoo, bbar, 1));\n    assertEquals(0, jedis.hincrBy(bfoo, bbar, -1));\n    assertEquals(-10, jedis.hincrBy(bfoo, bbar, -10));\n  }\n\n  @Test\n  public void hincrByFloat() {\n    assertEquals(1.5d, jedis.hincrByFloat(\"foo\", \"bar\", 1.5d), 0);\n    assertEquals(0d, jedis.hincrByFloat(\"foo\", \"bar\", -1.5d), 0);\n    assertEquals(-10.7d, jedis.hincrByFloat(\"foo\", \"bar\", -10.7d), 0);\n\n    // Binary\n    assertEquals(1.5d, jedis.hincrByFloat(bfoo, bbar, 1.5d), 0d);\n    assertEquals(0d, jedis.hincrByFloat(bfoo, bbar, -1.5d), 0d);\n    assertEquals(-10.7d, jedis.hincrByFloat(bfoo, bbar, -10.7d), 0d);\n  }\n\n  @Test\n  public void hexists() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertFalse(jedis.hexists(\"bar\", \"foo\"));\n    assertFalse(jedis.hexists(\"foo\", \"foo\"));\n    assertTrue(jedis.hexists(\"foo\", \"bar\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertFalse(jedis.hexists(bbar, bfoo));\n    assertFalse(jedis.hexists(bfoo, bfoo));\n    assertTrue(jedis.hexists(bfoo, bbar));\n  }\n\n  @Test\n  public void hdel() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertEquals(0, jedis.hdel(\"bar\", \"foo\"));\n    assertEquals(0, jedis.hdel(\"foo\", \"foo\"));\n    assertEquals(1, jedis.hdel(\"foo\", \"bar\"));\n    assertNull(jedis.hget(\"foo\", \"bar\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertEquals(0, jedis.hdel(bbar, bfoo));\n    assertEquals(0, jedis.hdel(bfoo, bfoo));\n    assertEquals(1, jedis.hdel(bfoo, bbar));\n    assertNull(jedis.hget(bfoo, bbar));\n\n    // Deleting multiple fields returns the right value.\n    jedis.hmset(\"foo\", hash);\n    assertEquals(2, jedis.hdel(\"foo\", \"bar\", \"car\", \"dne\"));\n\n    jedis.hmset(bfoo, bhash);\n    assertEquals(2, jedis.hdel(bfoo, bbar, bcar, bbar1));\n  }\n\n  @Test\n  public void hlen() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertEquals(0, jedis.hlen(\"bar\"));\n    assertEquals(2, jedis.hlen(\"foo\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertEquals(0, jedis.hlen(bbar));\n    assertEquals(2, jedis.hlen(bfoo));\n  }\n\n  @Test\n  public void hkeys() {\n    Map<String, String> hash = new LinkedHashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    Set<String> keys = jedis.hkeys(\"foo\");\n    Set<String> expected = new LinkedHashSet<String>();\n    expected.add(\"bar\");\n    expected.add(\"car\");\n    assertEquals(expected, keys);\n\n    // Binary\n    Map<byte[], byte[]> bhash = new LinkedHashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    Set<byte[]> bkeys = jedis.hkeys(bfoo);\n    Set<byte[]> bexpected = new LinkedHashSet<byte[]>();\n    bexpected.add(bbar);\n    bexpected.add(bcar);\n    AssertUtil.assertByteArraySetEquals(bexpected, bkeys);\n  }\n\n  @Test\n  public void hvals() {\n    Map<String, String> hash = new LinkedHashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    List<String> vals = jedis.hvals(\"foo\");\n    assertEquals(2, vals.size());\n    AssertUtil.assertCollectionContains(vals, \"bar\");\n    AssertUtil.assertCollectionContains(vals, \"car\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new LinkedHashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    List<byte[]> bvals = jedis.hvals(bfoo);\n\n    assertEquals(2, bvals.size());\n    AssertUtil.assertByteArrayCollectionContains(bvals, bbar);\n    AssertUtil.assertByteArrayCollectionContains(bvals, bcar);\n  }\n\n  @Test\n  public void hgetAll() {\n    Map<String, String> h = new HashMap<String, String>();\n    h.put(\"bar\", \"car\");\n    h.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", h);\n\n    Map<String, String> hash = jedis.hgetAll(\"foo\");\n    assertEquals(2, hash.size());\n    assertEquals(\"car\", hash.get(\"bar\"));\n    assertEquals(\"bar\", hash.get(\"car\"));\n\n    // Binary\n    Map<byte[], byte[]> bh = new HashMap<byte[], byte[]>();\n    bh.put(bbar, bcar);\n    bh.put(bcar, bbar);\n    jedis.hmset(bfoo, bh);\n    Map<byte[], byte[]> bhash = jedis.hgetAll(bfoo);\n\n    assertEquals(2, bhash.size());\n    assertArrayEquals(bcar, bhash.get(bbar));\n    assertArrayEquals(bbar, bhash.get(bcar));\n  }\n\n  @Test\n  public void hgetAllPipeline() {\n    Map<byte[], byte[]> bh = new HashMap<byte[], byte[]>();\n    bh.put(bbar, bcar);\n    bh.put(bcar, bbar);\n    jedis.hmset(bfoo, bh);\n    Pipeline pipeline = jedis.pipelined();\n    Response<Map<byte[], byte[]>> bhashResponse = pipeline.hgetAll(bfoo);\n    pipeline.sync();\n    Map<byte[], byte[]> bhash = bhashResponse.get();\n\n    assertEquals(2, bhash.size());\n    assertArrayEquals(bcar, bhash.get(bbar));\n    assertArrayEquals(bbar, bhash.get(bcar));\n  }\n\n  @Test\n  public void hscan() {\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(\"a\", \"b\"));\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(\"x\", \"y\"));\n\n    // binary\n    jedis.hset(bfoo, bbar, bcar);\n\n    ScanResult<Map.Entry<byte[], byte[]>> bResult = jedis.hscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(1, bResult.getResult().size());\n\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(bbar));\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(bcar));\n  }\n\n  @Test\n  public void hscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n    jedis.hset(\"foo\", \"aa\", \"xx\");\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(\"a\", \"aa\"));\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(\"x\", \"xx\"));\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<Map.Entry<byte[], byte[]>> bResult = jedis.hscan(bfoo, SCAN_POINTER_START_BINARY,\n        params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(4, bResult.getResult().size());\n\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(bbar, bbar1, bbar2, bbar3));\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(bcar, bcar, bcar, bcar));\n  }\n\n  @Test\n  public void hscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.hset(\"foo\", \"a\" + i, \"x\" + i);\n    }\n\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getKey).map(s -> s.substring(0, 1)).collect(Collectors.toSet()),\n        containsInAnyOrder(\"a\"));\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getValue).map(s -> s.substring(0, 1)).collect(Collectors.toSet()),\n        containsInAnyOrder(\"x\"));\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<Map.Entry<byte[], byte[]>> bResult = jedis.hscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getKey)\n            .map(a -> Arrays.copyOfRange(a, 0, 4)).map(Arrays::toString).collect(Collectors.toSet()),\n        containsInAnyOrder(Arrays.toString(bbar)));\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getValue)\n            .map(a -> Arrays.copyOfRange(a, 0, 4)).map(Arrays::toString).collect(Collectors.toSet()),\n        containsInAnyOrder(Arrays.toString(bcar)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hscanNoValues() {\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n\n    ScanResult<String> result = jedis.hscanNoValues(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(result.getResult(), containsInAnyOrder(\"a\", \"b\"));\n\n    // binary\n    jedis.hset(bfoo, bbar, bcar);\n\n    ScanResult<byte[]> bResult = jedis.hscanNoValues(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(1, bResult.getResult().size());\n\n    assertThat(bResult.getResult(), containsInAnyOrder(bbar));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"NOVALUES flag (since Redis 7.4)\")\n  public void hscanNoValuesMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n    jedis.hset(\"foo\", \"aa\", \"xx\");\n    ScanResult<String> result = jedis.hscanNoValues(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(result.getResult(), containsInAnyOrder(\"a\", \"aa\"));\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<byte[]> bResult = jedis.hscanNoValues(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(4, bResult.getResult().size());\n\n    assertThat(bResult.getResult(), containsInAnyOrder(bbar, bbar1, bbar2, bbar3));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"NOVALUES flag (since Redis 7.4)\")\n  public void hscanNoValuesCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.hset(\"foo\", \"a\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.hscanNoValues(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    assertThat(\n        result.getResult().stream().map(s -> s.substring(0, 1)).collect(Collectors.toSet()),\n        containsInAnyOrder(\"a\"));\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<byte[]> bResult = jedis.hscanNoValues(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n\n    assertThat(\n        bResult.getResult().stream()\n            .map(a -> Arrays.copyOfRange(a, 0, 4)).map(Arrays::toString).collect(Collectors.toSet()),\n        containsInAnyOrder(Arrays.toString(bbar)));\n  }\n\n  @Test\n  public void testHstrLen_EmptyHash() {\n    long response = jedis.hstrlen(\"myhash\", \"k1\");\n    assertEquals(0L, response);\n  }\n\n  @Test\n  public void testHstrLen() {\n    Map<String, String> values = new HashMap<>();\n    values.put(\"key\", \"value\");\n    jedis.hmset(\"myhash\", values);\n    long response = jedis.hstrlen(\"myhash\", \"key\");\n    assertEquals(5L, response);\n\n  }\n\n  @Test\n  public void testBinaryHstrLen() {\n    Map<byte[], byte[]> values = new HashMap<>();\n    values.put(bbar, bcar);\n    jedis.hmset(bfoo, values);\n    long response = jedis.hstrlen(bfoo, bbar);\n    assertEquals(4L, response);\n  }\n\n  @Test\n  public void hrandfield() {\n    assertNull(jedis.hrandfield(\"foo\"));\n    assertEquals(Collections.emptyList(), jedis.hrandfield(\"foo\", 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(\"foo\", 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(\"foo\", -1));\n\n    Map<String, String> hash = new LinkedHashMap<>();\n    hash.put(\"bar\", \"bar\");\n    hash.put(\"car\", \"car\");\n    hash.put(\"bar1\", \"bar1\");\n\n    jedis.hset(\"foo\", hash);\n\n    assertTrue(hash.containsKey(jedis.hrandfield(\"foo\")));\n    assertEquals(2, jedis.hrandfield(\"foo\", 2).size());\n\n    List<Map.Entry<String, String>> actual = jedis.hrandfieldWithValues(\"foo\", 2);\n    assertEquals(2, actual.size());\n    actual.forEach(e -> assertEquals(hash.get(e.getKey()), e.getValue()));\n\n    actual = jedis.hrandfieldWithValues(\"foo\", 5);\n    assertEquals(3, actual.size());\n    actual.forEach(e -> assertEquals(hash.get(e.getKey()), e.getValue()));\n\n    actual = jedis.hrandfieldWithValues(\"foo\", -5);\n    assertEquals(5, actual.size());\n    actual.forEach(e -> assertEquals(hash.get(e.getKey()), e.getValue()));\n\n    // binary\n    assertNull(jedis.hrandfield(bfoo));\n    assertEquals(Collections.emptyList(), jedis.hrandfield(bfoo, 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(bfoo, 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(bfoo, -1));\n\n    Map<byte[], byte[]> bhash = new JedisByteHashMap();\n    bhash.put(bbar, bbar);\n    bhash.put(bcar, bcar);\n    bhash.put(bbar1, bbar1);\n\n    jedis.hset(bfoo, bhash);\n\n    assertTrue(bhash.containsKey(jedis.hrandfield(bfoo)));\n    assertEquals(2, jedis.hrandfield(bfoo, 2).size());\n\n    List<Map.Entry<byte[], byte[]>> bactual = jedis.hrandfieldWithValues(bfoo, 2);\n    assertEquals(2, bactual.size());\n    bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue()));\n\n    bactual = jedis.hrandfieldWithValues(bfoo, 5);\n    assertEquals(3, bactual.size());\n    bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue()));\n\n    bactual = jedis.hrandfieldWithValues(bfoo, -5);\n    assertEquals(5, bactual.size());\n    bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue()));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAndHttl() {\n    long seconds1 = 20;\n    long seconds2 = 10;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(1L, -2L), jedis.hexpire(\"foo\", seconds1, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(0L, 1L), jedis.hexpire(\"foo\", seconds2, ExpiryOption.NX, \"bar\", \"bared\"));\n\n    assertThat(jedis.httl(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAndHttlBinary() {\n    long seconds1 = 20;\n    long seconds2 = 10;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds1, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(0L, 1L), jedis.hexpire(bfoo, seconds2, ExpiryOption.NX, bbar1, bbar3));\n\n    assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3),\n        contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAndHpttl() {\n    long millis1 = 20_000;\n    long millis2 = 10_000;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(1L, -2L), jedis.hpexpire(\"foo\", millis1, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(1L, 0L), jedis.hpexpire(\"foo\", millis2, ExpiryOption.XX, \"bar\", \"bared\"));\n\n    assertThat(jedis.hpttl(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 1000)), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAndHpttlBinary() {\n    long millis1 = 20_000;\n    long millis2 = 10_000;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    assertEquals(asList(1L, -2L), jedis.hpexpire(bfoo, millis1, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(1L, 0L), jedis.hpexpire(bfoo, millis2, ExpiryOption.XX, bbar1, bbar3));\n\n    assertThat(jedis.hpttl(bfoo, bbar1, bbar2, bbar3),\n        contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 1000)), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAtAndExpireTime() {\n    long currSeconds = System.currentTimeMillis() / 1000;\n    long seconds1 = currSeconds + 20;\n    long seconds2 = currSeconds + 10;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(1L, -2L), jedis.hexpireAt(\"foo\", seconds1, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(1L, 1L), jedis.hexpireAt(\"foo\", seconds2, ExpiryOption.LT, \"bar\", \"bared\"));\n\n    assertThat(jedis.hexpireTime(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAtAndExpireTimeBinary() {\n    long currSeconds = System.currentTimeMillis() / 1000;\n    long seconds1 = currSeconds + 20;\n    long seconds2 = currSeconds + 10;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    assertEquals(asList(1L, -2L), jedis.hexpireAt(bfoo, seconds1, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(1L, 1L), jedis.hexpireAt(bfoo, seconds2, ExpiryOption.LT, bbar1, bbar3));\n\n    assertThat(jedis.hexpireTime(bfoo, bbar1, bbar2, bbar3),\n        contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAtAndPexpireTime() {\n    long currMillis = System.currentTimeMillis();\n    long unixMillis = currMillis + 20_000;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(1L, -2L), jedis.hpexpireAt(\"foo\", unixMillis - 100, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(1L, 0L), jedis.hpexpireAt(\"foo\", unixMillis, ExpiryOption.GT, \"bar\", \"bared\"));\n\n    assertThat(jedis.hpexpireTime(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAtAndPexpireTimeBinary() {\n    long currMillis = System.currentTimeMillis();\n    long unixMillis = currMillis + 20_000;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    assertEquals(asList(1L, -2L), jedis.hpexpireAt(bfoo, unixMillis - 100, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(1L, 0L), jedis.hpexpireAt(bfoo, unixMillis, ExpiryOption.GT, bbar1, bbar3));\n\n    assertThat(jedis.hpexpireTime(bfoo, bbar1, bbar2, bbar3),\n        contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpersist() {\n    long seconds = 20;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(1L, -2L), jedis.hexpire(\"foo\", seconds, \"bar\", \"bared\"));\n\n    assertEquals(asList(1L, -1L, -2L), jedis.hpersist(\"foo\", \"bar\", \"bare\", \"bared\"));\n\n    assertThat(jedis.httl(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(equalTo(-1L), equalTo(-1L), equalTo(-2L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpersistBinary() {\n    long seconds = 20;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds, bbar1, bbar3));\n\n    assertEquals(asList(1L, -1L, -2L), jedis.hpersist(bfoo, bbar1, bbar2, bbar3));\n\n    assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3),\n        contains(equalTo(-1L), equalTo(-1L), equalTo(-2L)));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/HotkeysCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.util.RedisVersionUtil.getRedisVersion;\n\nimport java.time.Duration;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.utils.RedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.HotkeysMetric;\nimport redis.clients.jedis.params.HotkeysParams;\nimport redis.clients.jedis.resps.HotkeysInfo;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\n@EnabledOnCommand(\"HOTKEYS\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\npublic class HotkeysCommandsTest extends JedisCommandsTestBase {\n\n  public HotkeysCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  @BeforeEach\n  public void setUp() throws Exception {\n    super.setUp();\n    clearState();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    clearState();\n  }\n\n  private void clearState() {\n    if (jedis != null) {\n      jedis.flushAll();\n      jedis.hotkeysStop();\n      jedis.hotkeysReset();\n    }\n  }\n\n  @Test\n  public void hotkeysGetBeforeStart() {\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNull(reply);\n  }\n\n  @Test\n  public void hotkeysLifecycle() {\n    String startResult = jedis\n        .hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU));\n    assertEquals(\"OK\", startResult);\n\n    jedis.set(\"key1\", \"value1\");\n    jedis.get(\"key1\");\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertTrue(reply.isTrackingActive());\n\n    String stopResult = jedis.hotkeysStop();\n    assertEquals(\"OK\", stopResult);\n\n    reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertFalse(reply.isTrackingActive());\n    assertTrue(reply.getByCpuTimeUs().containsKey(\"key1\"));\n\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU));\n    jedis.set(\"key2\", \"val2\");\n    reply = jedis.hotkeysGet();\n    assertTrue(reply.isTrackingActive());\n    assertTrue(reply.getByCpuTimeUs().containsKey(\"key2\"));\n    assertFalse(reply.getByCpuTimeUs().containsKey(\"key1\"));\n\n    jedis.hotkeysStop();\n    String resetResult = jedis.hotkeysReset();\n    assertEquals(\"OK\", resetResult);\n\n    reply = jedis.hotkeysGet();\n    assertNull(reply);\n  }\n\n  @Test\n  public void hotkeysBothMetrics() {\n    jedis.hotkeysStart(\n      HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU, HotkeysMetric.NET).sample(1));\n\n    String cpuHot = \"stats:counter\";\n    for (int i = 0; i < 20; i++) {\n      jedis.incr(cpuHot);\n    }\n\n    String netHot = \"blob:data\";\n    String largeValue = new String(new char[6000]).replace('\\0', 'x');\n    jedis.set(netHot, largeValue);\n    jedis.get(netHot);\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n\n    assertTrue(reply.getByCpuTimeUs().containsKey(cpuHot));\n    assertThat(reply.getByCpuTimeUs().get(cpuHot), greaterThan(0L));\n\n    assertTrue(reply.getByNetBytes().containsKey(netHot));\n    assertThat(reply.getByNetBytes().get(netHot), greaterThan(6000L));\n  }\n\n  @Test\n  public void hotkeysStartOptions() {\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU).sample(5));\n\n    for (int i = 0; i < 20; i++) {\n      jedis.set(\"samplekey\" + i, \"value\" + i);\n    }\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertEquals(5, reply.getSampleRatio());\n    assertThat(reply.getByCpuTimeUs().size(), lessThan(20));\n\n    jedis.hotkeysStop();\n    jedis.hotkeysReset();\n\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU).count(10));\n\n    for (int i = 1; i <= 25; i++) {\n      jedis.set(\"countkey\" + i, \"value\" + i);\n    }\n\n    reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertThat(reply.getByCpuTimeUs().size(), lessThanOrEqualTo(10));\n  }\n\n  @Test\n  public void hotkeysResponseFields() {\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU, HotkeysMetric.NET));\n\n    jedis.set(\"testkey\", \"testvalue\");\n    jedis.get(\"testkey\");\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n\n    assertTrue(reply.isTrackingActive());\n    assertEquals(1, reply.getSampleRatio());\n    assertNotNull(reply.getSelectedSlots());\n    // In standalone mode, server returns slot ranges (e.g., [[0, 16383]] for all slots)\n    assertThat(reply.getCollectionStartTimeUnixMs(), greaterThan(0L));\n    assertThat(reply.getCollectionDurationMs(), greaterThanOrEqualTo(0L));\n    assertNotNull(reply.getByCpuTimeUs());\n    assertNotNull(reply.getByNetBytes());\n  }\n\n  @Test\n  public void hotkeysDurationOption() {\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU).duration(1));\n\n    jedis.set(\"durationkey\", \"testvalue\");\n\n    await().atMost(Duration.ofSeconds(2)).until(() -> {\n      HotkeysInfo info = jedis.hotkeysGet();\n      return info != null && !info.isTrackingActive();\n    });\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertFalse(reply.isTrackingActive());\n    assertThat(reply.getCollectionDurationMs(), greaterThanOrEqualTo(1000L));\n    assertTrue(reply.getByCpuTimeUs().containsKey(\"durationkey\"));\n  }\n\n  @Test\n  public void infoHotkeysSection() {\n    boolean isRedis8_6_1OrHigher = getRedisVersion(jedis)\n        .isGreaterThanOrEqualTo(RedisVersion.of(\"8.6.1\"));\n\n    String info = jedis.info();\n    // Hotkeys section is displayed in info even when empty starting from Redis 8.6.1+\n    if (isRedis8_6_1OrHigher) {\n      assertTrue(info.contains(\"# Hotkeys\"));\n    } else {\n      assertFalse(info.contains(\"# Hotkeys\"));\n    }\n\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU));\n    info = jedis.info();\n    assertTrue(info.contains(\"# Hotkeys\"));\n    assertTrue(info.contains(\"hotkeys-tracking-active:1\"));\n\n    jedis.hotkeysStop();\n    info = jedis.info();\n    assertTrue(info.contains(\"# Hotkeys\"));\n    assertTrue(info.contains(\"hotkeys-tracking-active:0\"));\n\n    jedis.hotkeysReset();\n    info = jedis.info();\n\n    // Hotkeys section is displayed in info even when empty starting from Redis 8.6.1+\n    if (isRedis8_6_1OrHigher) {\n      assertTrue(info.contains(\"# Hotkeys\"));\n    } else {\n      assertFalse(info.contains(\"# Hotkeys\"));\n    }\n\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/HyperLogLogCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class HyperLogLogCommandsTest extends JedisCommandsTestBase {\n\n  public HyperLogLogCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void pfadd() {\n    long status = jedis.pfadd(\"foo\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"foo\", \"a\");\n    assertEquals(0, status);\n  }\n\n  @Test\n  public void pfaddBinary() {\n    byte[] bFoo = SafeEncoder.encode(\"foo\");\n    byte[] bBar = SafeEncoder.encode(\"bar\");\n    byte[] bBar2 = SafeEncoder.encode(\"bar2\");\n\n    long status = jedis.pfadd(bFoo, bBar, bBar2);\n    assertEquals(1, status);\n\n    status = jedis.pfadd(bFoo, bBar, bBar2);\n    assertEquals(0, status);\n  }\n\n  @Test\n  public void pfcount() {\n    long status = jedis.pfadd(\"hll\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"hll\", \"zap\", \"zap\", \"zap\");\n    assertEquals(0, status);\n\n    status = jedis.pfadd(\"hll\", \"foo\", \"bar\");\n    assertEquals(0, status);\n\n    status = jedis.pfcount(\"hll\");\n    assertEquals(3, status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pfcounts() {\n    long status = jedis.pfadd(\"hll_1\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n    status = jedis.pfadd(\"hll_2\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"hll_3\", \"foo\", \"bar\", \"baz\");\n    assertEquals(1, status);\n    status = jedis.pfcount(\"hll_1\");\n    assertEquals(3, status);\n    status = jedis.pfcount(\"hll_2\");\n    assertEquals(3, status);\n    status = jedis.pfcount(\"hll_3\");\n    assertEquals(3, status);\n\n    status = jedis.pfcount(\"hll_1\", \"hll_2\");\n    assertEquals(3, status);\n\n    status = jedis.pfcount(\"hll_1\", \"hll_2\", \"hll_3\");\n    assertEquals(4, status);\n\n  }\n\n  @Test\n  public void pfcountBinary() {\n    byte[] bHll = SafeEncoder.encode(\"hll\");\n    byte[] bFoo = SafeEncoder.encode(\"foo\");\n    byte[] bBar = SafeEncoder.encode(\"bar\");\n    byte[] bZap = SafeEncoder.encode(\"zap\");\n\n    long status = jedis.pfadd(bHll, bFoo, bBar, bZap);\n    assertEquals(1, status);\n\n    status = jedis.pfadd(bHll, bZap, bZap, bZap);\n    assertEquals(0, status);\n\n    status = jedis.pfadd(bHll, bFoo, bBar);\n    assertEquals(0, status);\n\n    status = jedis.pfcount(bHll);\n    assertEquals(3, status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pfmerge() {\n    long status = jedis.pfadd(\"hll1\", \"foo\", \"bar\", \"zap\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"hll2\", \"a\", \"b\", \"c\", \"foo\");\n    assertEquals(1, status);\n\n    String mergeStatus = jedis.pfmerge(\"hll3\", \"hll1\", \"hll2\");\n    assertEquals(\"OK\", mergeStatus);\n\n    status = jedis.pfcount(\"hll3\");\n    assertEquals(6, status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pfmergeBinary() {\n    byte[] bHll1 = SafeEncoder.encode(\"hll1\");\n    byte[] bHll2 = SafeEncoder.encode(\"hll2\");\n    byte[] bHll3 = SafeEncoder.encode(\"hll3\");\n    byte[] bFoo = SafeEncoder.encode(\"foo\");\n    byte[] bBar = SafeEncoder.encode(\"bar\");\n    byte[] bZap = SafeEncoder.encode(\"zap\");\n    byte[] bA = SafeEncoder.encode(\"a\");\n    byte[] bB = SafeEncoder.encode(\"b\");\n    byte[] bC = SafeEncoder.encode(\"c\");\n\n    long status = jedis.pfadd(bHll1, bFoo, bBar, bZap, bA);\n    assertEquals(1, status);\n\n    status = jedis.pfadd(bHll2, bA, bB, bC, bFoo);\n    assertEquals(1, status);\n\n    String mergeStatus = jedis.pfmerge(bHll3, bHll1, bHll2);\n    assertEquals(\"OK\", mergeStatus);\n\n    status = jedis.pfcount(bHll3);\n    assertEquals(6, status);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/JedisCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@Tag(\"integration\")\npublic abstract class JedisCommandsTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone0\"));\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone0\"));\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n\n  protected final RedisProtocol protocol;\n\n  protected Jedis jedis;\n\n  /**\n   * The RESP protocol is to be injected by the subclasses, usually via JUnit\n   * parameterized tests, because most of the subclassed tests are meant to be\n   * executed against multiple RESP versions. For the special cases where a single\n   * RESP version is relevant, we still force the subclass to be explicit and\n   * call this constructor.\n   *\n   * @param protocol The RESP protocol to use during the tests.\n   */\n  public JedisCommandsTestBase(RedisProtocol protocol) {\n    this.protocol = protocol;\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    jedis = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder()\n        .protocol(protocol).timeoutMillis(500).build());\n    jedis.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    jedis.close();\n  }\n\n  protected Jedis createJedis() {\n    return new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder()\n        .protocol(protocol).build());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ListCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ListCommandsTest extends JedisCommandsTestBase {\n\n  private final Logger logger = LoggerFactory.getLogger(getClass());\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x05 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] bA = { 0x0A };\n  final byte[] bB = { 0x0B };\n  final byte[] bC = { 0x0C };\n  final byte[] b1 = { 0x01 };\n  final byte[] b2 = { 0x02 };\n  final byte[] b3 = { 0x03 };\n  final byte[] bhello = { 0x04, 0x02 };\n  final byte[] bx = { 0x02, 0x04 };\n  final byte[] bdst = { 0x11, 0x12, 0x13, 0x14 };\n\n  public ListCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void rpush() {\n    assertEquals(1, jedis.rpush(\"foo\", \"bar\"));\n    assertEquals(2, jedis.rpush(\"foo\", \"foo\"));\n    assertEquals(4, jedis.rpush(\"foo\", \"bar\", \"foo\"));\n\n    // Binary\n    assertEquals(1, jedis.rpush(bfoo, bbar));\n    assertEquals(2, jedis.rpush(bfoo, bfoo));\n    assertEquals(4, jedis.rpush(bfoo, bbar, bfoo));\n  }\n\n  @Test\n  public void lpush() {\n    assertEquals(1, jedis.lpush(\"foo\", \"bar\"));\n    assertEquals(2, jedis.lpush(\"foo\", \"foo\"));\n    assertEquals(4, jedis.lpush(\"foo\", \"bar\", \"foo\"));\n\n    // Binary\n    assertEquals(1, jedis.lpush(bfoo, bbar));\n    assertEquals(2, jedis.lpush(bfoo, bfoo));\n    assertEquals(4, jedis.lpush(bfoo, bbar, bfoo));\n  }\n\n  @Test\n  public void llen() {\n    assertEquals(0, jedis.llen(\"foo\"));\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo\", \"car\");\n    assertEquals(2, jedis.llen(\"foo\"));\n\n    // Binary\n    assertEquals(0, jedis.llen(bfoo));\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo, bcar);\n    assertEquals(2, jedis.llen(bfoo));\n\n  }\n\n  @Test\n  public void llenNotOnList() {\n    try {\n      jedis.set(\"foo\", \"bar\");\n      jedis.llen(\"foo\");\n      fail(\"JedisDataException expected\");\n    } catch (final JedisDataException e) {\n    }\n\n    // Binary\n    try {\n      jedis.set(bfoo, bbar);\n      jedis.llen(bfoo);\n      fail(\"JedisDataException expected\");\n    } catch (final JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void lrange() {\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    List<String> range = jedis.lrange(\"foo\", 0, 2);\n    assertEquals(expected, range);\n\n    range = jedis.lrange(\"foo\", 0, 20);\n    assertEquals(expected, range);\n\n    expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    range = jedis.lrange(\"foo\", 1, 2);\n    assertEquals(expected, range);\n\n    range = jedis.lrange(\"foo\", 2, 1);\n    assertEquals(Collections.<String> emptyList(), range);\n\n    // Binary\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    bexpected.add(bC);\n\n    List<byte[]> brange = jedis.lrange(bfoo, 0, 2);\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.lrange(bfoo, 0, 20);\n    assertByteArrayListEquals(bexpected, brange);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bB);\n    bexpected.add(bC);\n\n    brange = jedis.lrange(bfoo, 1, 2);\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.lrange(bfoo, 2, 1);\n    assertByteArrayListEquals(Collections.<byte[]> emptyList(), brange);\n  }\n\n  @Test\n  public void ltrim() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n    String status = jedis.ltrim(\"foo\", 0, 1);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"3\");\n    expected.add(\"2\");\n\n    assertEquals(\"OK\", status);\n    assertEquals(2, jedis.llen(\"foo\"));\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n    String bstatus = jedis.ltrim(bfoo, 0, 1);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b3);\n    bexpected.add(b2);\n\n    assertEquals(\"OK\", bstatus);\n    assertEquals(2, jedis.llen(bfoo));\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void lset() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"3\");\n    expected.add(\"bar\");\n    expected.add(\"1\");\n\n    String status = jedis.lset(\"foo\", 1, \"bar\");\n\n    assertEquals(\"OK\", status);\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b3);\n    bexpected.add(bbar);\n    bexpected.add(b1);\n\n    String bstatus = jedis.lset(bfoo, 1, bbar);\n\n    assertEquals(\"OK\", bstatus);\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void lindex() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n\n    assertEquals(\"3\", jedis.lindex(\"foo\", 0));\n    assertNull(jedis.lindex(\"foo\", 100));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n\n    assertArrayEquals(b3, jedis.lindex(bfoo, 0));\n    assertNull(jedis.lindex(bfoo, 100));\n  }\n\n  @Test\n  public void lrem() {\n    jedis.lpush(\"foo\", \"hello\");\n    jedis.lpush(\"foo\", \"hello\");\n    jedis.lpush(\"foo\", \"x\");\n    jedis.lpush(\"foo\", \"hello\");\n    jedis.lpush(\"foo\", \"c\");\n    jedis.lpush(\"foo\", \"b\");\n    jedis.lpush(\"foo\", \"a\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n    expected.add(\"hello\");\n    expected.add(\"x\");\n\n    assertEquals(2, jedis.lrem(\"foo\", -2, \"hello\"));\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 1000));\n    assertEquals(0, jedis.lrem(\"bar\", 100, \"foo\"));\n\n    // Binary\n    jedis.lpush(bfoo, bhello);\n    jedis.lpush(bfoo, bhello);\n    jedis.lpush(bfoo, bx);\n    jedis.lpush(bfoo, bhello);\n    jedis.lpush(bfoo, bC);\n    jedis.lpush(bfoo, bB);\n    jedis.lpush(bfoo, bA);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    bexpected.add(bC);\n    bexpected.add(bhello);\n    bexpected.add(bx);\n\n    assertEquals(2, jedis.lrem(bfoo, -2, bhello));\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 1000));\n    assertEquals(0, jedis.lrem(bbar, 100, bfoo));\n\n  }\n\n  @Test\n  public void lpop() {\n\n    assertNull(jedis.lpop(\"foo\"));\n    assertNull(jedis.lpop(\"foo\", 0));\n\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    assertEquals(\"a\", jedis.lpop(\"foo\"));\n    assertEquals(Arrays.asList(\"b\", \"c\"), jedis.lpop(\"foo\", 10));\n\n    assertNull(jedis.lpop(\"foo\"));\n    assertNull(jedis.lpop(\"foo\", 1));\n\n    // Binary\n\n    assertNull(jedis.lpop(bfoo));\n    assertNull(jedis.lpop(bfoo, 0));\n\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    assertArrayEquals(bA, jedis.lpop(bfoo));\n    assertByteArrayListEquals(Arrays.asList(bB, bC), jedis.lpop(bfoo, 10));\n\n    assertNull(jedis.lpop(bfoo));\n    assertNull(jedis.lpop(bfoo, 1));\n\n  }\n\n  @Test\n  public void rpop() {\n\n    assertNull(jedis.rpop(\"foo\"));\n    assertNull(jedis.rpop(\"foo\", 0));\n\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    assertEquals(\"c\", jedis.rpop(\"foo\"));\n    assertEquals(Arrays.asList(\"b\", \"a\"), jedis.rpop(\"foo\", 10));\n\n    assertNull(jedis.rpop(\"foo\"));\n    assertNull(jedis.rpop(\"foo\", 1));\n\n    // Binary\n\n    assertNull(jedis.rpop(bfoo));\n    assertNull(jedis.rpop(bfoo, 0));\n\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    assertArrayEquals(bC, jedis.rpop(bfoo));\n    assertByteArrayListEquals(Arrays.asList(bB, bA), jedis.rpop(bfoo, 10));\n\n    assertNull(jedis.rpop(bfoo));\n    assertNull(jedis.rpop(bfoo, 1));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void rpoplpush() {\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    jedis.rpush(\"dst\", \"foo\");\n    jedis.rpush(\"dst\", \"bar\");\n\n    String element = jedis.rpoplpush(\"foo\", \"dst\");\n\n    assertEquals(\"c\", element);\n\n    List<String> srcExpected = new ArrayList<String>();\n    srcExpected.add(\"a\");\n    srcExpected.add(\"b\");\n\n    List<String> dstExpected = new ArrayList<String>();\n    dstExpected.add(\"c\");\n    dstExpected.add(\"foo\");\n    dstExpected.add(\"bar\");\n\n    assertEquals(srcExpected, jedis.lrange(\"foo\", 0, 1000));\n    assertEquals(dstExpected, jedis.lrange(\"dst\", 0, 1000));\n\n    // Binary\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    jedis.rpush(bdst, bfoo);\n    jedis.rpush(bdst, bbar);\n\n    byte[] belement = jedis.rpoplpush(bfoo, bdst);\n\n    assertArrayEquals(bC, belement);\n\n    List<byte[]> bsrcExpected = new ArrayList<byte[]>();\n    bsrcExpected.add(bA);\n    bsrcExpected.add(bB);\n\n    List<byte[]> bdstExpected = new ArrayList<byte[]>();\n    bdstExpected.add(bC);\n    bdstExpected.add(bfoo);\n    bdstExpected.add(bbar);\n\n    assertByteArrayListEquals(bsrcExpected, jedis.lrange(bfoo, 0, 1000));\n    assertByteArrayListEquals(bdstExpected, jedis.lrange(bdst, 0, 1000));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blpop() throws InterruptedException {\n    List<String> result = jedis.blpop(1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.blpop(1, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo\", result.get(0));\n    assertEquals(\"bar\", result.get(1));\n\n    // Multi keys\n    result = jedis.blpop(1, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.blpop(1, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo1\", result.get(0));\n    assertEquals(\"bar1\", result.get(1));\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    List<byte[]> bresult = jedis.blpop(1, bfoo);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n\n    // Binary Multi keys\n    bresult = jedis.blpop(1, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.blpop(1, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blpopDouble() throws InterruptedException {\n    KeyValue<String, String> result = jedis.blpop(0.1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.blpop(3.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n\n    // Multi keys\n    result = jedis.blpop(0.18, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.blpop(1d, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo1\", result.getKey());\n    assertEquals(\"bar1\", result.getValue());\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    KeyValue<byte[], byte[]> bresult = jedis.blpop(3.12, bfoo);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n\n    // Binary Multi keys\n    bresult = jedis.blpop(0.11, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.blpop(1d, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n  }\n\n  @Test\n  @Timeout(5)\n  public void blpopDoubleWithSleep() {\n    KeyValue<String, String> result = jedis.blpop(0.04, \"foo\");\n    assertNull(result);\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(30);\n      } catch(InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      try (Jedis j = createJedis()) {\n        j.lpush(\"foo\", \"bar\");\n      }\n    }).start();\n    result = jedis.blpop(1.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpop() throws InterruptedException {\n    List<String> result = jedis.brpop(1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.brpop(1, \"foo\");\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo\", result.get(0));\n    assertEquals(\"bar\", result.get(1));\n\n    // Multi keys\n    result = jedis.brpop(1, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.brpop(1, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo1\", result.get(0));\n    assertEquals(\"bar1\", result.get(1));\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    List<byte[]> bresult = jedis.brpop(1, bfoo);\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n\n    // Binary Multi keys\n    bresult = jedis.brpop(1, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.brpop(1, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpopDouble() throws InterruptedException {\n    KeyValue<String, String> result = jedis.brpop(0.1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.brpop(3.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n\n    // Multi keys\n    result = jedis.brpop(0.18, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.brpop(1d, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo1\", result.getKey());\n    assertEquals(\"bar1\", result.getValue());\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    KeyValue<byte[], byte[]> bresult = jedis.brpop(3.12, bfoo);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n\n    // Binary Multi keys\n    bresult = jedis.brpop(0.11, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.brpop(1d, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n  }\n\n  @Test\n  @Timeout(5)\n  public void brpopDoubleWithSleep() {\n    KeyValue<String, String> result = jedis.brpop(0.04, \"foo\");\n    assertNull(result);\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(30);\n      } catch(InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      try (Jedis j = createJedis()) {\n        j.lpush(\"foo\", \"bar\");\n      }\n    }).start();\n    result = jedis.brpop(1.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n  }\n\n  @Test\n  public void lpushx() {\n    assertEquals(0, jedis.lpushx(\"foo\", \"bar\"));\n\n    jedis.lpush(\"foo\", \"a\");\n    assertEquals(2, jedis.lpushx(\"foo\", \"b\"));\n\n    // Binary\n    assertEquals(0, jedis.lpushx(bfoo, bbar));\n\n    jedis.lpush(bfoo, bA);\n    assertEquals(2, jedis.lpushx(bfoo, bB));\n  }\n\n  @Test\n  public void rpushx() {\n    assertEquals(0, jedis.rpushx(\"foo\", \"bar\"));\n\n    jedis.lpush(\"foo\", \"a\");\n    assertEquals(2, jedis.rpushx(\"foo\", \"b\"));\n\n    // Binary\n    assertEquals(0, jedis.rpushx(bfoo, bbar));\n\n    jedis.lpush(bfoo, bA);\n    assertEquals(2, jedis.rpushx(bfoo, bB));\n  }\n\n  @Test\n  public void linsert() {\n    assertEquals(0, jedis.linsert(\"foo\", ListPosition.BEFORE, \"bar\", \"car\"));\n\n    jedis.lpush(\"foo\", \"a\");\n    assertEquals(2, jedis.linsert(\"foo\", ListPosition.AFTER, \"a\", \"b\"));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 100));\n\n    assertEquals(-1, jedis.linsert(\"foo\", ListPosition.BEFORE, \"bar\", \"car\"));\n\n    // Binary\n    assertEquals(0, jedis.linsert(bfoo, ListPosition.BEFORE, bbar, bcar));\n\n    jedis.lpush(bfoo, bA);\n    assertEquals(2, jedis.linsert(bfoo, ListPosition.AFTER, bA, bB));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 100));\n\n    assertEquals(-1, jedis.linsert(bfoo, ListPosition.BEFORE, bbar, bcar));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpoplpush() {\n\n    new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          Thread.sleep(100);\n        } catch (InterruptedException e) {\n          logger.error(\"\", e);\n        }\n        try (Jedis j = createJedis()) {\n          j.lpush(\"foo\", \"a\");\n        }\n      }\n    }).start();\n\n    String element = jedis.brpoplpush(\"foo\", \"bar\", 0);\n\n    assertEquals(\"a\", element);\n    assertEquals(1, jedis.llen(\"bar\"));\n    assertEquals(\"a\", jedis.lrange(\"bar\", 0, -1).get(0));\n\n    // Binary\n\n    new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          Thread.sleep(100);\n        } catch (InterruptedException e) {\n          logger.error(\"\", e);\n        }\n        try (Jedis j = createJedis()) {\n          j.lpush(bfoo, bA);\n        }\n      }\n    }).start();\n\n    byte[] belement = jedis.brpoplpush(bfoo, bbar, 0);\n\n    assertArrayEquals(bA, belement);\n    assertEquals(1, jedis.llen(\"bar\"));\n    assertArrayEquals(bA, jedis.lrange(bbar, 0, -1).get(0));\n  }\n\n  @Test\n  public void lpos() {\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    Long pos = jedis.lpos(\"foo\", \"b\");\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"d\");\n    assertNull(pos);\n\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"b\");\n\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams());\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(3));\n    assertEquals(5, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2));\n    assertEquals(4, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-5));\n    assertNull(pos);\n\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(1).maxlen(2));\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2).maxlen(2));\n    assertNull(pos);\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2).maxlen(2));\n    assertEquals(4, pos.intValue());\n\n    List<Long> expected = new ArrayList<Long>();\n    expected.add(1L);\n    expected.add(4L);\n    expected.add(5L);\n    List<Long> posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams(), 2);\n    assertEquals(expected.subList(0, 2), posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams(), 0);\n    assertEquals(expected, posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2), 0);\n    assertEquals(expected.subList(1, 3), posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2).maxlen(5), 0);\n    assertEquals(expected.subList(1, 2), posList);\n\n    Collections.reverse(expected);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2), 0);\n    assertEquals(expected.subList(1, 3), posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-1).maxlen(5), 2);\n    assertEquals(expected.subList(0, 2), posList);\n\n    // Binary\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    pos = jedis.lpos(bfoo, bB);\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(bfoo, b3);\n    assertNull(pos);\n\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bA);\n\n    pos = jedis.lpos(bfoo, bB, LPosParams.lPosParams().rank(2));\n    assertEquals(4, pos.intValue());\n    pos = jedis.lpos(bfoo, bB, LPosParams.lPosParams().rank(-2).maxlen(5));\n    assertEquals(1, pos.intValue());\n\n    expected.clear();\n    expected.add(0L);\n    expected.add(3L);\n    expected.add(5L);\n\n    posList = jedis.lpos(bfoo, bA, LPosParams.lPosParams().maxlen(6), 0);\n    assertEquals(expected, posList);\n    posList = jedis.lpos(bfoo, bA, LPosParams.lPosParams().maxlen(6).rank(2), 1);\n    assertEquals(expected.subList(1, 2), posList);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lmove() {\n    jedis.rpush(\"foo\", \"bar1\", \"bar2\", \"bar3\");\n    assertEquals(\"bar3\", jedis.lmove(\"foo\", \"bar\", ListDirection.RIGHT, ListDirection.LEFT));\n    assertEquals(Collections.singletonList(\"bar3\"), jedis.lrange(\"bar\", 0, -1));\n    assertEquals(Arrays.asList(\"bar1\", \"bar2\"), jedis.lrange(\"foo\", 0, -1));\n\n    // Binary\n    jedis.rpush(bfoo, b1, b2, b3);\n    assertArrayEquals(b3, jedis.lmove(bfoo, bbar, ListDirection.RIGHT, ListDirection.LEFT));\n    assertByteArrayListEquals(Collections.singletonList(b3), jedis.lrange(bbar, 0, -1));\n    assertByteArrayListEquals(Arrays.asList(b1, b2), jedis.lrange(bfoo, 0, -1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blmove() {\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      try (Jedis j = createJedis()) {\n        j.rpush(\"foo\", \"bar1\", \"bar2\", \"bar3\");\n      }\n    }).start();\n\n    assertEquals(\"bar3\", jedis.blmove(\"foo\", \"bar\", ListDirection.RIGHT, ListDirection.LEFT, 0));\n    assertEquals(Collections.singletonList(\"bar3\"), jedis.lrange(\"bar\", 0, -1));\n    assertEquals(Arrays.asList(\"bar1\", \"bar2\"), jedis.lrange(\"foo\", 0, -1));\n\n    // Binary\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      try (Jedis j = createJedis()) {\n        j.rpush(bfoo, b1, b2, b3);\n      }\n    }).start();\n    assertArrayEquals(b3, jedis.blmove(bfoo, bbar, ListDirection.RIGHT, ListDirection.LEFT, 0));\n    assertByteArrayListEquals(Collections.singletonList(b3), jedis.lrange(bbar, 0, -1));\n    assertByteArrayListEquals(Arrays.asList(b1, b2), jedis.lrange(bfoo, 0, -1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lmpop() {\n    String mylist1 = \"mylist1\";\n    String mylist2 = \"mylist2\";\n\n    // add elements to list\n    jedis.lpush(mylist1, \"one\", \"two\", \"three\", \"four\", \"five\");\n    jedis.lpush(mylist2, \"one\", \"two\", \"three\", \"four\", \"five\");\n\n    KeyValue<String, List<String>> elements = jedis.lmpop(ListDirection.LEFT, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(1, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.LEFT, 5, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(4, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.RIGHT, 100, mylist1, mylist2);\n    assertEquals(mylist2, elements.getKey());\n    assertEquals(5, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.RIGHT, mylist1, mylist2);\n    assertNull(elements);\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blmpopSimple() {\n    String mylist1 = \"mylist1\";\n    String mylist2 = \"mylist2\";\n\n    // add elements to list\n    jedis.lpush(mylist1, \"one\", \"two\", \"three\", \"four\", \"five\");\n    jedis.lpush(mylist2, \"one\", \"two\", \"three\", \"four\", \"five\");\n\n    KeyValue<String, List<String>> elements = jedis.blmpop(1L, ListDirection.LEFT, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(1, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.LEFT, 5, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(4, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.RIGHT, 100, mylist1, mylist2);\n    assertEquals(mylist2, elements.getKey());\n    assertEquals(5, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.RIGHT, mylist1, mylist2);\n    assertNull(elements);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/MigrateTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\n\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class MigrateTest extends JedisCommandsTestBase {\n\n  private static final byte[] bfoo = { 0x01, 0x02, 0x03 };\n  private static final byte[] bbar = { 0x04, 0x05, 0x06 };\n  private static final byte[] bfoo1 = { 0x07, 0x08, 0x01 };\n  private static final byte[] bbar1 = { 0x09, 0x00, 0x01 };\n  private static final byte[] bfoo2 = { 0x07, 0x08, 0x02 };\n  private static final byte[] bbar2 = { 0x09, 0x00, 0x02 };\n  private static final byte[] bfoo3 = { 0x07, 0x08, 0x03 };\n  private static final byte[] bbar3 = { 0x09, 0x00, 0x03 };\n\n  private Jedis dest;\n  private Jedis destAuth;\n\n  private static EndpointConfig destEndpoint;\n\n  private static EndpointConfig destEndpointWithAuth;\n\n  private static String host;\n  private static int port;\n  private static int portAuth;\n  private static final int db = 2;\n  private static final int dbAuth = 3;\n  private static final int timeout = Protocol.DEFAULT_TIMEOUT;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    destEndpoint = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n    destEndpointWithAuth = Endpoints.getRedisEndpoint(\"standalone1\");\n    host = destEndpoint.getHost();\n    port = destEndpoint.getPort();\n    portAuth = destEndpointWithAuth.getPort();\n  }\n\n  public MigrateTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n\n    dest = new Jedis(host, port, 500);\n    dest.flushAll();\n    dest.select(db);\n\n    destAuth = new Jedis(destEndpointWithAuth.getHostAndPort(),\n        destEndpointWithAuth.getClientConfigBuilder().build());\n    destAuth.flushAll();\n    destAuth.select(dbAuth);\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    dest.close();\n    destAuth.close();\n    super.tearDown();\n  }\n\n  @Test\n  public void nokey() {\n    assertEquals(\"NOKEY\", jedis.migrate(host, port, \"foo\", db, timeout));\n    assertEquals(\"NOKEY\", jedis.migrate(host, port, bfoo, db, timeout));\n    assertEquals(\"NOKEY\",\n      jedis.migrate(host, port, db, timeout, new MigrateParams(), \"foo1\", \"foo2\", \"foo3\"));\n    assertEquals(\"NOKEY\",\n      jedis.migrate(host, port, db, timeout, new MigrateParams(), bfoo1, bfoo2, bfoo3));\n  }\n\n  @Test\n  public void migrate() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", jedis.migrate(host, port, \"foo\", db, timeout));\n    assertEquals(\"bar\", dest.get(\"foo\"));\n    assertNull(jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(\"OK\", jedis.migrate(host, port, bfoo, db, timeout));\n    assertArrayEquals(bbar, dest.get(bfoo));\n    assertNull(jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateEmptyParams() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", jedis.migrate(host, port, db, timeout, new MigrateParams(), \"foo\"));\n    assertEquals(\"bar\", dest.get(\"foo\"));\n    assertNull(jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(\"OK\", jedis.migrate(host, port, db, timeout, new MigrateParams(), bfoo));\n    assertArrayEquals(bbar, dest.get(bfoo));\n    assertNull(jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateCopy() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", jedis.migrate(host, port, db, timeout, new MigrateParams().copy(), \"foo\"));\n    assertEquals(\"bar\", dest.get(\"foo\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(\"OK\", jedis.migrate(host, port, db, timeout, new MigrateParams().copy(), bfoo));\n    assertArrayEquals(bbar, dest.get(bfoo));\n    assertArrayEquals(bbar, jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateReplace() {\n    jedis.set(\"foo\", \"bar1\");\n    dest.set(\"foo\", \"bar2\");\n    assertEquals(\"OK\", jedis.migrate(host, port, db, timeout, new MigrateParams().replace(), \"foo\"));\n    assertEquals(\"bar1\", dest.get(\"foo\"));\n    assertNull(jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar1);\n    dest.set(bfoo, bbar2);\n    assertEquals(\"OK\", jedis.migrate(host, port, db, timeout, new MigrateParams().replace(), bfoo));\n    assertArrayEquals(bbar1, dest.get(bfoo));\n    assertNull(jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateCopyReplace() {\n    jedis.set(\"foo\", \"bar1\");\n    dest.set(\"foo\", \"bar2\");\n    assertEquals(\"OK\",\n      jedis.migrate(host, port, db, timeout, new MigrateParams().copy().replace(), \"foo\"));\n    assertEquals(\"bar1\", dest.get(\"foo\"));\n    assertEquals(\"bar1\", jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar1);\n    dest.set(bfoo, bbar2);\n    assertEquals(\"OK\",\n      jedis.migrate(host, port, db, timeout, new MigrateParams().copy().replace(), bfoo));\n    assertArrayEquals(bbar1, dest.get(bfoo));\n    assertArrayEquals(bbar1, jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateAuth() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", jedis.migrate(host, portAuth, dbAuth, timeout,\n        new MigrateParams().auth(destEndpointWithAuth.getPassword()), \"foo\"));\n    assertEquals(\"bar\", destAuth.get(\"foo\"));\n    assertNull(jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(\"OK\", jedis.migrate(host, portAuth, dbAuth, timeout,\n        new MigrateParams().auth(destEndpointWithAuth.getPassword()), bfoo));\n    assertArrayEquals(bbar, destAuth.get(bfoo));\n    assertNull(jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateAuth2() {\n    destAuth.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", destAuth.migrate(host, endpoint.getPort(), 0, timeout,\n        new MigrateParams().auth2(endpoint.getUsername(), endpoint.getPassword()), \"foo\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertNull(destAuth.get(\"foo\"));\n\n    // binary\n    dest.set(bfoo1, bbar1);\n    assertEquals(\"OK\", dest.migrate(host, endpoint.getPort(), 0, timeout,\n        new MigrateParams().auth2(endpoint.getUsername(), endpoint.getPassword()), bfoo1));\n    assertArrayEquals(bbar1, jedis.get(bfoo1));\n    assertNull(dest.get(bfoo1));\n  }\n\n  @Test\n  public void migrateCopyReplaceAuth() {\n    jedis.set(\"foo\", \"bar1\");\n    destAuth.set(\"foo\", \"bar2\");\n    assertEquals(\"OK\", jedis.migrate(host, portAuth, dbAuth, timeout,\n        new MigrateParams().copy().replace().auth(destEndpointWithAuth.getPassword()), \"foo\"));\n    assertEquals(\"bar1\", destAuth.get(\"foo\"));\n    assertEquals(\"bar1\", jedis.get(\"foo\"));\n\n    jedis.set(bfoo, bbar1);\n    destAuth.set(bfoo, bbar2);\n    assertEquals(\n      \"OK\",\n      jedis.migrate(host, portAuth, dbAuth, timeout,\n        new MigrateParams().copy().replace().auth(destEndpointWithAuth.getPassword()), bfoo));\n    assertArrayEquals(bbar1, destAuth.get(bfoo));\n    assertArrayEquals(bbar1, jedis.get(bfoo));\n  }\n\n  @Test\n  public void migrateMulti() {\n    jedis.mset(\"foo1\", \"bar1\", \"foo2\", \"bar2\", \"foo3\", \"bar3\");\n    assertEquals(\"OK\",\n      jedis.migrate(host, port, db, timeout, new MigrateParams(), \"foo1\", \"foo2\", \"foo3\"));\n    assertEquals(\"bar1\", dest.get(\"foo1\"));\n    assertEquals(\"bar2\", dest.get(\"foo2\"));\n    assertEquals(\"bar3\", dest.get(\"foo3\"));\n\n    jedis.mset(bfoo1, bbar1, bfoo2, bbar2, bfoo3, bbar3);\n    assertEquals(\"OK\",\n      jedis.migrate(host, port, db, timeout, new MigrateParams(), bfoo1, bfoo2, bfoo3));\n    assertArrayEquals(bbar1, dest.get(bfoo1));\n    assertArrayEquals(bbar2, dest.get(bfoo2));\n    assertArrayEquals(bbar3, dest.get(bfoo3));\n  }\n\n  @Test\n  public void migrateConflict() {\n    jedis.mset(\"foo1\", \"bar1\", \"foo2\", \"bar2\", \"foo3\", \"bar3\");\n    dest.set(\"foo2\", \"bar\");\n    try {\n      jedis.migrate(host, port, db, timeout, new MigrateParams(), \"foo1\", \"foo2\", \"foo3\");\n      fail(\"Should get BUSYKEY error\");\n    } catch (JedisDataException jde) {\n      assertTrue(jde.getMessage().contains(\"BUSYKEY\"));\n    }\n    assertEquals(\"bar1\", dest.get(\"foo1\"));\n    assertEquals(\"bar\", dest.get(\"foo2\"));\n    assertEquals(\"bar3\", dest.get(\"foo3\"));\n\n    jedis.mset(bfoo1, bbar1, bfoo2, bbar2, bfoo3, bbar3);\n    dest.set(bfoo2, bbar);\n    try {\n      jedis.migrate(host, port, db, timeout, new MigrateParams(), bfoo1, bfoo2, bfoo3);\n      fail(\"Should get BUSYKEY error\");\n    } catch (JedisDataException jde) {\n      assertTrue(jde.getMessage().contains(\"BUSYKEY\"));\n    }\n    assertArrayEquals(bbar1, dest.get(bfoo1));\n    assertArrayEquals(bbar, dest.get(bfoo2));\n    assertArrayEquals(bbar3, dest.get(bfoo3));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ModuleTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport redis.clients.jedis.Module;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.hamcrest.Matchers.hasProperty;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = true)\npublic class ModuleTest extends JedisCommandsTestBase {\n\n  enum ModuleCommand implements ProtocolCommand {\n\n    SIMPLE(\"testmodule.simple\");\n\n    private final byte[] raw;\n\n    ModuleCommand(String alt) {\n      raw = SafeEncoder.encode(alt);\n    }\n\n    @Override\n    public byte[] getRaw() {\n      return raw;\n    }\n  }\n\n  public ModuleTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testModules() {\n    try {\n      assertEquals(\"OK\", jedis.moduleLoad(TestEnvUtil.testModuleSoPath()));\n\n      List<Module> modules = jedis.moduleList();\n\n      assertThat(modules, hasItem(hasProperty(\"name\", equalTo(\"testmodule\"))));\n\n      Object output = jedis.sendCommand(ModuleCommand.SIMPLE);\n      assertTrue((Long) output > 0);\n\n    } finally {\n\n      assertEquals(\"OK\", jedis.moduleUnload(\"testmodule\"));\n      List<Module> modules = jedis.moduleList();\n      assertThat(modules, not(hasItem(hasProperty(\"name\", equalTo(\"testmodule\")))));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ObjectCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.SafeEncoder;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ObjectCommandsTest extends JedisCommandsTestBase {\n\n  private final String key = \"mylist\";\n  private final byte[] binaryKey = SafeEncoder.encode(key);\n  private static EndpointConfig lfuEndpoint;\n  private Jedis lfuJedis;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    lfuEndpoint = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n  }\n\n  public ObjectCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n\n    lfuJedis = new Jedis(lfuEndpoint.getHostAndPort(),\n        lfuEndpoint.getClientConfigBuilder().build());\n    lfuJedis.connect();\n    lfuJedis.flushAll();\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    lfuJedis.disconnect();\n    super.tearDown();\n  }\n\n  @Test\n  public void objectRefcount() {\n    jedis.lpush(key, \"hello world\");\n    Long refcount = jedis.objectRefcount(key);\n    assertEquals(Long.valueOf(1), refcount);\n\n    // Binary\n    refcount = jedis.objectRefcount(binaryKey);\n    assertEquals(Long.valueOf(1), refcount);\n\n  }\n\n  @Test\n  public void objectEncodingString() {\n    jedis.set(key, \"hello world\");\n    assertThat(jedis.objectEncoding(key), containsString(\"str\"));\n\n    // Binary\n    assertThat(SafeEncoder.encode(jedis.objectEncoding(binaryKey)), containsString(\"str\"));\n  }\n\n  @Test\n  public void objectEncodingList() {\n    jedis.lpush(key, \"hello world\");\n    assertThat(jedis.objectEncoding(key), containsString(\"list\"));\n\n    // Binary\n    assertThat(SafeEncoder.encode(jedis.objectEncoding(binaryKey)), containsString(\"list\"));\n  }\n\n  @Test\n  public void objectIdletime() throws InterruptedException {\n    jedis.lpush(key, \"hello world\");\n\n    Long time = jedis.objectIdletime(key);\n    assertThat(time, lessThanOrEqualTo(10L));\n\n    // Binary\n    time = jedis.objectIdletime(binaryKey);\n    assertThat(time, lessThanOrEqualTo(10L));\n  }\n\n  @Test\n  public void objectHelp() {\n    // String\n    List<String> helpTexts = jedis.objectHelp();\n    assertNotNull(helpTexts);\n\n    // Binary\n    List<byte[]> helpBinaryTexts = jedis.objectHelpBinary();\n    assertNotNull(helpBinaryTexts);\n  }\n\n  @Test\n  public void objectFreq() {\n    lfuJedis.set(key, \"test1\");\n    lfuJedis.get(key);\n    // String\n    assertThat(lfuJedis.objectFreq(key), greaterThanOrEqualTo(1L));\n    // Binary\n    assertThat(lfuJedis.objectFreq(binaryKey), greaterThanOrEqualTo(1L));\n\n    assertNull(lfuJedis.objectFreq(\"no_such_key\"));\n\n    jedis.set(key, \"test2\");\n    assertThrows(JedisDataException.class, () -> jedis.objectFreq(key), \"Freq is only allowed with LFU policy\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/PublishSubscribeCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static redis.clients.jedis.Protocol.Command.CLIENT;\n\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.BinaryJedisPubSub;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@Timeout(value = 5, unit = TimeUnit.MINUTES)\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class PublishSubscribeCommandsTest extends JedisCommandsTestBase {\n\n  public PublishSubscribeCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  private void publishOne(final String channel, final String message) {\n    Thread t = new Thread(new Runnable() {\n      public void run() {\n        try ( Jedis j = createJedis()) {\n          j.publish(channel, message);\n          j.disconnect();\n        } catch (Exception ex) {\n          // ignore\n        }\n      }\n    });\n    t.start();\n  }\n\n  @Test\n  public void subscribe() throws InterruptedException {\n    jedis.subscribe(new JedisPubSub() {\n      public void onMessage(String channel, String message) {\n        assertEquals(\"foo\", channel);\n        assertEquals(\"exit\", message);\n        unsubscribe();\n      }\n\n      public void onSubscribe(String channel, int subscribedChannels) {\n        assertEquals(\"foo\", channel);\n        assertEquals(1, subscribedChannels);\n\n        // now that I'm subscribed... publish\n        publishOne(\"foo\", \"exit\");\n      }\n\n      public void onUnsubscribe(String channel, int subscribedChannels) {\n        assertEquals(\"foo\", channel);\n        assertEquals(0, subscribedChannels);\n      }\n    }, \"foo\");\n  }\n\n  @Test\n  public void pubSubChannels() {\n    jedis.subscribe(new JedisPubSub() {\n      private int count = 0;\n\n      @Override\n      public void onSubscribe(String channel, int subscribedChannels) {\n        count++;\n        // All channels are subscribed\n        if (count == 3) {\n          List<String> activeChannels;\n          try (Jedis otherJedis = createJedis()) {\n            activeChannels = otherJedis.pubsubChannels();\n          }\n          // Since we are utilizing sentinel for the tests, there is an additional\n          // '__sentinel__:hello' channel that has subscribers and will be returned\n          // from PUBSUB CHANNELS.\n          assertThat(activeChannels, hasItems(\"testchan1\", \"testchan2\", \"testchan3\"));\n          unsubscribe();\n        }\n      }\n    }, \"testchan1\", \"testchan2\", \"testchan3\");\n  }\n\n  @Test\n  public void pubSubChannelsWithPattern() {\n    jedis.subscribe(new JedisPubSub() {\n      private int count = 0;\n\n      @Override\n      public void onSubscribe(String channel, int subscribedChannels) {\n        count++;\n        // All channels are subscribed\n        if (count == 3) {\n          List<String> activeChannels;\n          try (Jedis otherJedis = createJedis()) {\n            activeChannels = otherJedis.pubsubChannels(\"test*\");\n          }\n          assertThat(activeChannels, hasItems(\"testchan1\", \"testchan2\", \"testchan3\"));\n          unsubscribe();\n        }\n      }\n    }, \"testchan1\", \"testchan2\", \"testchan3\");\n  }\n\n  @Test\n  public void pubSubChannelWithPingPong() throws InterruptedException {\n    final CountDownLatch latchUnsubscribed = new CountDownLatch(1);\n    final CountDownLatch latchReceivedPong = new CountDownLatch(1);\n    jedis.subscribe(new JedisPubSub() {\n\n      @Override\n      public void onSubscribe(String channel, int subscribedChannels) {\n        publishOne(\"testchan1\", \"hello\");\n      }\n\n      @Override\n      public void onMessage(String channel, String message) {\n        this.ping();\n      }\n\n      @Override\n      public void onPong(String pattern) {\n        latchReceivedPong.countDown();\n        unsubscribe();\n      }\n\n      @Override\n      public void onUnsubscribe(String channel, int subscribedChannels) {\n        latchUnsubscribed.countDown();\n      }\n    }, \"testchan1\");\n    assertEquals(0L, latchReceivedPong.getCount());\n    assertEquals(0L, latchUnsubscribed.getCount());\n  }\n\n  @Test\n  public void pubSubChannelWithPingPongWithArgument() throws InterruptedException {\n    final CountDownLatch latchUnsubscribed = new CountDownLatch(1);\n    final CountDownLatch latchReceivedPong = new CountDownLatch(1);\n    final List<String> pongPatterns = new ArrayList<>();\n    jedis.subscribe(new JedisPubSub() {\n\n      @Override\n      public void onSubscribe(String channel, int subscribedChannels) {\n        publishOne(\"testchan1\", \"hello\");\n      }\n\n      @Override\n      public void onMessage(String channel, String message) {\n        this.ping(\"hi!\");\n      }\n\n      @Override\n      public void onPong(String pattern) {\n        pongPatterns.add(pattern);\n        latchReceivedPong.countDown();\n        unsubscribe();\n      }\n\n      @Override\n      public void onUnsubscribe(String channel, int subscribedChannels) {\n        latchUnsubscribed.countDown();\n      }\n    }, \"testchan1\");\n\n    assertEquals(0L, latchReceivedPong.getCount());\n    assertEquals(0L, latchUnsubscribed.getCount());\n    assertEquals(Collections.singletonList(\"hi!\"), pongPatterns);\n  }\n\n  @Test\n  public void pubSubNumPat() {\n    jedis.psubscribe(new JedisPubSub() {\n      private int count = 0;\n\n      @Override\n      public void onPSubscribe(String pattern, int subscribedChannels) {\n        count++;\n        if (count == 3) {\n          Long numPatterns;\n          try (Jedis otherJedis = createJedis()) {\n            numPatterns = otherJedis.pubsubNumPat();\n          }\n          assertEquals(Long.valueOf(2L), numPatterns);\n          punsubscribe();\n        }\n      }\n\n    }, \"test*\", \"test*\", \"chan*\");\n  }\n\n  @Test\n  public void pubSubNumSub() {\n    final Map<String, Long> expectedNumSub = new HashMap<>();\n    expectedNumSub.put(\"testchannel2\", 1L);\n    expectedNumSub.put(\"testchannel1\", 1L);\n    jedis.subscribe(new JedisPubSub() {\n      private int count = 0;\n\n      @Override\n      public void onSubscribe(String channel, int subscribedChannels) {\n        count++;\n        if (count == 2) {\n          Map<String, Long> numSub;\n          try (Jedis otherJedis = createJedis()) {\n            numSub = otherJedis.pubsubNumSub(\"testchannel1\", \"testchannel2\");\n          }\n          assertEquals(expectedNumSub, numSub);\n          unsubscribe();\n        }\n      }\n    }, \"testchannel1\", \"testchannel2\");\n  }\n\n  @Test\n  public void subscribeMany() throws UnknownHostException, IOException, InterruptedException {\n    jedis.subscribe(new JedisPubSub() {\n      public void onMessage(String channel, String message) {\n        unsubscribe(channel);\n      }\n\n      public void onSubscribe(String channel, int subscribedChannels) {\n        publishOne(channel, \"exit\");\n      }\n\n    }, \"foo\", \"bar\");\n  }\n\n  @Test\n  public void psubscribe() throws UnknownHostException, IOException, InterruptedException {\n    jedis.psubscribe(new JedisPubSub() {\n      public void onPSubscribe(String pattern, int subscribedChannels) {\n        assertEquals(\"foo.*\", pattern);\n        assertEquals(1, subscribedChannels);\n        publishOne(\"foo.bar\", \"exit\");\n\n      }\n\n      public void onPUnsubscribe(String pattern, int subscribedChannels) {\n        assertEquals(\"foo.*\", pattern);\n        assertEquals(0, subscribedChannels);\n      }\n\n      public void onPMessage(String pattern, String channel, String message) {\n        assertEquals(\"foo.*\", pattern);\n        assertEquals(\"foo.bar\", channel);\n        assertEquals(\"exit\", message);\n        punsubscribe();\n      }\n    }, \"foo.*\");\n  }\n\n  @Test\n  public void psubscribeMany() throws UnknownHostException, IOException, InterruptedException {\n    jedis.psubscribe(new JedisPubSub() {\n      public void onPSubscribe(String pattern, int subscribedChannels) {\n        publishOne(pattern.replace(\"*\", \"123\"), \"exit\");\n      }\n\n      public void onPMessage(String pattern, String channel, String message) {\n        punsubscribe(pattern);\n      }\n    }, \"foo.*\", \"bar.*\");\n  }\n\n  @Test\n  public void subscribeLazily() throws UnknownHostException, IOException, InterruptedException {\n    final JedisPubSub pubsub = new JedisPubSub() {\n      public void onMessage(String channel, String message) {\n        unsubscribe(channel);\n      }\n\n      public void onSubscribe(String channel, int subscribedChannels) {\n        publishOne(channel, \"exit\");\n        if (!channel.equals(\"bar\")) {\n          this.subscribe(\"bar\");\n          this.psubscribe(\"bar.*\");\n        }\n      }\n\n      public void onPSubscribe(String pattern, int subscribedChannels) {\n        publishOne(pattern.replace(\"*\", \"123\"), \"exit\");\n      }\n\n      public void onPMessage(String pattern, String channel, String message) {\n        punsubscribe(pattern);\n      }\n    };\n\n    jedis.subscribe(pubsub, \"foo\");\n  }\n\n  @Test\n  public void binarySubscribe() throws UnknownHostException, IOException, InterruptedException {\n    jedis.subscribe(new BinaryJedisPubSub() {\n      public void onMessage(byte[] channel, byte[] message) {\n        assertArrayEquals(SafeEncoder.encode(\"foo\"), channel);\n        assertArrayEquals(SafeEncoder.encode(\"exit\"), message);\n        unsubscribe();\n      }\n\n      public void onSubscribe(byte[] channel, int subscribedChannels) {\n        assertArrayEquals(SafeEncoder.encode(\"foo\"), channel);\n        assertEquals(1, subscribedChannels);\n        publishOne(SafeEncoder.encode(channel), \"exit\");\n      }\n\n      public void onUnsubscribe(byte[] channel, int subscribedChannels) {\n        assertArrayEquals(SafeEncoder.encode(\"foo\"), channel);\n        assertEquals(0, subscribedChannels);\n      }\n    }, SafeEncoder.encode(\"foo\"));\n  }\n\n  @Test\n  public void binarySubscribeMany() throws UnknownHostException, IOException, InterruptedException {\n    jedis.subscribe(new BinaryJedisPubSub() {\n      public void onMessage(byte[] channel, byte[] message) {\n        unsubscribe(channel);\n      }\n\n      public void onSubscribe(byte[] channel, int subscribedChannels) {\n        publishOne(SafeEncoder.encode(channel), \"exit\");\n      }\n    }, SafeEncoder.encode(\"foo\"), SafeEncoder.encode(\"bar\"));\n  }\n\n  @Test\n  public void binaryPsubscribe() throws UnknownHostException, IOException, InterruptedException {\n    jedis.psubscribe(new BinaryJedisPubSub() {\n      public void onPSubscribe(byte[] pattern, int subscribedChannels) {\n        assertArrayEquals(SafeEncoder.encode(\"foo.*\"), pattern);\n        assertEquals(1, subscribedChannels);\n        publishOne(SafeEncoder.encode(pattern).replace(\"*\", \"bar\"), \"exit\");\n      }\n\n      public void onPUnsubscribe(byte[] pattern, int subscribedChannels) {\n        assertArrayEquals(SafeEncoder.encode(\"foo.*\"), pattern);\n        assertEquals(0, subscribedChannels);\n      }\n\n      public void onPMessage(byte[] pattern, byte[] channel, byte[] message) {\n        assertArrayEquals(SafeEncoder.encode(\"foo.*\"), pattern);\n        assertArrayEquals(SafeEncoder.encode(\"foo.bar\"), channel);\n        assertArrayEquals(SafeEncoder.encode(\"exit\"), message);\n        punsubscribe();\n      }\n    }, SafeEncoder.encode(\"foo.*\"));\n  }\n\n  @Test\n  public void binaryPsubscribeMany() throws UnknownHostException, IOException, InterruptedException {\n    jedis.psubscribe(new BinaryJedisPubSub() {\n      public void onPSubscribe(byte[] pattern, int subscribedChannels) {\n        publishOne(SafeEncoder.encode(pattern).replace(\"*\", \"123\"), \"exit\");\n      }\n\n      public void onPMessage(byte[] pattern, byte[] channel, byte[] message) {\n        punsubscribe(pattern);\n      }\n    }, SafeEncoder.encode(\"foo.*\"), SafeEncoder.encode(\"bar.*\"));\n  }\n\n  @Test\n  public void binaryPubSubChannelWithPingPong() throws InterruptedException {\n    final CountDownLatch latchUnsubscribed = new CountDownLatch(1);\n    final CountDownLatch latchReceivedPong = new CountDownLatch(1);\n\n    jedis.subscribe(new BinaryJedisPubSub() {\n\n      @Override\n      public void onSubscribe(byte[] channel, int subscribedChannels) {\n        publishOne(\"testchan1\", \"hello\");\n      }\n\n      @Override\n      public void onMessage(byte[] channel, byte[] message) {\n        this.ping();\n      }\n\n      @Override\n      public void onPong(byte[] pattern) {\n        latchReceivedPong.countDown();\n        unsubscribe();\n      }\n\n      @Override\n      public void onUnsubscribe(byte[] channel, int subscribedChannels) {\n        latchUnsubscribed.countDown();\n      }\n    }, SafeEncoder.encode(\"testchan1\"));\n    assertEquals(0L, latchReceivedPong.getCount());\n    assertEquals(0L, latchUnsubscribed.getCount());\n  }\n\n  @Test\n  public void binaryPubSubChannelWithPingPongWithArgument() throws InterruptedException {\n    final CountDownLatch latchUnsubscribed = new CountDownLatch(1);\n    final CountDownLatch latchReceivedPong = new CountDownLatch(1);\n    final List<byte[]> pongPatterns = new ArrayList<>();\n    final byte[] pingMessage = SafeEncoder.encode(\"hi!\");\n\n    jedis.subscribe(new BinaryJedisPubSub() {\n\n      @Override\n      public void onSubscribe(byte[] channel, int subscribedChannels) {\n        publishOne(\"testchan1\", \"hello\");\n      }\n\n      @Override\n      public void onMessage(byte[] channel, byte[] message) {\n        this.ping(pingMessage);\n      }\n\n      @Override\n      public void onPong(byte[] pattern) {\n        pongPatterns.add(pattern);\n        latchReceivedPong.countDown();\n        unsubscribe();\n      }\n\n      @Override\n      public void onUnsubscribe(byte[] channel, int subscribedChannels) {\n        latchUnsubscribed.countDown();\n      }\n    }, SafeEncoder.encode(\"testchan1\"));\n\n    assertEquals(0L, latchReceivedPong.getCount());\n    assertEquals(0L, latchUnsubscribed.getCount());\n    assertArrayEquals(pingMessage, pongPatterns.get(0));\n  }\n\n  @Test\n  public void binarySubscribeLazily() throws UnknownHostException, IOException,\n      InterruptedException {\n    final BinaryJedisPubSub pubsub = new BinaryJedisPubSub() {\n      public void onMessage(byte[] channel, byte[] message) {\n        unsubscribe(channel);\n      }\n\n      public void onSubscribe(byte[] channel, int subscribedChannels) {\n        publishOne(SafeEncoder.encode(channel), \"exit\");\n\n        if (!SafeEncoder.encode(channel).equals(\"bar\")) {\n          this.subscribe(SafeEncoder.encode(\"bar\"));\n          this.psubscribe(SafeEncoder.encode(\"bar.*\"));\n        }\n      }\n\n      public void onPSubscribe(byte[] pattern, int subscribedChannels) {\n        publishOne(SafeEncoder.encode(pattern).replace(\"*\", \"123\"), \"exit\");\n      }\n\n      public void onPMessage(byte[] pattern, byte[] channel, byte[] message) {\n        punsubscribe(pattern);\n      }\n    };\n\n    jedis.subscribe(pubsub, SafeEncoder.encode(\"foo\"));\n  }\n\n  @Test\n  public void unsubscribeWhenNotSusbscribed() throws InterruptedException {\n    JedisPubSub pubsub = new JedisPubSub() {\n    };\n    assertThrows(JedisException.class, pubsub::unsubscribe);\n  }\n\n  @Test\n  // NOTE(imalinovskyi): Pushing 100Mb over high latency network can take a lot of time,\n  // so skipping for RE for now\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void handleClientOutputBufferLimitForSubscribeTooSlow() throws InterruptedException {\n    assertThrows(JedisException.class, () -> {\n      final Jedis j = createJedis();\n      final AtomicBoolean exit = new AtomicBoolean(false);\n\n      final Thread t = new Thread(new Runnable() {\n        public void run() {\n          try {\n\n            // we already set jedis1 config to\n            // client-output-buffer-limit pubsub 256k 128k 5\n            // it means if subscriber delayed to receive over 256k or\n            // 128k continuously 5 sec,\n            // redis disconnects subscriber\n\n            // we publish over 100M data for making situation for exceed\n            // client-output-buffer-limit\n            String veryLargeString = makeLargeString(10485760);\n\n            // 10M * 10 = 100M\n            for (int i = 0; i < 10 && !exit.get(); i++) {\n              j.publish(\"foo\", veryLargeString);\n            }\n\n            j.disconnect();\n          } catch (Exception ex) {\n          }\n        }\n      });\n      t.start();\n      try {\n        jedis.subscribe(new JedisPubSub() {\n          public void onMessage(String channel, String message) {\n            try {\n              // wait 0.5 secs to slow down subscribe and\n              // client-output-buffer exceed\n              Thread.sleep(100);\n            } catch (Exception e) {\n              try {\n                t.join();\n              } catch (InterruptedException e1) {\n              }\n\n              fail(e.getMessage());\n            }\n          }\n        }, \"foo\");\n      } finally {\n        // exit the publisher thread. if exception is thrown, thread might\n        // still keep publishing things.\n        exit.set(true);\n        if (t.isAlive()) {\n          t.join();\n        }\n      }\n    });\n  }\n\n  private String makeLargeString(int size) {\n    StringBuffer sb = new StringBuffer();\n    for (int i = 0; i < size; i++)\n      sb.append((char) ('a' + i % 26));\n\n    return sb.toString();\n  }\n\n  @Test\n  @Timeout(5)\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void subscribeCacheInvalidateChannel() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n\n    final String cacheInvalidate = \"__redis__:invalidate\";\n    final AtomicBoolean onMessage = new AtomicBoolean(false);\n    final JedisPubSub pubsub = new JedisPubSub() {\n      @Override public void onMessage(String channel, String message) {\n        onMessage.set(true);\n        assertEquals(cacheInvalidate, channel);\n        if (message != null) {\n          assertEquals(\"foo\", message);\n          consumeJedis(j -> j.flushAll());\n        } else {\n          unsubscribe(channel);\n        }\n      }\n\n      @Override public void onSubscribe(String channel, int subscribedChannels) {\n        assertEquals(cacheInvalidate, channel);\n        consumeJedis(j -> j.set(\"foo\", \"bar\"));\n      }\n    };\n\n    try (Jedis subscriber = createJedis()) {\n      long clientId = subscriber.clientId();\n      subscriber.sendCommand(CLIENT, \"TRACKING\", \"ON\", \"REDIRECT\", Long.toString(clientId), \"BCAST\");\n      subscriber.subscribe(pubsub, cacheInvalidate);\n      assertTrue(onMessage.get(), \"Subscriber didn't get any message.\");\n    }\n  }\n\n  @Test\n  @Timeout(5)\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void subscribeCacheInvalidateChannelBinary() {\n    assumeTrue(protocol != RedisProtocol.RESP3);\n\n    final byte[] cacheInvalidate = \"__redis__:invalidate\".getBytes();\n    final AtomicBoolean onMessage = new AtomicBoolean(false);\n    final BinaryJedisPubSub pubsub = new BinaryJedisPubSub() {\n      @Override public void onMessage(byte[] channel, byte[] message) {\n        onMessage.set(true);\n        assertArrayEquals(cacheInvalidate, channel);\n        if (message != null) {\n          assertArrayEquals(\"foo\".getBytes(), message);\n          consumeJedis(j -> j.flushAll());\n        } else {\n          unsubscribe(channel);\n        }\n      }\n\n      @Override public void onSubscribe(byte[] channel, int subscribedChannels) {\n        assertArrayEquals(cacheInvalidate, channel);\n        consumeJedis(j -> j.set(\"foo\".getBytes(), \"bar\".getBytes()));\n      }\n    };\n\n    try (Jedis subscriber = createJedis()) {\n      long clientId = subscriber.clientId();\n      subscriber.sendCommand(CLIENT, \"TRACKING\", \"ON\", \"REDIRECT\", Long.toString(clientId), \"BCAST\");\n      subscriber.subscribe(pubsub, cacheInvalidate);\n      assertTrue(onMessage.get(), \"Subscriber didn't get any message.\");\n    }\n  }\n\n  private void consumeJedis(Consumer<Jedis> consumer) {\n    Thread t = new Thread(() -> consumer.accept(jedis));\n    t.start();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/ScriptingCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.utils.RedisVersion;\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.exceptions.JedisNoScriptException;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\nimport redis.clients.jedis.util.*;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ScriptingCommandsTest extends JedisCommandsTestBase {\n\n  public ScriptingCommandsTest(RedisProtocol redisProtocol) {\n    super(redisProtocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n    if (RedisVersionUtil.getRedisVersion(jedis)\n            .isGreaterThanOrEqualTo(RedisVersion.V7_0_0)) {\n      jedis.functionFlush();\n    }\n  }\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x0A };\n  final byte[] bfoo2 = { 0x01, 0x02, 0x03, 0x04, 0x0B };\n  final byte[] bfoo3 = { 0x01, 0x02, 0x03, 0x04, 0x0C };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bfoobar = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void evalMultiBulk() {\n    String script = \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2],ARGV[3]}\";\n    List<String> keys = new ArrayList<String>();\n    keys.add(\"key1\");\n    keys.add(\"key2\");\n\n    List<String> args = new ArrayList<String>();\n    args.add(\"first\");\n    args.add(\"second\");\n    args.add(\"third\");\n\n    List<String> response = (List<String>) jedis.eval(script, keys, args);\n\n    assertEquals(5, response.size());\n    assertEquals(\"key1\", response.get(0));\n    assertEquals(\"key2\", response.get(1));\n    assertEquals(\"first\", response.get(2));\n    assertEquals(\"second\", response.get(3));\n    assertEquals(\"third\", response.get(4));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void evalMultiBulkWithBinaryJedis() {\n    String script = \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2],ARGV[3]}\";\n    List<byte[]> keys = new ArrayList<byte[]>();\n    keys.add(\"key1\".getBytes());\n    keys.add(\"key2\".getBytes());\n\n    List<byte[]> args = new ArrayList<byte[]>();\n    args.add(\"first\".getBytes());\n    args.add(\"second\".getBytes());\n    args.add(\"third\".getBytes());\n\n    List<byte[]> responses = (List<byte[]>) jedis.eval(script.getBytes(), keys, args);\n    assertEquals(5, responses.size());\n    assertEquals(\"key1\", new String(responses.get(0)));\n    assertEquals(\"key2\", new String(responses.get(1)));\n    assertEquals(\"first\", new String(responses.get(2)));\n    assertEquals(\"second\", new String(responses.get(3)));\n    assertEquals(\"third\", new String(responses.get(4)));\n  }\n\n  @Test\n  public void evalBulk() {\n    String script = \"return KEYS[1]\";\n    List<String> keys = new ArrayList<String>();\n    keys.add(\"key1\");\n\n    List<String> args = new ArrayList<String>();\n    args.add(\"first\");\n\n    String response = (String) jedis.eval(script, keys, args);\n\n    assertEquals(\"key1\", response);\n  }\n\n  @Test\n  public void evalInt() {\n    String script = \"return 2\";\n    List<String> keys = new ArrayList<String>();\n    keys.add(\"key1\");\n\n    Long response = (Long) jedis.eval(script, keys, new ArrayList<String>());\n\n    assertEquals(Long.valueOf(2), response);\n  }\n\n  @Test\n  public void evalNestedLists() {\n    String script = \"return { {KEYS[1]} , {2} }\";\n    List<?> results = (List<?>) jedis.eval(script, 1, \"key1\");\n\n    MatcherAssert.assertThat((List<String>) results.get(0), Matchers.hasItem(\"key1\"));\n    MatcherAssert.assertThat((List<Long>) results.get(1), Matchers.hasItem(2L));\n  }\n\n  @Test\n  public void evalNoArgs() {\n    String script = \"return KEYS[1]\";\n    List<String> keys = new ArrayList<String>();\n    keys.add(\"key1\");\n    String response = (String) jedis.eval(script, keys, new ArrayList<String>());\n\n    assertEquals(\"key1\", response);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void evalReadonly() {\n    String script = \"return KEYS[1]\";\n    List<String> keys = new ArrayList<String>();\n    keys.add(\"key1\");\n\n    List<String> args = new ArrayList<String>();\n    args.add(\"first\");\n\n    String response = (String) jedis.evalReadonly(script, keys, args);\n\n    assertEquals(\"key1\", response);\n  }\n\n  @Test\n  public void evalsha() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.eval(\"return redis.call('get','foo')\");\n    String result = (String) jedis.evalsha(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\");\n\n    assertEquals(\"bar\", result);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void evalshaReadonly() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.eval(\"return redis.call('get','foo')\");\n    String result = (String) jedis.evalshaReadonly(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\",\n            Collections.emptyList(), Collections.emptyList());\n\n    assertEquals(\"bar\", result);\n  }\n\n  @Test\n  public void evalshaBinary() {\n    jedis.set(SafeEncoder.encode(\"foo\"), SafeEncoder.encode(\"bar\"));\n    jedis.eval(SafeEncoder.encode(\"return redis.call('get','foo')\"));\n    byte[] result = (byte[]) jedis.evalsha(SafeEncoder\n        .encode(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\"));\n\n    assertArrayEquals(SafeEncoder.encode(\"bar\"), result);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void evalshaReadonlyBinary() {\n    jedis.set(SafeEncoder.encode(\"foo\"), SafeEncoder.encode(\"bar\"));\n    jedis.eval(SafeEncoder.encode(\"return redis.call('get','foo')\"));\n    byte[] result = (byte[]) jedis.evalshaReadonly(SafeEncoder.encode(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\"),\n            Collections.emptyList(), Collections.emptyList());\n\n    assertArrayEquals(SafeEncoder.encode(\"bar\"), result);\n  }\n\n  @Test\n  public void evalshaShaNotFound() {\n    assertThrows(JedisNoScriptException.class, () -> {\n      jedis.evalsha(\"ffffffffffffffffffffffffffffffffffffffff\");\n    });\n  }\n\n  @Test\n  public void scriptFlush() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.eval(\"return redis.call('get','foo')\");\n    jedis.scriptFlush();\n    assertFalse(jedis.scriptExists(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\"));\n  }\n\n  @Test\n  public void scriptFlushMode() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.eval(\"return redis.call('get','foo')\");\n    String sha1 = \"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\";\n    assertTrue(jedis.scriptExists(sha1));\n    jedis.scriptFlush(FlushMode.SYNC);\n    assertFalse(jedis.scriptExists(sha1));\n  }\n\n  @Test\n  public void scriptExists() {\n    jedis.scriptLoad(\"return redis.call('get','foo')\");\n    List<Boolean> exists = jedis.scriptExists(\"ffffffffffffffffffffffffffffffffffffffff\",\n      \"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\");\n    assertFalse(exists.get(0));\n    assertTrue(exists.get(1));\n  }\n\n  @Test\n  public void scriptExistsBinary() {\n    jedis.scriptLoad(SafeEncoder.encode(\"return redis.call('get','foo')\"));\n    List<Boolean> exists = jedis.scriptExists(\n      SafeEncoder.encode(\"ffffffffffffffffffffffffffffffffffffffff\"),\n      SafeEncoder.encode(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\"));\n    assertFalse(exists.get(0));\n    assertTrue(exists.get(1));\n  }\n\n  @Test\n  public void scriptLoad() {\n    jedis.scriptLoad(\"return redis.call('get','foo')\");\n    assertTrue(jedis.scriptExists(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\"));\n  }\n\n  @Test\n  public void scriptLoadBinary() {\n    jedis.scriptLoad(SafeEncoder.encode(\"return redis.call('get','foo')\"));\n    assertTrue(jedis.scriptExists(SafeEncoder.encode(\"6b1bf486c81ceb7edf3c093f4c48582e38c0e791\")));\n  }\n\n  @Test\n  public void scriptKill() {\n    try {\n      jedis.scriptKill();\n    } catch (JedisDataException e) {\n      assertTrue(e.getMessage().contains(\"No scripts in execution right now.\"));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void scriptEvalReturnNullValues() {\n    jedis.del(\"key1\");\n    jedis.del(\"key2\");\n\n    String script = \"return {redis.call('hget',KEYS[1],ARGV[1]),redis.call('hget',KEYS[2],ARGV[2])}\";\n    List<String> results = (List<String>) jedis.eval(script, 2, \"key1\", \"key2\", \"1\", \"2\");\n    assertEquals(2, results.size());\n    assertNull(results.get(0));\n    assertNull(results.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void scriptEvalShaReturnNullValues() {\n    jedis.del(\"key1\");\n    jedis.del(\"key2\");\n\n    String script = \"return {redis.call('hget',KEYS[1],ARGV[1]),redis.call('hget',KEYS[2],ARGV[2])}\";\n    String sha = jedis.scriptLoad(script);\n    List<String> results = (List<String>) jedis.evalsha(sha, 2, \"key1\", \"key2\", \"1\", \"2\");\n    assertEquals(2, results.size());\n    assertNull(results.get(0));\n    assertNull(results.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void scriptEvalShaReturnValues() {\n    jedis.hset(\"foo\", \"foo1\", \"bar1\");\n    jedis.hset(\"foobar\", \"foo2\", \"bar2\");\n\n    String script = \"return {redis.call('hget',KEYS[1],ARGV[1]),redis.call('hget',KEYS[2],ARGV[2])}\";\n    String sha = jedis.scriptLoad(script);\n    List<String> results = (List<String>) jedis.evalsha(sha, Arrays.asList(\"foo\", \"foobar\"), Arrays.asList(\"foo1\", \"foo2\"));\n    assertEquals(2, results.size());\n    assertEquals(\"bar1\", results.get(0));\n    assertEquals(\"bar2\", results.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void scriptEvalShaReturnValuesBinary() {\n    jedis.hset(bfoo, bfoo1, bbar1);\n    jedis.hset(bfoobar, bfoo2, bbar2);\n\n    byte[] script = \"return {redis.call('hget',KEYS[1],ARGV[1]),redis.call('hget',KEYS[2],ARGV[2])}\".getBytes();\n    byte[] sha = jedis.scriptLoad(script);\n    List<byte[]> results = (List<byte[]>) jedis.evalsha(sha, Arrays.asList(bfoo, bfoobar), Arrays.asList(bfoo1, bfoo2));\n    assertEquals(2, results.size());\n    assertArrayEquals(bbar1, results.get(0));\n    assertArrayEquals(bbar2, results.get(1));\n  }\n\n  @Test\n  public void scriptExistsWithBrokenConnection() {\n    Jedis deadClient = createJedis();\n\n    deadClient.clientSetname(\"DEAD\");\n\n    ClientKillerUtil.killClient(deadClient, \"DEAD\");\n\n    // sure, script doesn't exist, but it's just for checking connection\n    try {\n      deadClient.scriptExists(\"abcdefg\");\n    } catch (JedisConnectionException e) {\n      // ignore it\n    }\n\n    assertEquals(true, deadClient.isBroken());\n\n    deadClient.close();\n  }\n\n  @Test\n  public void emptyLuaTableReply() {\n    Object reply = jedis.eval(\"return {}\");\n    assertEquals(Collections.emptyList(), reply);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void functionLoadAndDelete() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args[1] end)\";\n    String functionCode = String.format(\"#!%s name=%s \\n %s\", engine, library, function);\n\n    assertEquals(library, jedis.functionLoad(functionCode));\n    assertEquals(library, jedis.functionLoadReplace(functionCode));\n\n    assertEquals(\"OK\", jedis.functionDelete(library));\n\n    // Binary\n    assertEquals(library, jedis.functionLoad(functionCode.getBytes()));\n    assertEquals(library, jedis.functionLoadReplace(functionCode.getBytes()));\n\n    assertEquals(\"OK\", jedis.functionDelete(library.getBytes()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void functionFlush() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args[1] end)\";\n    String functionCode = String.format(\"#!%s name=%s \\n %s\", engine, library, function);\n\n    assertEquals(library, jedis.functionLoad(functionCode));\n    jedis.functionFlush();\n    assertEquals(library, jedis.functionLoad(functionCode));\n    jedis.functionFlush(FlushMode.ASYNC);\n    assertEquals(library, jedis.functionLoad(functionCode));\n    jedis.functionFlush(FlushMode.SYNC);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void functionList() {\n    String engine = \"LUA\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args[1] end)\";\n    String functionCode = String.format(\"#!%s name=%s \\n %s\", engine, library, function);\n    jedis.functionLoad(functionCode);\n\n    LibraryInfo response = jedis.functionList().get(0);\n    assertEquals(library, response.getLibraryName());\n    assertEquals(engine, response.getEngine());\n    assertEquals(1, response.getFunctions().size());\n\n    // check function info\n    Map func = response.getFunctions().get(0);\n    assertEquals(\"myfunc\", func.get(\"name\"));\n    assertNull(func.get(\"description\"));\n    assertTrue(((List) func.get(\"flags\")).isEmpty());\n\n    // check WITHCODE\n    response = jedis.functionListWithCode().get(0);\n    assertEquals(\"myfunc\", func.get(\"name\"));\n    assertEquals(functionCode, response.getLibraryCode());\n\n    // check with LIBRARYNAME\n    response = jedis.functionList(library).get(0);\n    assertEquals(library, response.getLibraryName());\n\n    // check with code and with LIBRARYNAME\n    response = jedis.functionListWithCode(library).get(0);\n    assertEquals(library, response.getLibraryName());\n    assertEquals(functionCode, response.getLibraryCode());\n\n    // Binary\n    if (protocol != RedisProtocol.RESP3) {\n\n      List<Object> bresponse = (List<Object>) jedis.functionListBinary().get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(1));\n\n      bresponse = (List<Object>) jedis.functionListWithCodeBinary().get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(1));\n      assertNotNull(bresponse.get(7));\n\n      bresponse = (List<Object>) jedis.functionList(library.getBytes()).get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(1));\n\n      bresponse = (List<Object>) jedis.functionListWithCode(library.getBytes()).get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(1));\n      assertNotNull(bresponse.get(7));\n    } else {\n\n      List<KeyValue> bresponse = (List<KeyValue>) jedis.functionListBinary().get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(0).getValue());\n\n      bresponse = (List<KeyValue>) jedis.functionListWithCodeBinary().get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(0).getValue());\n      assertNotNull(bresponse.get(3));\n\n      bresponse = (List<KeyValue>) jedis.functionList(library.getBytes()).get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(0).getValue());\n\n      bresponse = (List<KeyValue>) jedis.functionListWithCode(library.getBytes()).get(0);\n      assertArrayEquals(library.getBytes(), (byte[]) bresponse.get(0).getValue());\n      assertNotNull(bresponse.get(3));\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void functionDumpRestore() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args[1] end)\";\n\n    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n    byte[] payload = jedis.functionDump();\n    jedis.functionFlush();\n    assertEquals(\"OK\", jedis.functionRestore(payload));\n    jedis.functionFlush();\n    assertEquals(\"OK\", jedis.functionRestore(payload, FunctionRestorePolicy.FLUSH));\n    jedis.functionFlush();\n    assertEquals(\"OK\", jedis.functionRestore(payload, FunctionRestorePolicy.APPEND));\n    jedis.functionFlush();\n    assertEquals(\"OK\", jedis.functionRestore(payload, FunctionRestorePolicy.REPLACE));\n    jedis.functionFlush();\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void functionStatsWithoutRunning() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args[1] end)\";\n\n    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n    FunctionStats stats = jedis.functionStats();\n    assertNull(stats.getRunningScript());\n    assertEquals(1, stats.getEngines().size());\n  }\n//\n//  @Test\n//  public void functionStatsWithRunning() throws InterruptedException {\n//    jedis.functionFlush();\n//    function = \"redis.register_function('myfunc', function(keys, args)\\n local a = 1 while true do a = a + 1 end \\nend)\";\n//\n//    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n//    jedis.fcall(\"myfunc\", new ArrayList<>(), new ArrayList<>());\n//    stats = jedis.functionStats();\n//    assertNotNull(stats.getScript());\n//    assertEquals(\"myfunc\", stats.getScript().getName());\n//  }\n//\n//  @Test\n//  public void functionKill() {\n//    String engine = \"Lua\";\n//    String library = \"mylib\";\n//    String function = \"redis.register_function('myfunc', function(keys, args)\\n local a = 1 while true do a = a + 1 end \\nend)\";\n//\n//    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n//    jedis.fcall(\"myfunc\", Collections.emptyList(), Collections.emptyList());\n//    assertEquals(\"OK\", jedis.functionKill());\n//  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void functionKillWithoutRunningFunction() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args)\\n local a = 1 while true do a = a + 1 end \\nend)\";\n\n    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n    try {\n      jedis.functionKill();\n      fail(\"Should get NOTBUSY error.\");\n    } catch (JedisDataException jde) {\n      assertEquals(\"NOTBUSY No scripts in execution right now.\", jde.getMessage());\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void fcall() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args end)\";\n\n    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n    List<String> args = Arrays.asList(\"hello\");\n    assertEquals(args, jedis.fcall(\"myfunc\", Collections.emptyList(), args));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void fcallBinary() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function('myfunc', function(keys, args) return args[1] end)\";\n\n    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n    List<byte[]> bargs = Arrays.asList(\"hello\".getBytes());\n    assertArrayEquals(\"hello\".getBytes(), (byte[]) jedis.fcall(\"myfunc\".getBytes(), Collections.emptyList(), bargs));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void fcallReadonly() {\n    String engine = \"Lua\";\n    String library = \"mylib\";\n    String function = \"redis.register_function{function_name='noop', callback=function() return 1 end, flags={ 'no-writes' }}\";\n    jedis.functionLoad(String.format(\"#!%s name=%s \\n %s\", engine, library, function));\n\n    assertEquals(Long.valueOf(1), jedis.fcallReadonly(\"noop\", Collections.emptyList(), Collections.emptyList()));\n\n    // Binary\n    assertEquals(Long.valueOf(1), jedis.fcallReadonly(\"noop\".getBytes(), Collections.emptyList(), Collections.emptyList()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/SentinelCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.hamcrest.Matchers;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class SentinelCommandsTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  protected static final String MASTER_NAME = \"mymaster\";\n\n  protected static List<HostAndPort> nodes;\n\n  protected static Set<String> nodesPorts;\n\n  protected static List<HostAndPort> sentinels2;\n\n  @BeforeAll\n  public static void prepare() throws Exception {\n    nodes = Arrays.asList(Endpoints.getRedisEndpoint(\"standalone2-primary\").getHostAndPort(),\n        Endpoints.getRedisEndpoint(\"standalone3-replica-of-standalone2\").getHostAndPort());\n\n    nodesPorts = nodes.stream().map(HostAndPort::getPort).map(String::valueOf)\n        .collect(Collectors.toSet());\n\n    sentinels2 = Arrays.asList(\n        Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort(),\n        Endpoints.getRedisEndpoint(\"sentinel-standalone2-3\").getHostAndPort());\n  }\n\n  @Test\n  public void myIdAndSentinels() {\n    Map<String, Integer> idToPort = new HashMap<>();\n    sentinels2.forEach((hap) -> {\n      try (Jedis sentinel = new Jedis(hap)) {\n        String id = sentinel.sentinelMyId();\n        assertThat(id, Matchers.not(Matchers.emptyOrNullString()));\n        idToPort.put(id, hap.getPort());\n      }\n    });\n    assertEquals(2, idToPort.size());\n\n    try (Jedis sentinel = new Jedis(sentinels2.stream().findAny().get())) {\n      List<Map<String, String>> detailsList = sentinel.sentinelSentinels(MASTER_NAME);\n      assertThat(detailsList, Matchers.not(Matchers.empty()));\n      detailsList.forEach((details)\n          -> assertEquals(idToPort.get(details.get(\"runid\")),\n              Integer.valueOf(details.get(\"port\"))));\n    }\n  }\n\n  @Test\n  public void masterAndMasters() {\n    String runId, port;\n    try (Jedis sentinel = new Jedis(sentinels2.get(0))) {\n      Map<String, String> details = sentinel.sentinelMaster(MASTER_NAME);\n      assertEquals(MASTER_NAME, details.get(\"name\"));\n      runId = details.get(\"runid\");\n      port = details.get(\"port\");\n      assertThat(port, Matchers.in(nodesPorts));\n    }\n\n    try (Jedis sentinel2 = new Jedis(sentinels2.get(1))) {\n      Map<String, String> details = sentinel2.sentinelMasters().get(0);\n      assertEquals(MASTER_NAME, details.get(\"name\"));\n      assertEquals(runId, details.get(\"runid\"));\n      assertEquals(port, details.get(\"port\"));\n    }\n  }\n\n  @Test\n  public void replicas() {\n    try (Jedis sentinel = new Jedis(sentinels2.stream().findAny().get())) {\n      List<Map<String, String>> detailsList = sentinel.sentinelReplicas(MASTER_NAME);\n      assertThat(detailsList, Matchers.not(Matchers.empty()));\n      detailsList.forEach((details)\n          -> assertThat(details.get(\"port\"), Matchers.in(nodesPorts)));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/SetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayCollectionContainsAll;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArraySetEquals;\nimport static redis.clients.jedis.util.AssertUtil.assertCollectionContainsAll;\nimport static redis.clients.jedis.util.ByteArrayUtil.byteArrayCollectionRemoveAll;\n\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SetCommandsTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bd = { 0x0D };\n  final byte[] bx = { 0x42 };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public SetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void sadd() {\n    long status = jedis.sadd(\"foo\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.sadd(\"foo\", \"a\");\n    assertEquals(0, status);\n\n    long bstatus = jedis.sadd(bfoo, ba);\n    assertEquals(1, bstatus);\n\n    bstatus = jedis.sadd(bfoo, ba);\n    assertEquals(0, bstatus);\n\n  }\n\n  @Test\n  public void smembers() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n\n    Set<String> members = jedis.smembers(\"foo\");\n\n    assertEquals(expected, members);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n\n    Set<byte[]> bmembers = jedis.smembers(bfoo);\n\n    assertByteArraySetEquals(bexpected, bmembers);\n  }\n\n  @Test\n  public void srem() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    long status = jedis.srem(\"foo\", \"a\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"b\");\n\n    assertEquals(1, status);\n    assertEquals(expected, jedis.smembers(\"foo\"));\n\n    status = jedis.srem(\"foo\", \"bar\");\n\n    assertEquals(0, status);\n\n    // Binary\n\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    long bstatus = jedis.srem(bfoo, ba);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n\n    assertEquals(1, bstatus);\n    assertByteArraySetEquals(bexpected, jedis.smembers(bfoo));\n\n    bstatus = jedis.srem(bfoo, bbar);\n\n    assertEquals(0, bstatus);\n\n  }\n\n  @Test\n  public void spop() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    String member = jedis.spop(\"foo\");\n\n    assertTrue(\"a\".equals(member) || \"b\".equals(member));\n    assertEquals(1, jedis.smembers(\"foo\").size());\n\n    member = jedis.spop(\"bar\");\n    assertNull(member);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    byte[] bmember = jedis.spop(bfoo);\n\n    assertTrue(Arrays.equals(ba, bmember) || Arrays.equals(bb, bmember));\n    assertEquals(1, jedis.smembers(bfoo).size());\n\n    bmember = jedis.spop(bbar);\n    assertNull(bmember);\n\n  }\n\n  @Test\n  public void spopWithCount() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n    jedis.sadd(\"foo\", \"c\");\n\n    Set<String> superSet = new HashSet<String>();\n    superSet.add(\"c\");\n    superSet.add(\"b\");\n    superSet.add(\"a\");\n\n    Set<String> members = jedis.spop(\"foo\", 2);\n\n    assertEquals(2, members.size());\n    assertCollectionContainsAll(superSet, members);\n    superSet.removeAll(members);\n\n    members = jedis.spop(\"foo\", 2);\n    assertEquals(1, members.size());\n    assertEquals(superSet, members);\n\n    assertTrue(jedis.spop(\"foo\", 2).isEmpty());\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n    jedis.sadd(bfoo, bc);\n\n    Set<byte[]> bsuperSet = new HashSet<byte[]>();\n    bsuperSet.add(bc);\n    bsuperSet.add(bb);\n    bsuperSet.add(ba);\n\n    Set<byte[]> bmembers = jedis.spop(bfoo, 2);\n\n    assertEquals(2, bmembers.size());\n    assertByteArrayCollectionContainsAll(bsuperSet, bmembers);\n    byteArrayCollectionRemoveAll(bsuperSet, bmembers);\n\n    bmembers = jedis.spop(bfoo, 2);\n    assertEquals(1, bmembers.size());\n    assertByteArraySetEquals(bsuperSet, bmembers);\n\n    assertTrue(jedis.spop(bfoo, 2).isEmpty());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void smove() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"c\");\n\n    long status = jedis.smove(\"foo\", \"bar\", \"a\");\n\n    Set<String> expectedSrc = new HashSet<String>();\n    expectedSrc.add(\"b\");\n\n    Set<String> expectedDst = new HashSet<String>();\n    expectedDst.add(\"c\");\n    expectedDst.add(\"a\");\n\n    assertEquals(status, 1);\n    assertEquals(expectedSrc, jedis.smembers(\"foo\"));\n    assertEquals(expectedDst, jedis.smembers(\"bar\"));\n\n    status = jedis.smove(\"foo\", \"bar\", \"a\");\n\n    assertEquals(status, 0);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bc);\n\n    long bstatus = jedis.smove(bfoo, bbar, ba);\n\n    Set<byte[]> bexpectedSrc = new HashSet<byte[]>();\n    bexpectedSrc.add(bb);\n\n    Set<byte[]> bexpectedDst = new HashSet<byte[]>();\n    bexpectedDst.add(bc);\n    bexpectedDst.add(ba);\n\n    assertEquals(bstatus, 1);\n    assertByteArraySetEquals(bexpectedSrc, jedis.smembers(bfoo));\n    assertByteArraySetEquals(bexpectedDst, jedis.smembers(bbar));\n\n    bstatus = jedis.smove(bfoo, bbar, ba);\n    assertEquals(bstatus, 0);\n\n  }\n\n  @Test\n  public void scard() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    long card = jedis.scard(\"foo\");\n\n    assertEquals(2, card);\n\n    card = jedis.scard(\"bar\");\n    assertEquals(0, card);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    long bcard = jedis.scard(bfoo);\n\n    assertEquals(2, bcard);\n\n    bcard = jedis.scard(bbar);\n    assertEquals(0, bcard);\n\n  }\n\n  @Test\n  public void sismember() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    assertTrue(jedis.sismember(\"foo\", \"a\"));\n\n    assertFalse(jedis.sismember(\"foo\", \"c\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    assertTrue(jedis.sismember(bfoo, ba));\n\n    assertFalse(jedis.sismember(bfoo, bc));\n\n  }\n\n  @Test\n  public void smismember() {\n    jedis.sadd(\"foo\", \"a\", \"b\");\n\n    assertEquals(Arrays.asList(true, false), jedis.smismember(\"foo\", \"a\", \"c\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba, bb);\n\n    assertEquals(Arrays.asList(true, false), jedis.smismember(bfoo, ba, bc));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sinter() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"b\");\n\n    Set<String> intersection = jedis.sinter(\"foo\", \"bar\");\n    assertEquals(expected, intersection);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n\n    Set<byte[]> bintersection = jedis.sinter(bfoo, bbar);\n    assertByteArraySetEquals(bexpected, bintersection);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sinterstore() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"b\");\n\n    long status = jedis.sinterstore(\"car\", \"foo\", \"bar\");\n    assertEquals(1, status);\n\n    assertEquals(expected, jedis.smembers(\"car\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n\n    long bstatus = jedis.sinterstore(bcar, bfoo, bbar);\n    assertEquals(1, bstatus);\n\n    assertByteArraySetEquals(bexpected, jedis.smembers(bcar));\n\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sintercard() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"a\");\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    long card = jedis.sintercard(\"foo\", \"bar\");\n    assertEquals(2, card);\n    long limitedCard = jedis.sintercard(1, \"foo\", \"bar\");\n    assertEquals(1, limitedCard);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, ba);\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    long bcard = jedis.sintercard(bfoo, bbar);\n    assertEquals(2, bcard);\n    long blimitedCard = jedis.sintercard(1, bfoo, bbar);\n    assertEquals(1, blimitedCard);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sunion() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    Set<String> union = jedis.sunion(\"foo\", \"bar\");\n    assertEquals(expected, union);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    Set<byte[]> bunion = jedis.sunion(bfoo, bbar);\n    assertByteArraySetEquals(bexpected, bunion);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sunionstore() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    long status = jedis.sunionstore(\"car\", \"foo\", \"bar\");\n    assertEquals(3, status);\n\n    assertEquals(expected, jedis.smembers(\"car\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    long bstatus = jedis.sunionstore(bcar, bfoo, bbar);\n    assertEquals(3, bstatus);\n\n    assertByteArraySetEquals(bexpected, jedis.smembers(bcar));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sdiff() {\n    jedis.sadd(\"foo\", \"x\");\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n    jedis.sadd(\"foo\", \"c\");\n\n    jedis.sadd(\"bar\", \"c\");\n\n    jedis.sadd(\"car\", \"a\");\n    jedis.sadd(\"car\", \"d\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"x\");\n    expected.add(\"b\");\n\n    Set<String> diff = jedis.sdiff(\"foo\", \"bar\", \"car\");\n    assertEquals(expected, diff);\n\n    // Binary\n    jedis.sadd(bfoo, bx);\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n    jedis.sadd(bfoo, bc);\n\n    jedis.sadd(bbar, bc);\n\n    jedis.sadd(bcar, ba);\n    jedis.sadd(bcar, bd);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(bx);\n\n    Set<byte[]> bdiff = jedis.sdiff(bfoo, bbar, bcar);\n    assertByteArraySetEquals(bexpected, bdiff);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sdiffstore() {\n    jedis.sadd(\"foo\", \"x\");\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n    jedis.sadd(\"foo\", \"c\");\n\n    jedis.sadd(\"bar\", \"c\");\n\n    jedis.sadd(\"car\", \"a\");\n    jedis.sadd(\"car\", \"d\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"x\");\n    expected.add(\"b\");\n\n    long status = jedis.sdiffstore(\"tar\", \"foo\", \"bar\", \"car\");\n    assertEquals(2, status);\n    assertEquals(expected, jedis.smembers(\"tar\"));\n\n    // Binary\n    jedis.sadd(bfoo, bx);\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n    jedis.sadd(bfoo, bc);\n\n    jedis.sadd(bbar, bc);\n\n    jedis.sadd(bcar, ba);\n    jedis.sadd(bcar, bd);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bx);\n    bexpected.add(bb);\n\n    long bstatus = jedis.sdiffstore(\"tar\".getBytes(), bfoo, bbar, bcar);\n    assertEquals(2, bstatus);\n    assertByteArraySetEquals(bexpected, jedis.smembers(\"tar\".getBytes()));\n\n  }\n\n  @Test\n  public void srandmember() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    String member = jedis.srandmember(\"foo\");\n\n    assertTrue(\"a\".equals(member) || \"b\".equals(member));\n    assertEquals(2, jedis.smembers(\"foo\").size());\n\n    List<String> members = jedis.srandmember(\"foo\", 2);\n    members.sort(Comparator.naturalOrder());\n    assertEquals( Arrays.asList(\"a\", \"b\"), members);\n\n    member = jedis.srandmember(\"bar\");\n    assertNull(member);\n    \n    members = jedis.srandmember(\"bar\", 2);\n    assertEquals(0, members.size());\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    byte[] bmember = jedis.srandmember(bfoo);\n\n    assertTrue(Arrays.equals(ba, bmember) || Arrays.equals(bb, bmember));\n    assertEquals(2, jedis.smembers(bfoo).size());\n    \n    List<byte[]> bmembers = jedis.srandmember(bfoo, 2);\n    assertEquals(2, bmembers.size());\n\n    bmember = jedis.srandmember(bbar);\n    assertNull(bmember);\n    \n    members = jedis.srandmember(\"bbar\", 2);\n    assertEquals(0, members.size());\n  }\n\n  @Test\n  public void sscan() {\n    jedis.sadd(\"foo\", \"a\", \"b\");\n\n    ScanResult<String> result = jedis.sscan(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    jedis.sadd(bfoo, ba, bb);\n\n    ScanResult<byte[]> bResult = jedis.sscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void sscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.sadd(\"foo\", \"b\", \"a\", \"aa\");\n    ScanResult<String> result = jedis.sscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.sadd(bfoo, bbar1, bbar2, bbar3);\n    ScanResult<byte[]> bResult = jedis.sscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void sscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    jedis.sadd(\"foo\", \"a1\", \"a2\", \"a3\", \"a4\", \"a5\");\n\n    ScanResult<String> result = jedis.sscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.sadd(bfoo, bbar1, bbar2, bbar3);\n    ScanResult<byte[]> bResult = jedis.sscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/SlowlogCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.util.*;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.resps.Slowlog;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class SlowlogCommandsTest extends JedisCommandsTestBase {\n\n  private static final String SLOWLOG_TIME_PARAM = \"slowlog-log-slower-than\";\n  private static final String ZERO_STRING = \"0\";\n\n  private String slowlogTimeValue;\n\n  public SlowlogCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n    slowlogTimeValue = jedis.configGet(SLOWLOG_TIME_PARAM).get(SLOWLOG_TIME_PARAM);\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    jedis.configSet(SLOWLOG_TIME_PARAM, slowlogTimeValue);\n    super.tearDown();\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void slowlog() {\n    jedis.configSet(SLOWLOG_TIME_PARAM, ZERO_STRING);\n    jedis.slowlogReset();\n\n    jedis.set(\"foo\", \"bar\");\n    jedis.set(\"foo2\", \"bar2\");\n\n    List<Slowlog> reducedLog = jedis.slowlogGet(1);\n    assertEquals(1, reducedLog.size());\n\n    Slowlog log = reducedLog.get(0);\n    assertThat(log.getId(), Matchers.greaterThan(0L));\n    assertThat(log.getTimeStamp(), Matchers.greaterThan(0L));\n    assertThat(log.getExecutionTime(), Matchers.greaterThanOrEqualTo(0L));\n    assertNotNull(log.getArgs());\n\n    List<Object> breducedLog = jedis.slowlogGetBinary(1);\n    assertEquals(1, breducedLog.size());\n\n    List<Slowlog> log1 = jedis.slowlogGet();\n    List<Object> blog1 = jedis.slowlogGetBinary();\n\n    assertNotNull(log1);\n    assertNotNull(blog1);\n\n//    assertEquals(7, jedis.slowlogLen());\n    assertThat(jedis.slowlogLen(),\n        Matchers.allOf(Matchers.greaterThanOrEqualTo(6L), Matchers.lessThanOrEqualTo(13L)));\n    assertThat(jedis.slowlogGet().toString(), Matchers.containsString(\"SLOWLOG\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void slowlogObjectDetails() {\n    final String clientName = \"slowlog-object-client-\" + UUID.randomUUID();\n    jedis.clientSetname(clientName);\n    jedis.slowlogReset();\n    jedis.configSet(SLOWLOG_TIME_PARAM, ZERO_STRING);\n\n    List<Slowlog> logs = jedis.slowlogGet(); // Get only 'CONFIG SET', or including 'SLOWLOG RESET'\n    //assertEquals(1, logs.size());\n    assertThat(logs.size(), Matchers.allOf(Matchers.greaterThanOrEqualTo(1), Matchers.lessThanOrEqualTo(2)));\n    Slowlog log = logs.get(logs.size() - 1);\n    assertEquals(clientName, log.getClientName());\n    assertThat(log.getId(), Matchers.greaterThan(0L));\n    assertThat(log.getTimeStamp(), Matchers.greaterThan(0L));\n    assertThat(log.getExecutionTime(), Matchers.greaterThanOrEqualTo(0L));\n    assertEquals(4, log.getArgs().size());\n    assertEquals(SafeEncoder.encode(Protocol.Command.CONFIG.getRaw()), log.getArgs().get(0));\n    assertEquals(SafeEncoder.encode(Protocol.Keyword.SET.getRaw()), log.getArgs().get(1));\n    assertEquals(SLOWLOG_TIME_PARAM, log.getArgs().get(2));\n    assertEquals(ZERO_STRING, log.getArgs().get(3));\n    assertThat(log.getClientIpPort().getHost(), Matchers.in(getAllLocalIps()));\n    assertThat(log.getClientIpPort().getPort(), Matchers.greaterThan(0));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void slowlogBinaryObjectDetails() {\n    final byte[] clientName = SafeEncoder.encode(\"slowlog-binary-client\");\n    jedis.clientSetname(clientName);\n    jedis.slowlogReset();\n    jedis.configSet(SafeEncoder.encode(SLOWLOG_TIME_PARAM), SafeEncoder.encode(ZERO_STRING));\n\n    List<Object> logs = jedis.slowlogGetBinary(); // Get only 'CONFIG SET', or including 'SLOWLOG RESET'\n    //assertEquals(1, logs.size());\n    assertThat(logs.size(), Matchers.allOf(Matchers.greaterThanOrEqualTo(1), Matchers.lessThanOrEqualTo(2)));\n    List<Object> log = (List<Object>) logs.get(0);\n    assertThat((Long) log.get(0), Matchers.greaterThan(0L));\n    assertThat((Long) log.get(1), Matchers.greaterThan(0L));\n    assertThat((Long) log.get(2), Matchers.greaterThanOrEqualTo(0L));\n    List<Object> args = (List<Object>) log.get(3);\n    assertEquals(4, args.size());\n    assertArrayEquals(Protocol.Command.CONFIG.getRaw(), (byte[]) args.get(0));\n    assertArrayEquals(Protocol.Keyword.SET.getRaw(), (byte[]) args.get(1));\n    assertArrayEquals(SafeEncoder.encode(SLOWLOG_TIME_PARAM), (byte[]) args.get(2));\n    assertArrayEquals(Protocol.toByteArray(0), (byte[]) args.get(3));\n//    assertTrue(SafeEncoder.encode((byte[]) log.get(4)).startsWith(\"127.0.0.1:\"));\n    assertThat(((byte[]) log.get(4)).length, Matchers.greaterThanOrEqualTo(10)); // 'IP:PORT'\n    assertArrayEquals(clientName, (byte[]) log.get(5));\n  }\n\n  private static Set<String> getAllLocalIps()  {\n    Set<String> allLocalIps = new HashSet<>();\n      try {\n          for (NetworkInterface netIf : Collections.list(NetworkInterface.getNetworkInterfaces())) {\n            for (InetAddress addr : Collections.list(netIf.getInetAddresses())) {\n              allLocalIps.add(addr.getHostAddress());\n            }\n          }\n      } catch (SocketException e) {\n          throw new RuntimeException(e);\n      }\n      //ipv6 loopback\n      allLocalIps.add(\"[::1]\");\n      return allLocalIps;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/SortedSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.*;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SortedSetCommandsTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bInclusiveB = { 0x5B, 0x0B };\n  final byte[] bExclusiveC = { 0x28, 0x0C };\n  final byte[] bLexMinusInf = { 0x2D };\n  final byte[] bLexPlusInf = { 0x2B };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public SortedSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void zadd() {\n    assertEquals(1, jedis.zadd(\"foo\", 1d, \"a\"));\n\n    assertEquals(1, jedis.zadd(\"foo\", 10d, \"b\"));\n\n    assertEquals(1, jedis.zadd(\"foo\", 0.1d, \"c\"));\n\n    assertEquals(0, jedis.zadd(\"foo\", 2d, \"a\"));\n\n    // Binary\n    assertEquals(1, jedis.zadd(bfoo, 1d, ba));\n\n    assertEquals(1, jedis.zadd(bfoo, 10d, bb));\n\n    assertEquals(1, jedis.zadd(bfoo, 0.1d, bc));\n\n    assertEquals(0, jedis.zadd(bfoo, 2d, ba));\n  }\n\n  @Test\n  public void zaddWithParams() {\n    jedis.del(\"foo\");\n\n    // xx: never add new member\n    assertEquals(0L, jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().xx()));\n\n    jedis.zadd(\"foo\", 1d, \"a\");\n    // nx: never update current member\n    assertEquals(0L, jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(1d), jedis.zscore(\"foo\", \"a\"));\n\n    Map<String, Double> scoreMembers = new HashMap<String, Double>();\n    scoreMembers.put(\"a\", 2d);\n    scoreMembers.put(\"b\", 1d);\n    // ch: return count of members not only added, but also updated\n    assertEquals(2L, jedis.zadd(\"foo\", scoreMembers, ZAddParams.zAddParams().ch()));\n\n    // lt: only update existing elements if the new score is less than the current score.\n    jedis.zadd(\"foo\", 3d, \"a\", ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"a\"));\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(\"foo\", \"a\"));\n\n    // gt: only update existing elements if the new score is greater than the current score.\n    jedis.zadd(\"foo\", 0d, \"b\", ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(\"foo\", \"b\"));\n    jedis.zadd(\"foo\", 2d, \"b\", ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"b\"));\n\n    // incr: don't update already existing elements.\n    assertNull(jedis.zaddIncr(\"foo\", 1d, \"b\", ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"b\"));\n    // incr: update elements that already exist.\n    assertEquals(Double.valueOf(3d), jedis.zaddIncr(\"foo\", 1d,\"b\", ZAddParams.zAddParams().xx()));\n    assertEquals(Double.valueOf(3d), jedis.zscore(\"foo\", \"b\"));\n\n    // binary\n    jedis.del(bfoo);\n\n    // xx: never add new member\n    assertEquals(0L, jedis.zadd(bfoo, 1d, ba, ZAddParams.zAddParams().xx()));\n\n    jedis.zadd(bfoo, 1d, ba);\n    // nx: never update current member\n    assertEquals(0L, jedis.zadd(bfoo, 2d, ba, ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(1d), jedis.zscore(bfoo, ba));\n\n    Map<byte[], Double> binaryScoreMembers = new HashMap<byte[], Double>();\n    binaryScoreMembers.put(ba, 2d);\n    binaryScoreMembers.put(bb, 1d);\n    // ch: return count of members not only added, but also updated\n    assertEquals(2L, jedis.zadd(bfoo, binaryScoreMembers, ZAddParams.zAddParams().ch()));\n\n    // lt: only update existing elements if the new score is less than the current score.\n    jedis.zadd(bfoo, 3d, ba, ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, ba));\n    jedis.zadd(bfoo, 1d, ba, ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(bfoo, ba));\n\n    // gt: only update existing elements if the new score is greater than the current score.\n    jedis.zadd(bfoo, 0d, bb, ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(bfoo, bb));\n    jedis.zadd(bfoo, 2d, bb, ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, bb));\n\n    // incr: don't update already existing elements.\n    assertNull(jedis.zaddIncr(bfoo, 1d, bb, ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, bb));\n    // incr: update elements that already exist.\n    assertEquals(Double.valueOf(3d), jedis.zaddIncr(bfoo, 1d, bb, ZAddParams.zAddParams().xx()));\n    assertEquals(Double.valueOf(3d), jedis.zscore(bfoo, bb));\n  }\n\n  @Test\n  public void zrange() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"a\");\n\n    List<String> range = jedis.zrange(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(\"b\");\n    range = jedis.zrange(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    List<byte[]> brange = jedis.zrange(bfoo, 0, 1);\n    assertByteArrayListEquals(bexpected, brange);\n\n    bexpected.add(bb);\n    brange = jedis.zrange(bfoo, 0, 100);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrangeByLex() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"bb\");\n    jedis.zadd(\"foo\", 1, \"d\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"bb\");\n    expected.add(\"c\");\n\n    // exclusive aa ~ inclusive c\n    assertEquals(expected, jedis.zrangeByLex(\"foo\", \"(aa\", \"[c\"));\n\n    expected.clear();\n    expected.add(\"bb\");\n    expected.add(\"c\");\n\n    // with LIMIT\n    assertEquals(expected, jedis.zrangeByLex(\"foo\", \"-\", \"+\", 1, 2));\n  }\n\n  @Test\n  public void zrangeByLexBinary() {\n    // binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    List<byte[]> bExpected = new ArrayList<byte[]>();\n    bExpected.add(bb);\n\n    assertByteArrayListEquals(bExpected, jedis.zrangeByLex(bfoo, bInclusiveB, bExclusiveC));\n\n    bExpected.clear();\n    bExpected.add(ba);\n    bExpected.add(bb);\n\n    // with LIMIT\n    assertByteArrayListEquals(bExpected, jedis.zrangeByLex(bfoo, bLexMinusInf, bLexPlusInf, 0, 2));\n  }\n\n  @Test\n  public void zrevrangeByLex() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"bb\");\n    jedis.zadd(\"foo\", 1, \"d\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"bb\");\n\n    // exclusive aa ~ inclusive c\n    assertEquals(expected, jedis.zrevrangeByLex(\"foo\", \"[c\", \"(aa\"));\n\n    expected.clear();\n    expected.add(\"c\");\n    expected.add(\"bb\");\n\n    // with LIMIT\n    assertEquals(expected, jedis.zrevrangeByLex(\"foo\", \"+\", \"-\", 1, 2));\n  }\n\n  @Test\n  public void zrevrangeByLexBinary() {\n    // binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    List<byte[]> bExpected = new ArrayList<byte[]>();\n    bExpected.add(bb);\n\n    assertByteArrayListEquals(bExpected, jedis.zrevrangeByLex(bfoo, bExclusiveC, bInclusiveB));\n\n    bExpected.clear();\n    bExpected.add(bc);\n    bExpected.add(bb);\n\n    // with LIMIT\n    assertByteArrayListEquals(bExpected, jedis.zrevrangeByLex(bfoo, bLexPlusInf, bLexMinusInf, 0, 2));\n  }\n\n  @Test\n  public void zrevrange() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"a\");\n\n    List<String> range = jedis.zrevrange(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(\"c\");\n    range = jedis.zrevrange(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n\n    List<byte[]> brange = jedis.zrevrange(bfoo, 0, 1);\n    assertByteArrayListEquals(bexpected, brange);\n\n    bexpected.add(bc);\n    brange = jedis.zrevrange(bfoo, 0, 100);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrem() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    assertEquals(1, jedis.zrem(\"foo\", \"a\"));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    assertEquals(0, jedis.zrem(\"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    assertEquals(1, jedis.zrem(bfoo, ba));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n\n    assertEquals(0, jedis.zrem(bfoo, bbar));\n  }\n\n  @Test\n  public void zincrby() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    assertEquals(3d, jedis.zincrby(\"foo\", 2d, \"a\"), 0);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"a\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    assertEquals(3d, jedis.zincrby(bfoo, 2d, ba), 0);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void zincrbyWithParams() {\n    jedis.del(\"foo\");\n\n    // xx: never add new member\n    assertNull(jedis.zincrby(\"foo\", 2d, \"a\", ZIncrByParams.zIncrByParams().xx()));\n\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    // nx: never update current member\n    assertNull(jedis.zincrby(\"foo\", 1d, \"a\", ZIncrByParams.zIncrByParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"a\"));\n\n    // Binary\n\n    jedis.del(bfoo);\n\n    // xx: never add new member\n    assertNull(jedis.zincrby(bfoo, 2d, ba, ZIncrByParams.zIncrByParams().xx()));\n\n    jedis.zadd(bfoo, 2d, ba);\n\n    // nx: never update current member\n    assertNull(jedis.zincrby(bfoo, 1d, ba, ZIncrByParams.zIncrByParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, ba));\n  }\n\n  @Test\n  public void zrank() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    long rank = jedis.zrank(\"foo\", \"a\");\n    assertEquals(0, rank);\n\n    rank = jedis.zrank(\"foo\", \"b\");\n    assertEquals(1, rank);\n\n    assertNull(jedis.zrank(\"car\", \"b\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    long brank = jedis.zrank(bfoo, ba);\n    assertEquals(0, brank);\n\n    brank = jedis.zrank(bfoo, bb);\n    assertEquals(1, brank);\n\n    assertNull(jedis.zrank(bcar, bb));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.2.0\")\n  public void zrankWithScore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    KeyValue<Long, Double> keyValue = jedis.zrankWithScore(\"foo\", \"a\");\n    assertEquals(Long.valueOf(0), keyValue.getKey());\n    assertEquals(Double.valueOf(1d), keyValue.getValue());\n\n    keyValue = jedis.zrankWithScore(\"foo\", \"b\");\n    assertEquals(Long.valueOf(1), keyValue.getKey());\n    assertEquals(Double.valueOf(2d), keyValue.getValue());\n\n    assertNull(jedis.zrankWithScore(\"car\", \"b\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    keyValue = jedis.zrankWithScore(bfoo, ba);\n    assertEquals(Long.valueOf(0), keyValue.getKey());\n    assertEquals(Double.valueOf(1d), keyValue.getValue());\n\n    keyValue = jedis.zrankWithScore(bfoo, bb);\n    assertEquals(Long.valueOf(1), keyValue.getKey());\n    assertEquals(Double.valueOf(2d), keyValue.getValue());\n\n    assertNull(jedis.zrankWithScore(bcar, bb));\n  }\n\n  @Test\n  public void zrevrank() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    long rank = jedis.zrevrank(\"foo\", \"a\");\n    assertEquals(1, rank);\n\n    rank = jedis.zrevrank(\"foo\", \"b\");\n    assertEquals(0, rank);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    long brank = jedis.zrevrank(bfoo, ba);\n    assertEquals(1, brank);\n\n    brank = jedis.zrevrank(bfoo, bb);\n    assertEquals(0, brank);\n  }\n\n  @Test\n  public void zrangeWithScores() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"a\", 2d));\n\n    List<Tuple> range = jedis.zrangeWithScores(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(new Tuple(\"b\", 10d));\n    range = jedis.zrangeWithScores(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    List<Tuple> brange = jedis.zrangeWithScores(bfoo, 0, 1);\n    assertEquals(bexpected, brange);\n\n    bexpected.add(new Tuple(bb, 10d));\n    brange = jedis.zrangeWithScores(bfoo, 0, 100);\n    assertEquals(bexpected, brange);\n\n  }\n\n  @Test\n  public void zrevrangeWithScores() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"b\", 10d));\n    expected.add(new Tuple(\"a\", 2d));\n\n    List<Tuple> range = jedis.zrevrangeWithScores(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(new Tuple(\"c\", 0.1d));\n    range = jedis.zrevrangeWithScores(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bb, 10d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    List<Tuple> brange = jedis.zrevrangeWithScores(bfoo, 0, 1);\n    assertEquals(bexpected, brange);\n\n    bexpected.add(new Tuple(bc, 0.1d));\n    brange = jedis.zrevrangeWithScores(bfoo, 0, 100);\n    assertEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zcard() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(3, jedis.zcard(\"foo\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(3, jedis.zcard(bfoo));\n  }\n\n  @Test\n  public void zscore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals((Double) 10d, jedis.zscore(\"foo\", \"b\"));\n\n    assertEquals((Double) 0.1d, jedis.zscore(\"foo\", \"c\"));\n\n    assertNull(jedis.zscore(\"foo\", \"s\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals((Double) 10d, jedis.zscore(bfoo, bb));\n\n    assertEquals((Double) 0.1d, jedis.zscore(bfoo, bc));\n\n    assertNull(jedis.zscore(bfoo, SafeEncoder.encode(\"s\")));\n\n  }\n\n  @Test\n  public void zmscore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(Arrays.asList(10d, 0.1d, null), jedis.zmscore(\"foo\", \"b\", \"c\", \"s\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(Arrays.asList(10d, 0.1d, null),\n      jedis.zmscore(bfoo, bb, bc, SafeEncoder.encode(\"s\")));\n  }\n\n  @Test\n  public void zpopmax() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"d\");\n\n    Tuple actual = jedis.zpopmax(\"foo\");\n    Tuple expected = new Tuple(\"b\", 10d);\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\");\n    expected = new Tuple(\"d\", 2d);\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\");\n    expected = new Tuple(\"a\", 1d);\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\");\n    expected = new Tuple(\"c\", 0.1d);\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(\"foo\");\n    assertNull(actual);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    // First\n    actual = jedis.zpopmax(bfoo);\n    expected = new Tuple(bb, 10d);\n    assertEquals(expected, actual);\n\n    // Second\n    actual = jedis.zpopmax(bfoo);\n    expected = new Tuple(ba, 2d);\n    assertEquals(expected, actual);\n\n    // Third\n    actual = jedis.zpopmax(bfoo);\n    expected = new Tuple(bc, 0.1d);\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(bfoo);\n    assertNull(actual);\n  }\n\n  @Test\n  public void zpopmaxWithCount() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"d\");\n    jedis.zadd(\"foo\", 0.03, \"e\");\n\n    List<Tuple> actual = jedis.zpopmax(\"foo\", 2);\n    assertEquals(2, actual.size());\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"b\", 10d));\n    expected.add(new Tuple(\"d\", 2d));\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\", 3);\n    assertEquals(3, actual.size());\n\n    expected.clear();\n    expected.add(new Tuple(\"a\", 1d));\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"e\", 0.03d));\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(\"foo\", 1);\n    expected.clear();\n    assertEquals(expected, actual);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    // First\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    expected.add(new Tuple(bb, 10d));\n    assertEquals(expected, actual);\n\n    // Second\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    expected.add(new Tuple(ba, 2d));\n    assertEquals(expected, actual);\n\n    // Last 2 (just 1, because 1 was overwritten)\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    expected.add(new Tuple(bc, 0.1d));\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    assertEquals(expected, actual);\n  }\n\n  @Test\n  public void zpopmin() {\n\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    List<Tuple> range = jedis.zpopmin(\"foo\", 2);\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"a\", 1d));\n\n    assertEquals(expected, range);\n\n    assertEquals(new Tuple(\"b\", 10d), jedis.zpopmin(\"foo\"));\n\n    // Binary\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> brange = jedis.zpopmin(bfoo, 2);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n    assertEquals(new Tuple(bb, 10d), jedis.zpopmin(bfoo));\n  }\n\n  @Test\n  public void zcount() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(2, jedis.zcount(\"foo\", 0.01d, 2.1d));\n\n    assertEquals(3, jedis.zcount(\"foo\", \"(0.01\", \"+inf\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(2, jedis.zcount(bfoo, 0.01d, 2.1d));\n\n    assertEquals(3, jedis.zcount(bfoo, SafeEncoder.encode(\"(0.01\"), SafeEncoder.encode(\"+inf\")));\n  }\n\n  @Test\n  public void zlexcount() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 1, \"b\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"aa\");\n\n    assertEquals(2, jedis.zlexcount(\"foo\", \"[aa\", \"(c\"));\n\n    assertEquals(4, jedis.zlexcount(\"foo\", \"-\", \"+\"));\n\n    assertEquals(3, jedis.zlexcount(\"foo\", \"-\", \"(c\"));\n\n    assertEquals(3, jedis.zlexcount(\"foo\", \"[aa\", \"+\"));\n  }\n\n  @Test\n  public void zlexcountBinary() {\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    assertEquals(1, jedis.zlexcount(bfoo, bInclusiveB, bExclusiveC));\n\n    assertEquals(3, jedis.zlexcount(bfoo, bLexMinusInf, bLexPlusInf));\n  }\n\n  @Test\n  public void zrangebyscore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<String> range = jedis.zrangeByScore(\"foo\", 0d, 2d);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"a\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScore(\"foo\", 0d, 2d, 0, 1);\n\n    expected = new ArrayList<String>();\n    expected.add(\"c\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScore(\"foo\", 0d, 2d, 1, 1);\n    List<String> range2 = jedis.zrangeByScore(\"foo\", \"-inf\", \"(2\");\n    assertEquals(expected, range2);\n\n    expected = new ArrayList<String>();\n    expected.add(\"a\");\n\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> brange = jedis.zrangeByScore(bfoo, 0d, 2d);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScore(bfoo, 0d, 2d, 0, 1);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScore(bfoo, 0d, 2d, 1, 1);\n    List<byte[]> brange2 = jedis.zrangeByScore(bfoo, SafeEncoder.encode(\"-inf\"),\n      SafeEncoder.encode(\"(2\"));\n    assertByteArrayListEquals(bexpected, brange2);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n  }\n\n  @Test\n  public void zrevrangebyscore() {\n    jedis.zadd(\"foo\", 1.0d, \"a\");\n    jedis.zadd(\"foo\", 2.0d, \"b\");\n    jedis.zadd(\"foo\", 3.0d, \"c\");\n    jedis.zadd(\"foo\", 4.0d, \"d\");\n    jedis.zadd(\"foo\", 5.0d, \"e\");\n\n    List<String> range = jedis.zrevrangeByScore(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"b\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", 4d, 2d);\n    expected = new ArrayList<String>();\n    expected.add(\"d\");\n    expected.add(\"c\");\n    expected.add(\"b\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", \"4\", \"2\", 0, 2);\n    expected = new ArrayList<String>();\n    expected.add(\"d\");\n    expected.add(\"c\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", \"+inf\", \"(4\");\n    expected = new ArrayList<String>();\n    expected.add(\"e\");\n\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> brange = jedis.zrevrangeByScore(bfoo, 2d, 0d);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.zrevrangeByScore(bfoo, 2d, 0d, 0, 1);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    List<byte[]> brange2 = jedis.zrevrangeByScore(bfoo, SafeEncoder.encode(\"+inf\"),\n      SafeEncoder.encode(\"(2\"));\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, brange2);\n\n    brange = jedis.zrevrangeByScore(bfoo, 2d, 0d, 1, 1);\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrangebyscoreWithScores() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<Tuple> range = jedis.zrangeByScoreWithScores(\"foo\", 0d, 2d);\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"a\", 2d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScoreWithScores(\"foo\", 0d, 2d, 0, 1);\n\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScoreWithScores(\"foo\", 0d, 2d, 1, 1);\n\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", 2d));\n\n    assertEquals(expected, range);\n\n    // Binary\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> brange = jedis.zrangeByScoreWithScores(bfoo, 0d, 2d);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScoreWithScores(bfoo, 0d, 2d, 0, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScoreWithScores(bfoo, 0d, 2d, 1, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrangeParams() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"bb\");\n    jedis.zadd(\"foo\", 1, \"d\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"bb\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", ZRangeParams.zrangeByLexParams(\"[c\", \"(aa\").rev()));\n    assertNotNull(jedis.zrangeWithScores(\"foo\", ZRangeParams.zrangeByScoreParams(0, 1)));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    List<byte[]> bExpected = new ArrayList<byte[]>();\n    bExpected.add(bb);\n\n    assertByteArrayListEquals(bExpected, jedis.zrange(bfoo, ZRangeParams.zrangeByLexParams(bExclusiveC, bInclusiveB).rev()));\n    assertNotNull(jedis.zrangeWithScores(bfoo, ZRangeParams.zrangeByScoreParams(0, 1).limit(0, 3)));\n  }\n\n  @Test\n  public void zrangeParamsLongMinMax() {\n\n    long min = 0;\n    long max = 1;\n\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"a\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", ZRangeParams.zrangeParams(min, max).rev()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zrangestore() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 2, \"c\");\n    jedis.zadd(\"foo\", 3, \"bb\");\n\n    long stored = jedis.zrangestore(\"bar\", \"foo\", ZRangeParams.zrangeByScoreParams(1, 2));\n    assertEquals(2, stored);\n\n    List<String> range = jedis.zrange(\"bar\", 0, -1);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"aa\");\n    expected.add(\"c\");\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    long bstored = jedis.zrangestore(bbar, bfoo, ZRangeParams.zrangeParams(0, 1).rev());\n    assertEquals(2, bstored);\n\n    List<byte[]> brange = jedis.zrevrange(bbar, 0, 1);\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrevrangebyscoreWithScores() {\n    jedis.zadd(\"foo\", 1.0d, \"a\");\n    jedis.zadd(\"foo\", 2.0d, \"b\");\n    jedis.zadd(\"foo\", 3.0d, \"c\");\n    jedis.zadd(\"foo\", 4.0d, \"d\");\n    jedis.zadd(\"foo\", 5.0d, \"e\");\n\n    List<Tuple> range = jedis.zrevrangeByScoreWithScores(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 3.0d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScoreWithScores(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 3.0d));\n    expected.add(new Tuple(\"b\", 2.0d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScoreWithScores(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"b\", 2.0d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScoreWithScores(\"foo\", 4d, 2d);\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"d\", 4.0d));\n    expected.add(new Tuple(\"c\", 3.0d));\n    expected.add(new Tuple(\"b\", 2.0d));\n\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> brange = jedis.zrevrangeByScoreWithScores(bfoo, 2d, 0d);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, 2d));\n    bexpected.add(new Tuple(bc, 0.1d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrevrangeByScoreWithScores(bfoo, 2d, 0d, 0, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrevrangeByScoreWithScores(bfoo, 2d, 0d, 1, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n\n    assertEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zremrangeByRank() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(1, jedis.zremrangeByRank(\"foo\", 0, 0));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(1, jedis.zremrangeByRank(bfoo, 0, 0));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n\n  }\n\n  @Test\n  public void zremrangeByScore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(2, jedis.zremrangeByScore(\"foo\", 0, 2));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(2, jedis.zremrangeByScore(bfoo, 0, 2));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void zremrangeByScoreExclusive() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 0d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    assertEquals(1, jedis.zremrangeByScore(\"foo\", \"(0\", \"(2\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 0d, bc);\n    jedis.zadd(bfoo, 2d, bb);\n\n    assertEquals(1, jedis.zremrangeByScore(bfoo, \"(0\".getBytes(), \"(2\".getBytes()));\n  }\n\n  @Test\n  public void zremrangeByLex() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 1, \"b\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"aa\");\n\n    assertEquals(2, jedis.zremrangeByLex(\"foo\", \"[aa\", \"(c\"));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"c\");\n\n    assertEquals(expected, jedis.zrangeByLex(\"foo\", \"-\", \"+\"));\n  }\n\n  @Test\n  public void zremrangeByLexBinary() {\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    assertEquals(1, jedis.zremrangeByLex(bfoo, bInclusiveB, bExclusiveC));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, jedis.zrangeByLex(bfoo, bLexMinusInf, bLexPlusInf));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunion() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    List<String> expected = new ArrayList<>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    assertEquals(expected, jedis.zunion(params, \"foo\", \"bar\"));\n\n    List<Tuple> expectedTuple = new ArrayList<>();\n    expectedTuple.add(new Tuple(\"a\", new Double(7)));\n    expectedTuple.add(new Tuple(\"b\", new Double(9)));\n    assertEquals(expectedTuple, jedis.zunionWithScores(params, \"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(ba);\n    bexpected.add(bb);\n    AssertUtil.assertByteArrayListEquals(bexpected, jedis.zunion(params, bfoo, bbar));\n\n    List<Tuple> bexpectedTuple = new ArrayList<>();\n    bexpectedTuple.add(new Tuple(ba, new Double(7)));\n    bexpectedTuple.add(new Tuple(bb, new Double(9)));\n    assertEquals(bexpectedTuple, jedis.zunionWithScores(params, bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunionstore() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 2, \"b\");\n\n    assertEquals(2, jedis.zunionstore(\"dst\", \"foo\", \"bar\"));\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", new Double(3)));\n    expected.add(new Tuple(\"b\", new Double(4)));\n\n    assertEquals(expected, jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    assertEquals(2, jedis.zunionstore(SafeEncoder.encode(\"dst\"), bfoo, bbar));\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, new Double(3)));\n    bexpected.add(new Tuple(bb, new Double(4)));\n\n    assertEquals(bexpected, jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunionstoreParams() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(2, jedis.zunionstore(\"dst\", params, \"foo\", \"bar\"));\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", new Double(7)));\n    expected.add(new Tuple(\"b\", new Double(9)));\n\n    assertEquals(expected, jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(2, jedis.zunionstore(SafeEncoder.encode(\"dst\"), bparams, bfoo, bbar));\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, new Double(7)));\n    bexpected.add(new Tuple(bb, new Double(9)));\n\n    assertEquals(bexpected, jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zinter() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n    assertEquals(singletonList(\"a\"), jedis.zinter(params, \"foo\", \"bar\"));\n\n    assertEquals(singletonList(new Tuple(\"a\", new Double(7))),\n      jedis.zinterWithScores(params, \"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n    AssertUtil.assertByteArrayListEquals(singletonList(ba), jedis.zinter(params, bfoo, bbar));\n\n    assertEquals(singletonList(new Tuple(ba, new Double(7))),\n      jedis.zinterWithScores(bparams, bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zinterstore() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n\n    assertEquals(1, jedis.zinterstore(\"dst\", \"foo\", \"bar\"));\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", new Double(3)));\n\n    assertEquals(expected, jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n\n    assertEquals(1, jedis.zinterstore(SafeEncoder.encode(\"dst\"), bfoo, bbar));\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, new Double(3)));\n\n    assertEquals(bexpected, jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zintertoreParams() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(1, jedis.zinterstore(\"dst\", params, \"foo\", \"bar\"));\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", new Double(7)));\n\n    assertEquals(expected, jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(1, jedis.zinterstore(SafeEncoder.encode(\"dst\"), bparams, bfoo, bbar));\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, new Double(7)));\n\n    assertEquals(bexpected, jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zintercard() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 1, \"b\");\n\n    assertEquals(2, jedis.zintercard(\"foo\", \"bar\"));\n    assertEquals(1, jedis.zintercard(1, \"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    assertEquals(2, jedis.zintercard(bfoo, bbar));\n    assertEquals(1, jedis.zintercard(1, bfoo, bbar));\n  }\n\n  @Test\n  public void zscan() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n\n    ScanResult<Tuple> result = jedis.zscan(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bb);\n\n    ScanResult<Tuple> bResult = jedis.zscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void zscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 11, \"aa\");\n    ScanResult<Tuple> result = jedis.zscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.zadd(bfoo, 2, bbar1);\n    jedis.zadd(bfoo, 1, bbar2);\n    jedis.zadd(bfoo, 11, bbar3);\n    ScanResult<Tuple> bResult = jedis.zscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n\n  }\n\n  @Test\n  public void zscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    jedis.zadd(\"foo\", 1, \"a1\");\n    jedis.zadd(\"foo\", 2, \"a2\");\n    jedis.zadd(\"foo\", 3, \"a3\");\n    jedis.zadd(\"foo\", 4, \"a4\");\n    jedis.zadd(\"foo\", 5, \"a5\");\n\n    ScanResult<Tuple> result = jedis.zscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.zadd(bfoo, 2, bbar1);\n    jedis.zadd(bfoo, 1, bbar2);\n    jedis.zadd(bfoo, 11, bbar3);\n\n    ScanResult<Tuple> bResult = jedis.zscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void infinity() {\n    jedis.zadd(\"key\", Double.POSITIVE_INFINITY, \"pos\");\n    assertEquals(Double.POSITIVE_INFINITY, jedis.zscore(\"key\", \"pos\"), 0d);\n    jedis.zadd(\"key\", Double.NEGATIVE_INFINITY, \"neg\");\n    assertEquals(Double.NEGATIVE_INFINITY, jedis.zscore(\"key\", \"neg\"), 0d);\n    jedis.zadd(\"key\", 0d, \"zero\");\n\n    List<Tuple> set = jedis.zrangeWithScores(\"key\", 0, -1);\n    Iterator<Tuple> itr = set.iterator();\n    assertEquals(Double.NEGATIVE_INFINITY, itr.next().getScore(), 0d);\n    assertEquals(0d, itr.next().getScore(), 0d);\n    assertEquals(Double.POSITIVE_INFINITY, itr.next().getScore(), 0d);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bzpopmax() {\n    assertNull(jedis.bzpopmax(1, \"foo\", \"bar\"));\n\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"bar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    assertEquals(new KeyValue<>(\"foo\", new Tuple(\"b\", 10d)), jedis.bzpopmax(0, \"foo\", \"bar\"));\n\n    // Binary\n    assertNull(jedis.bzpopmax(1, bfoo, bbar));\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bbar, 0.1d, bc);\n    KeyValue<byte[], Tuple> actual = jedis.bzpopmax(0, bfoo, bbar);\n    assertArrayEquals(bfoo, actual.getKey());\n    assertEquals(new Tuple(bb, 10d), actual.getValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bzpopmin() {\n    assertNull(jedis.bzpopmin(1, \"bar\", \"foo\"));\n\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"bar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    assertEquals(new KeyValue<>(\"bar\", new Tuple(\"c\", 0.1)), jedis.bzpopmin(0, \"bar\", \"foo\"));\n\n    // Binary\n    assertNull(jedis.bzpopmin(1, bbar, bfoo));\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bbar, 0.1d, bc);\n    KeyValue<byte[], Tuple> actual = jedis.bzpopmin(0, bbar, bfoo);\n    assertArrayEquals(bbar, (byte[]) actual.getKey());\n    assertEquals(new Tuple(bc, 0.1), actual.getValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zdiff() {\n    jedis.zadd(\"foo\", 1.0, \"a\");\n    jedis.zadd(\"foo\", 2.0, \"b\");\n    jedis.zadd(\"bar\", 1.0, \"a\");\n\n    assertEquals(0, jedis.zdiff(\"bar1\", \"bar2\").size());\n    assertEquals(singletonList(\"b\"), jedis.zdiff(\"foo\", \"bar\"));\n    assertEquals(singletonList(new Tuple(\"b\", 2.0d)), jedis.zdiffWithScores(\"foo\", \"bar\"));\n\n    // binary\n\n    jedis.zadd(bfoo, 1.0, ba);\n    jedis.zadd(bfoo, 2.0, bb);\n    jedis.zadd(bbar, 1.0, ba);\n\n    assertEquals(0, jedis.zdiff(bbar1, bbar2).size());\n    List<byte[]> bactual = jedis.zdiff(bfoo, bbar);\n    assertEquals(1, bactual.size());\n    assertArrayEquals(bb, bactual.iterator().next());\n    assertEquals(singletonList(new Tuple(bb, 2.0d)), jedis.zdiffWithScores(bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zdiffstore() {\n    jedis.zadd(\"foo\", 1.0, \"a\");\n    jedis.zadd(\"foo\", 2.0, \"b\");\n    jedis.zadd(\"bar\", 1.0, \"a\");\n\n    assertEquals(0, jedis.zdiffstore(\"bar3\", \"bar1\", \"bar2\"));\n    assertEquals(1, jedis.zdiffstore(\"bar3\", \"foo\", \"bar\"));\n    assertEquals(singletonList(\"b\"), jedis.zrange(\"bar3\", 0, -1));\n\n    // binary\n\n    jedis.zadd(bfoo, 1.0, ba);\n    jedis.zadd(bfoo, 2.0, bb);\n    jedis.zadd(bbar, 1.0, ba);\n\n    assertEquals(0, jedis.zdiffstore(bbar3, bbar1, bbar2));\n    assertEquals(1, jedis.zdiffstore(bbar3, bfoo, bbar));\n    List<byte[]> bactual = jedis.zrange(bbar3, 0, -1);\n    assertArrayEquals(bb, bactual.iterator().next());\n  }\n\n  @Test\n  public void zrandmember() {\n    assertNull(jedis.zrandmember(\"foo\"));\n    assertEquals(Collections.emptyList(), jedis.zrandmember(\"foo\", 1));\n    assertEquals(Collections.emptyList(), jedis.zrandmemberWithScores(\"foo\", 1));\n\n    Map<String, Double> hash = new HashMap<>();\n    hash.put(\"bar1\", 1d);\n    hash.put(\"bar2\", 10d);\n    hash.put(\"bar3\", 0.1d);\n    jedis.zadd(\"foo\", hash);\n\n    AssertUtil.assertCollectionContains(hash.keySet(), jedis.zrandmember(\"foo\"));\n    assertEquals(2, jedis.zrandmember(\"foo\", 2).size());\n\n    List<Tuple> actual = jedis.zrandmemberWithScores(\"foo\", 2);\n    assertEquals(2, actual.size());\n    actual.forEach(t -> assertEquals(hash.get(t.getElement()), t.getScore(), 0d));\n\n    // Binary\n    assertNull(jedis.zrandmember(bfoo));\n    assertEquals(Collections.emptyList(), jedis.zrandmember(bfoo, 1));\n    assertEquals(Collections.emptyList(), jedis.zrandmemberWithScores(bfoo, 1));\n\n    Map<byte[], Double> bhash = new HashMap<>();\n    bhash.put(bbar1, 1d);\n    bhash.put(bbar2, 10d);\n    bhash.put(bbar3, 0.1d);\n    jedis.zadd(bfoo, bhash);\n\n    AssertUtil.assertByteArrayCollectionContains(bhash.keySet(), jedis.zrandmember(bfoo));\n    assertEquals(2, jedis.zrandmember(bfoo, 2).size());\n\n    List<Tuple> bactual = jedis.zrandmemberWithScores(bfoo, 2);\n    assertEquals(2, bactual.size());\n    bactual.forEach(t -> assertEquals(getScoreFromByteMap(bhash, t.getBinaryElement()), t.getScore(), 0d));\n  }\n\n  private Double getScoreFromByteMap(Map<byte[], Double> bhash, byte[] key) {\n    for (Map.Entry<byte[], Double> en : bhash.entrySet()) {\n      if (Arrays.equals(en.getKey(), key)) {\n        return en.getValue();\n      }\n    }\n    return null;\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void zmpop() {\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    KeyValue<String, List<Tuple>> single = jedis.zmpop(SortedSetOption.MAX, \"foo\");\n    KeyValue<String, List<Tuple>> range = jedis.zmpop(SortedSetOption.MIN, 2, \"foo\");\n\n    assertEquals(new Tuple(\"b\", 10d), single.getValue().get(0));\n    assertEquals(2, range.getValue().size());\n    assertNull(jedis.zmpop(SortedSetOption.MAX, \"foo\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  public void bzmpopSimple() {\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    KeyValue<String, List<Tuple>> single = jedis.bzmpop(1L, SortedSetOption.MAX, \"foo\");\n    KeyValue<String, List<Tuple>> range = jedis.bzmpop(1L, SortedSetOption.MIN, 2, \"foo\");\n\n    assertEquals(new Tuple(\"b\", 10d), single.getValue().get(0));\n    assertEquals(2, range.getValue().size());\n    assertNull(jedis.bzmpop(1L, SortedSetOption.MAX, \"foo\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/SortingCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class SortingCommandsTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bfoodest = { 0x01, 0x02, 0x03, 0x04, 0x05 };\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, '1' };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, '2' };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, '3' };\n  final byte[] bbar10 = { 0x05, 0x06, 0x07, 0x08, '1', '0' };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n  final byte[] bcar1 = { 0x0A, 0x0B, 0x0C, 0x0D, '1' };\n  final byte[] bcar2 = { 0x0A, 0x0B, 0x0C, 0x0D, '2' };\n  final byte[] bcar10 = { 0x0A, 0x0B, 0x0C, 0x0D, '1', '0' };\n  final byte[] bcarstar = { 0x0A, 0x0B, 0x0C, 0x0D, '*' };\n  final byte[] b1 = { '1' };\n  final byte[] b2 = { '2' };\n  final byte[] b3 = { '3' };\n  final byte[] b10 = { '1', '0' };\n\n  public SortingCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void sort() {\n    jedis.lpush(\"foo\", \"3\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"1\");\n\n    List<String> result = jedis.sort(\"foo\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"1\");\n    expected.add(\"2\");\n    expected.add(\"3\");\n\n    assertEquals(expected, result);\n\n    // Binary\n    jedis.lpush(bfoo, b3);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b1);\n\n    List<byte[]> bresult = jedis.sort(bfoo);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b1);\n    bexpected.add(b2);\n    bexpected.add(b3);\n\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sortBy() {\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n    jedis.lpush(\"foo\", \"1\");\n\n    jedis.set(\"bar1\", \"3\");\n    jedis.set(\"bar2\", \"2\");\n    jedis.set(\"bar3\", \"1\");\n\n    SortingParams sp = new SortingParams();\n    sp.by(\"bar*\");\n\n    List<String> result = jedis.sort(\"foo\", sp);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"3\");\n    expected.add(\"2\");\n    expected.add(\"1\");\n\n    assertEquals(expected, result);\n    \n    // Sort to dest key\n    long resultCount = jedis.sort(\"foo\", sp, \"foodest\");\n    assertEquals(3L, resultCount);\n\n    result = jedis.lpop(\"foodest\", 5);\n    assertEquals(expected, result);\n    \n    // Binary\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n    jedis.lpush(bfoo, b1);\n\n    jedis.set(bbar1, b3);\n    jedis.set(bbar2, b2);\n    jedis.set(bbar3, b1);\n\n    SortingParams bsp = new SortingParams();\n    bsp.by(bbarstar);\n\n    List<byte[]> bresult = jedis.sort(bfoo, bsp);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b3);\n    bexpected.add(b2);\n    bexpected.add(b1);\n\n    assertByteArrayListEquals(bexpected, bresult);\n    \n    // Sort to dest key\n    resultCount = jedis.sort(bfoo, sp, bfoodest);\n    assertEquals(3L, resultCount);\n\n    bresult = jedis.lpop(bfoodest, 5);\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n\n  @Test\n  public void sortDesc() {\n    jedis.lpush(\"foo\", \"3\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"1\");\n\n    SortingParams sp = new SortingParams();\n    sp.desc();\n\n    List<String> result = jedis.sort(\"foo\", sp);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"3\");\n    expected.add(\"2\");\n    expected.add(\"1\");\n\n    assertEquals(expected, result);\n\n    // Binary\n    jedis.lpush(bfoo, b3);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b1);\n\n    SortingParams bsp = new SortingParams();\n    bsp.desc();\n\n    List<byte[]> bresult = jedis.sort(bfoo, bsp);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b3);\n    bexpected.add(b2);\n    bexpected.add(b1);\n\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n\n  @Test\n  public void sortLimit() {\n    for (int n = 10; n > 0; n--) {\n      jedis.lpush(\"foo\", String.valueOf(n));\n    }\n\n    SortingParams sp = new SortingParams();\n    sp.limit(0, 3);\n\n    List<String> result = jedis.sort(\"foo\", sp);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"1\");\n    expected.add(\"2\");\n    expected.add(\"3\");\n\n    assertEquals(expected, result);\n\n    // Binary\n    jedis.rpush(bfoo, new byte[] { (byte) '4' });\n    jedis.rpush(bfoo, new byte[] { (byte) '3' });\n    jedis.rpush(bfoo, new byte[] { (byte) '2' });\n    jedis.rpush(bfoo, new byte[] { (byte) '1' });\n\n    SortingParams bsp = new SortingParams();\n    bsp.limit(0, 3);\n\n    List<byte[]> bresult = jedis.sort(bfoo, bsp);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b1);\n    bexpected.add(b2);\n    bexpected.add(b3);\n\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n\n  @Test\n  public void sortAlpha() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"10\");\n\n    SortingParams sp = new SortingParams();\n    sp.alpha();\n\n    List<String> result = jedis.sort(\"foo\", sp);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"1\");\n    expected.add(\"10\");\n    expected.add(\"2\");\n\n    assertEquals(expected, result);\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b10);\n\n    SortingParams bsp = new SortingParams();\n    bsp.alpha();\n\n    List<byte[]> bresult = jedis.sort(bfoo, bsp);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b1);\n    bexpected.add(b10);\n    bexpected.add(b2);\n\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n\n  @Test\n  public void sortGet() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"10\");\n\n    jedis.set(\"bar1\", \"bar1\");\n    jedis.set(\"bar2\", \"bar2\");\n    jedis.set(\"bar10\", \"bar10\");\n\n    jedis.set(\"car1\", \"car1\");\n    jedis.set(\"car2\", \"car2\");\n    jedis.set(\"car10\", \"car10\");\n\n    SortingParams sp = new SortingParams();\n    sp.get(\"car*\", \"bar*\");\n\n    List<String> result = jedis.sort(\"foo\", sp);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"car1\");\n    expected.add(\"bar1\");\n    expected.add(\"car2\");\n    expected.add(\"bar2\");\n    expected.add(\"car10\");\n    expected.add(\"bar10\");\n\n    assertEquals(expected, result);\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b10);\n\n    jedis.set(bbar1, bbar1);\n    jedis.set(bbar2, bbar2);\n    jedis.set(bbar10, bbar10);\n\n    jedis.set(bcar1, bcar1);\n    jedis.set(bcar2, bcar2);\n    jedis.set(bcar10, bcar10);\n\n    SortingParams bsp = new SortingParams();\n    bsp.get(bcarstar, bbarstar);\n\n    List<byte[]> bresult = jedis.sort(bfoo, bsp);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bcar1);\n    bexpected.add(bbar1);\n    bexpected.add(bcar2);\n    bexpected.add(bbar2);\n    bexpected.add(bcar10);\n    bexpected.add(bbar10);\n\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sortStore() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"10\");\n\n    long result = jedis.sort(\"foo\", \"result\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"1\");\n    expected.add(\"2\");\n    expected.add(\"10\");\n\n    assertEquals(3, result);\n    assertEquals(expected, jedis.lrange(\"result\", 0, 1000));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b10);\n\n    byte[] bkresult = new byte[] { 0X09, 0x0A, 0x0B, 0x0C };\n    long bresult = jedis.sort(bfoo, bkresult);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b1);\n    bexpected.add(b2);\n    bexpected.add(b10);\n\n    assertEquals(3, bresult);\n    assertByteArrayListEquals(bexpected, jedis.lrange(bkresult, 0, 1000));\n  }\n\n  @Test\n  @EnabledOnCommand(\"SORT_RO\")\n  public void sort_ro() {\n    jedis.rpush(\"foo\", \"1\", \"3\", \"2\");\n\n    List<String> result = jedis.sortReadonly(\"foo\", new SortingParams().desc());\n    List<String> expected = new ArrayList<>();\n    expected.add(\"3\");\n    expected.add(\"2\");\n    expected.add(\"1\");\n\n    assertEquals(expected, result);\n\n    // Binary\n    jedis.rpush(bfoo, b3, b1, b2);\n\n    List<byte[]> bresult = jedis.sortReadonly(bfoo, new SortingParams().alpha());\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(b1);\n    bexpected.add(b2);\n    bexpected.add(b3);\n\n    assertByteArrayListEquals(bexpected, bresult);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XCfgSetParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.params.XTrimParams;\nimport redis.clients.jedis.resps.StreamEntryBinary;\nimport redis.clients.jedis.resps.StreamEntryDeletionResult;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static io.redis.test.utils.RedisVersion.V8_4_0_STRING;\nimport static java.util.Collections.singletonMap;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY;\nimport static redis.clients.jedis.util.StreamEntryBinaryListMatcher.equalsStreamEntries;\n\n/**\n * Test replicated from {@link redis.clients.jedis.commands.unified.StreamsBinaryCommandsTestBase}\n * but adapted to use Jedis commands. Note: Consider merging with the unified test class if\n * possible. e.g., by using a common base class\n */\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class StreamsBinaryCommandsTest extends JedisCommandsTestBase {\n\n  protected static final byte[] STREAM_KEY_1 = \"{binary-stream}-1\".getBytes();\n  protected static final byte[] STREAM_KEY_2 = \"{binary-stream}-2\".getBytes();\n  protected static final byte[] GROUP_NAME = \"group-1\".getBytes();\n  protected static final byte[] CONSUMER_NAME = \"consumer-1\".getBytes();\n\n  protected static final byte[] FIELD_KEY_1 = \"binary-field-1\".getBytes();\n  // Test with invalid UTF-8 characters\n  protected static final byte[] BINARY_VALUE_1 = new byte[] { 0x00, 0x01, 0x02, 0x03, (byte) 0xFF };\n\n  protected static final byte[] FIELD_KEY_2 = \"binary-field-1\".getBytes();\n  protected static final byte[] BINARY_VALUE_2 = \"binary-value-2\".getBytes();\n  protected static final Map<byte[], byte[]> HASH_1 = singletonMap(FIELD_KEY_1, BINARY_VALUE_1);\n  protected static final Map<byte[], byte[]> HASH_2 = singletonMap(FIELD_KEY_2, BINARY_VALUE_2);\n\n  protected static final List<StreamEntryBinary> stream1Entries = new ArrayList<>();\n  protected static final List<StreamEntryBinary> stream2Entries = new ArrayList<>();\n\n  static {\n    stream1Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-1\"), HASH_1));\n    stream1Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-3\"), HASH_2));\n\n    stream2Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-2\"), HASH_1));\n  }\n\n  public StreamsBinaryCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  /**\n   * Creates a map of stream keys to StreamEntryID objects.\n   * @param streamOffsets Array of stream key and offset pairs\n   * @return Map of stream keys to StreamEntryID objects\n   */\n  public static Map<byte[], StreamEntryID> offsets(Object... streamOffsets) {\n    if (streamOffsets.length % 2 != 0) {\n      throw new IllegalArgumentException(\"Stream offsets must be provided as key-value pairs\");\n    }\n\n    Map<byte[], StreamEntryID> result = new HashMap<>();\n    for (int i = 0; i < streamOffsets.length; i += 2) {\n      byte[] key = (byte[]) streamOffsets[i];\n      Object value = streamOffsets[i + 1];\n\n      StreamEntryID id;\n      if (value instanceof String) {\n        id = new StreamEntryID((String) value);\n      } else if (value instanceof StreamEntryID) {\n        id = (StreamEntryID) value;\n      } else {\n        throw new IllegalArgumentException(\"Offset must be a String or StreamEntryID\");\n      }\n\n      result.put(key, id);\n    }\n\n    return result;\n  }\n\n  @BeforeEach\n  public void setUpTestStream() {\n    setUpTestStream(StreamEntryID.XGROUP_LAST_ENTRY.toString().getBytes());\n  }\n  \n  private void setUpTestStream(byte[] startId) {\n    jedis.del(STREAM_KEY_1);\n    jedis.del(STREAM_KEY_2);\n    try {\n      jedis.xgroupCreate(STREAM_KEY_1, GROUP_NAME, startId, true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n    try {\n      jedis.xgroupCreate(STREAM_KEY_2, GROUP_NAME, startId, true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n  }\n\n  @Test\n  public void xreadBinaryNoEntries() {\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadBinary(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertNull(actualEntries);\n  }\n\n  @Test\n  public void xreadBinary() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadBinary(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadBinaryCount() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadBinary(\n        XReadParams.xReadParams().count(1), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries.subList(0, 1)));\n  }\n\n  @Test\n  public void xreadBinaryAsMapNoEntries() {\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertNull(actualEntries);\n  }\n\n  @Test\n  public void xreadBinaryAsMap() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadBinaryAsMapCount() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams().count(1), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries.subList(0, 1)));\n  }\n\n  @Test\n  public void xreadBinaryAsMapWithMultipleStreams() {\n\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n    stream2Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_2, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\", STREAM_KEY_2, \"0-0\"));\n\n    assertThat(actualEntries.entrySet(), hasSize(2));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n    assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries));\n  }\n\n  @Test\n  public void xreadGroupBinary() {\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY));\n\n    // verify the result contains entries from one stream\n    // and is under the expected stream key\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadGroupBinaryAsMap() {\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadGroupBinaryAsMap(GROUP_NAME,\n        CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY));\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadGroupBinaryAsMapMultipleStreams() {\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n    stream2Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_2, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadGroupBinaryAsMap(GROUP_NAME,\n        CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY, STREAM_KEY_2,\n            XREADGROUP_UNDELIVERED_ENTRY));\n\n    assertThat(actualEntries.entrySet(), hasSize(2));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n    assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries));\n  }\n\n  // ========== XACKDEL Command Tests ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdel() {\n    // Add a message to the stream\n    byte[] messageId = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    assertNotNull(messageId);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Read the message with consumer group to add it to PEL\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(1, messages.get(0).getValue().size());\n    byte[] readMessageId = messages.get(0).getValue().get(0).getID().toString().getBytes();\n\n    // Test XACKDEL - should acknowledge and delete the message\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify message is deleted from stream\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdelWithTrimMode() {\n    // Add multiple messages\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Read the messages with consumer group\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Test XACKDEL with KEEP_REFERENCES mode\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamDeletionPolicy.KEEP_REFERENCES, readId1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify one message is deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdelUnreadMessages() {\n    // Add test entries but don't read them\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n\n    // Test XACKDEL on unread messages - should return NOT_FOUND for PEL\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1);\n\n    assertThat(results, hasSize(1));\n    // Should return NOT_FOUND because message was never read by the consumer group\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0));\n\n    // Stream should still contain the message\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdelMultipleMessages() {\n    // Add multiple messages\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"3-0\"), HASH_1);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Read the messages with consumer group\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(3, messages.get(0).getValue().size());\n\n    // Test XACKDEL with multiple IDs\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(1));\n\n    // Verify two messages are deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  // ========== XDELEX Command Tests ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelex() {\n    // Add test entries\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    byte[] id2 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES)\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify entry is deleted from stream\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexWithTrimMode() {\n    // Add test entries\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n\n    // Test XDELEX with DELETE_REFERENCES mode\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.DELETE_REFERENCES, id1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify entry is deleted from stream\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexMultipleEntries() {\n    // Add test entries\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    byte[] id3 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"3-0\"), HASH_1);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDELEX with multiple IDs\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1, id3);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(1));\n\n    // Verify two entries are deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexNonExistentEntries() {\n    // Add one entry\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDELEX with mix of existing and non-existent IDs\n    byte[] nonExistentId = \"999-0\".getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Existing entry\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(1)); // Non-existent entry\n\n    // Verify existing entry is deleted\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexWithConsumerGroups() {\n    // Add test entries\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Read messages with consumer group to add them to PEL\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Acknowledge only the first message\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes();\n    jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1);\n\n    // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged\n    assertEquals(StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED, results.get(1)); // id2 not acknowledged\n\n    // Verify only acknowledged entry was deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexEmptyStream() {\n    // Test XDELEX on empty stream\n    byte[] nonExistentId = \"1-0\".getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, nonExistentId);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0));\n  }\n\n  // ========== XTRIM Command Tests with trimmingMode ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXtrimWithKeepReferences() {\n    // Add test entries\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + \"-0\"), HASH_1);\n    }\n    assertEquals(5L, jedis.xlen(STREAM_KEY_1));\n\n    // Read messages with consumer group to create PEL entries\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    // Test XTRIM with KEEP_REFERENCES mode - should preserve PEL references\n    long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(\n        StreamDeletionPolicy.KEEP_REFERENCES));\n    assertEquals(2L, trimmed); // Should trim 2 entries\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXtrimWithAcknowledged() {\n    // Add test entries\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + \"-0\"), HASH_1);\n    }\n    assertEquals(5L, jedis.xlen(STREAM_KEY_1));\n\n    // Read messages with consumer group\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(3, messages.get(0).getValue().size());\n\n    // Acknowledge only the first 2 messages\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes();\n    jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2);\n\n    // Test XTRIM with ACKNOWLEDGED mode - should only trim acknowledged entries\n    long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(\n        StreamDeletionPolicy.ACKNOWLEDGED));\n    // The exact behavior depends on implementation, but it should respect acknowledgment status\n    assertTrue(trimmed >= 0);\n    assertTrue(jedis.xlen(STREAM_KEY_1) <= 5); // Should not exceed original length\n  }\n\n  // ========== XREADGROUP CLAIM Tests ==========\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimReturnsMetadataOrdered() throws InterruptedException {\n    setUpTestStream(\"0-0\".getBytes());\n\n    final byte[] CONSUMER_1 = \"consumer-1\".getBytes();\n    final byte[] CONSUMER_2 = \"consumer-2\".getBytes();\n    final long IDLE_TIME_MS = 5;\n\n    // Produce two entries\n    Map<byte[], byte[]> hash = singletonMap(FIELD_KEY_1, BINARY_VALUE_1);\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), hash);\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), hash);\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_1, XReadGroupParams.xReadGroupParams().count(10),\n        streams);\n\n    // Ensure idle time so entries are claimable\n    Thread.sleep(IDLE_TIME_MS);\n\n    // Produce fresh entries that are NOT claimed (not pending)\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), hash);\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), hash);\n\n    // Read with consumer-2 using CLAIM\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> consumer2Result = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_2, XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).count(10),\n        streams);\n\n    assertNotNull(consumer2Result);\n    assertEquals(1, consumer2Result.size());\n\n    List<StreamEntryBinary> entries = consumer2Result.get(0).getValue();\n    assertEquals(4, entries.size());\n\n    long claimedCount = entries.stream().filter(StreamEntryBinary::isClaimed).count();\n    long freshCount = entries.size() - claimedCount;\n\n    assertEquals(2, claimedCount);\n    assertEquals(2, freshCount);\n\n    // Assert order: pending entries are first\n    StreamEntryBinary first = entries.get(0);\n    StreamEntryBinary second = entries.get(1);\n    StreamEntryBinary third = entries.get(2);\n    StreamEntryBinary fourth = entries.get(3);\n\n    // Claimed entries\n    assertTrue(first.isClaimed());\n    assertTrue(second.isClaimed());\n    assertTrue(first.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n    assertTrue(second.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n\n    // Fresh entries\n    assertFalse(third.isClaimed());\n    assertFalse(fourth.isClaimed());\n    assertEquals(Long.valueOf(0), third.getDeliveredCount());\n    assertEquals(Long.valueOf(0), fourth.getDeliveredCount());\n    assertEquals(Long.valueOf(0), third.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(0), fourth.getMillisElapsedFromDelivery());\n  }\n\n  // ========== Idempotent Producer Tests ==========\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpAuto() {\n    // Add entry with IDMPAUTO\n    Map<byte[], byte[]> message = new HashMap<>();\n    message.put(\"order\".getBytes(), \"12345\".getBytes());\n    message.put(\"amount\".getBytes(), \"100.00\".getBytes());\n\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()),\n        message);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add same message again with same producer - should return same ID (duplicate detected)\n    byte[] id2 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()),\n        message);\n    assertArrayEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1)); // Stream length unchanged\n\n    // Add same message with different producer - should succeed\n    byte[] id3 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-2\".getBytes()),\n        message);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Add different message with same producer - should succeed\n    Map<byte[], byte[]> message2 = new HashMap<>();\n    message2.put(\"order\".getBytes(), \"67890\".getBytes());\n    message2.put(\"amount\".getBytes(), \"200.00\".getBytes());\n\n    byte[] id4 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()),\n        message2);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmp() {\n    Map<byte[], byte[]> hash1 = singletonMap(FIELD_KEY_1, BINARY_VALUE_1);\n    Map<byte[], byte[]> hash2 = singletonMap(FIELD_KEY_2, BINARY_VALUE_1);\n\n    // Add entry with explicit idempotent ID\n    byte[] id1 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\".getBytes(), \"iid-001\".getBytes()), hash1);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with same producer and idempotent ID - should return same ID (duplicate detected)\n    byte[] id2 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\".getBytes(), \"iid-001\".getBytes()), hash2);\n    assertArrayEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with same producer but different idempotent ID - should succeed\n    byte[] id3 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\".getBytes(), \"iid-002\".getBytes()), hash1);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with different producer but same idempotent ID - should succeed\n    byte[] id4 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-2\".getBytes(), \"iid-001\".getBytes()), hash1);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXcfgset() {\n    // Add an entry to create the stream\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams(), singletonMap(FIELD_KEY_1, BINARY_VALUE_1));\n\n    // Configure idempotent producer settings\n    byte[] result = jedis.xcfgset(STREAM_KEY_1,\n        XCfgSetParams.xCfgSetParams().idmpDuration(1000).idmpMaxsize(500));\n    assertArrayEquals(\"OK\".getBytes(), result);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static io.redis.test.utils.RedisVersion.V8_4_0_STRING;\nimport static java.util.Collections.singleton;\nimport static java.util.Collections.singletonMap;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.utils.RedisVersion;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.Pipeline;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.Transaction;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class StreamsCommandsTest extends JedisCommandsTestBase {\n\n  public StreamsCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void xadd() {\n\n    try {\n      Map<String, String> map1 = new HashMap<>();\n      jedis.xadd(\"stream1\", (StreamEntryID) null, map1);\n      fail();\n    } catch (JedisDataException expected) {\n      assertTrue(expected.getMessage().contains(\"wrong number of arguments\"));\n    }\n\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(\"xadd-stream1\", (StreamEntryID) null, map1);\n    assertNotNull(id1);\n\n    Map<String, String> map2 = new HashMap<>();\n    map2.put(\"f1\", \"v1\");\n    map2.put(\"f2\", \"v2\");\n    StreamEntryID id2 = jedis.xadd(\"xadd-stream1\", (StreamEntryID) null, map2);\n    assertTrue(id2.compareTo(id1) > 0);\n\n    Map<String, String> map3 = new HashMap<>();\n    map3.put(\"f2\", \"v2\");\n    map3.put(\"f3\", \"v3\");\n    StreamEntryID id3 = jedis.xadd(\"xadd-stream2\", (StreamEntryID) null, map3);\n\n    Map<String, String> map4 = new HashMap<>();\n    map4.put(\"f2\", \"v2\");\n    map4.put(\"f3\", \"v3\");\n    StreamEntryID idIn = new StreamEntryID(id3.getTime() + 1, 1L);\n    StreamEntryID id4 = jedis.xadd(\"xadd-stream2\", idIn, map4);\n    assertEquals(idIn, id4);\n    assertTrue(id4.compareTo(id3) > 0);\n\n    Map<String, String> map5 = new HashMap<>();\n    map5.put(\"f4\", \"v4\");\n    map5.put(\"f5\", \"v5\");\n    StreamEntryID id5 = jedis.xadd(\"xadd-stream2\", (StreamEntryID) null, map5);\n    assertTrue(id5.compareTo(id4) > 0);\n\n    Map<String, String> map6 = new HashMap<>();\n    map6.put(\"f4\", \"v4\");\n    map6.put(\"f5\", \"v5\");\n    StreamEntryID id6 = jedis.xadd(\"xadd-stream2\", map6, XAddParams.xAddParams().maxLen(3));\n    assertTrue(id6.compareTo(id5) > 0);\n    assertEquals(3L, jedis.xlen(\"xadd-stream2\"));\n  }\n\n  @Test\n  public void xaddWithParams() {\n\n    try {\n      jedis.xadd(\"stream1\", new HashMap<>(), XAddParams.xAddParams());\n      fail();\n    } catch (JedisDataException expected) {\n      assertTrue(expected.getMessage().contains(\"wrong number of arguments\"));\n    }\n\n    try {\n      jedis.xadd(\"stream1\", XAddParams.xAddParams(), new HashMap<>());\n      fail();\n    } catch (JedisDataException expected) {\n      assertTrue(expected.getMessage().contains(\"wrong number of arguments\"));\n    }\n\n    StreamEntryID id1 = jedis.xadd(\"xadd-stream1\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n    assertNotNull(id1);\n\n    Map<String, String> map2 = new HashMap<>();\n    map2.put(\"f1\", \"v1\");\n    map2.put(\"f2\", \"v2\");\n    StreamEntryID id2 = jedis.xadd(\"xadd-stream1\", map2, XAddParams.xAddParams());\n    assertTrue(id2.compareTo(id1) > 0);\n\n    Map<String, String> map3 = new HashMap<>();\n    map3.put(\"f2\", \"v2\");\n    map3.put(\"f3\", \"v3\");\n    StreamEntryID id3 = jedis.xadd(\"xadd-stream2\", XAddParams.xAddParams(), map3);\n\n    Map<String, String> map4 = new HashMap<>();\n    map4.put(\"f2\", \"v2\");\n    map4.put(\"f3\", \"v3\");\n    StreamEntryID idIn = new StreamEntryID(id3.getTime() + 1, 1L);\n    StreamEntryID id4 = jedis.xadd(\"xadd-stream2\", map4, XAddParams.xAddParams().id(idIn));\n    assertEquals(idIn, id4);\n    assertTrue(id4.compareTo(id3) > 0);\n\n    Map<String, String> map5 = new HashMap<>();\n    map5.put(\"f4\", \"v4\");\n    map5.put(\"f5\", \"v5\");\n    StreamEntryID id5 = jedis.xadd(\"xadd-stream2\", XAddParams.xAddParams(), map5);\n    assertTrue(id5.compareTo(id4) > 0);\n\n    Map<String, String> map6 = new HashMap<>();\n    map6.put(\"f4\", \"v4\");\n    map6.put(\"f5\", \"v5\");\n    StreamEntryID id6 = jedis.xadd(\"xadd-stream2\", map6, XAddParams.xAddParams().maxLen(3).exactTrimming());\n    assertTrue(id6.compareTo(id5) > 0);\n    assertEquals(3L, jedis.xlen(\"xadd-stream2\"));\n\n    // nomkstream\n    StreamEntryID id7 = jedis.xadd(\"xadd-stream3\", XAddParams.xAddParams().noMkStream().maxLen(3).exactTrimming(), map6);\n    assertNull(id7);\n    assertFalse(jedis.exists(\"xadd-stream3\"));\n\n    // minid\n    jedis.xadd(\"xadd-stream3\", map6, XAddParams.xAddParams().minId(\"2\").id(new StreamEntryID(2)));\n    assertEquals(1L, jedis.xlen(\"xadd-stream3\"));\n    jedis.xadd(\"xadd-stream3\", XAddParams.xAddParams().minId(\"4\").id(new StreamEntryID(3)), map6);\n    assertEquals(0L, jedis.xlen(\"xadd-stream3\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void xaddParamsId() {\n    StreamEntryID id;\n    String key = \"kk\";\n    Map<String, String> map = singletonMap(\"ff\", \"vv\");\n\n    id = jedis.xadd(key, XAddParams.xAddParams().id(new StreamEntryID(0, 1)), map);\n    assertNotNull(id);\n    assertEquals(\"0-1\", id.toString());\n    assertEquals(0, id.getTime());\n    assertEquals(1, id.getSequence());\n\n    id = jedis.xadd(key, XAddParams.xAddParams().id(2, 3), map);\n    assertNotNull(id);\n    assertEquals(2, id.getTime());\n    assertEquals(3, id.getSequence());\n\n    id = jedis.xadd(key, XAddParams.xAddParams().id(4), map);\n    assertNotNull(id);\n    assertEquals(4, id.getTime());\n    assertEquals(0, id.getSequence());\n\n    id = jedis.xadd(key, XAddParams.xAddParams().id(\"5-6\"), map);\n    assertNotNull(id);\n    assertEquals(5, id.getTime());\n    assertEquals(6, id.getSequence());\n\n    id = jedis.xadd(key, XAddParams.xAddParams().id(\"7-8\".getBytes()), map);\n    assertNotNull(id);\n    assertEquals(7, id.getTime());\n    assertEquals(8, id.getSequence());\n\n    id = jedis.xadd(key, XAddParams.xAddParams(), map);\n    assertNotNull(id);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithTrimmingModeKeepReferences() {\n    String streamKey = \"xadd-trim-keep-ref-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries to the stream\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(5L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages to create PEL entries\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Verify PEL has entries\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingBefore.size());\n\n    // Add new entry with maxLen=3 and KEEP_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"6-0\"))\n        .maxLen(3)\n        .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map);\n    assertNotNull(newId);\n\n    // Stream should be trimmed to 3 entries\n    assertEquals(3L, jedis.xlen(streamKey));\n\n    // PEL references should be preserved even though entries were trimmed\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingAfter.size()); // PEL entries should still exist\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithTrimmingModeDeleteReferences() {\n    String streamKey = \"xadd-trim-del-ref-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries to the stream\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(5L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages to create PEL entries\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Verify PEL has entries\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingBefore.size());\n\n    // Add new entry with maxLen=3 and DELETE_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"6-0\"))\n        .maxLen(3)\n        .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map);\n    assertNotNull(newId);\n\n    // Stream should be trimmed to 3 entries\n    assertEquals(3L, jedis.xlen(streamKey));\n\n    // PEL references should be removed for trimmed entries\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    // Only entries that still exist in the stream should remain in PEL\n    assertTrue(pendingAfter.size() <= 3);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithTrimmingModeAcknowledged() {\n    String streamKey = \"xadd-trim-acked-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries to the stream\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(5L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(groupName, consumerName,\n        XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Acknowledge the first 2 messages\n    StreamEntryID id1 = messages.get(0).getValue().get(0).getID();\n    StreamEntryID id2 = messages.get(0).getValue().get(1).getID();\n    jedis.xack(streamKey, groupName, id1, id2);\n\n    // Verify PEL state\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(1, pendingBefore.size()); // Only 1 unacknowledged message\n\n    // Add new entry with maxLen=3 and ACKNOWLEDGED mode\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"6-0\"))\n        .maxLen(3)\n        .trimmingMode(StreamDeletionPolicy.ACKNOWLEDGED), map);\n    assertNotNull(newId);\n\n    // Stream length should respect acknowledgment status\n    long streamLen = jedis.xlen(streamKey);\n    assertTrue(streamLen >= 3); // Should not trim unacknowledged entries aggressively\n\n    // PEL should still contain unacknowledged entries\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertFalse(pendingAfter.isEmpty()); // Unacknowledged entries should remain\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithMinIdTrimmingModeKeepReferences() {\n    String streamKey = \"xadd-minid-keep-ref-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries with specific IDs\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(\"0-\" + i)), map);\n    }\n    assertEquals(5L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages to create PEL entries\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Verify PEL has entries\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingBefore.size());\n\n    // Add new entry with minId=\"0-3\" and KEEP_REFERENCES mode (should trim entries < 0-3)\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"0-6\"))\n        .minId(\"0-3\")\n        .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map);\n    assertNotNull(newId);\n\n    // Stream should have entries >= 0-3 plus the new entry\n    long streamLen = jedis.xlen(streamKey);\n    assertTrue(streamLen >= 3); // Should keep entries 0-3, 0-4, 0-5, 0-6\n\n    // PEL references should be preserved even for trimmed entries\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingAfter.size()); // PEL entries should still exist\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithMinIdTrimmingModeDeleteReferences() {\n    String streamKey = \"xadd-minid-del-ref-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries with specific IDs\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(\"0-\" + i)), map);\n    }\n    assertEquals(5L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages to create PEL entries\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Verify PEL has entries\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingBefore.size());\n\n    // Add new entry with minId=\"0-3\" and DELETE_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"0-6\"))\n        .minId(\"0-3\")\n        .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map);\n    assertNotNull(newId);\n\n    // Stream should have entries >= 0-3 plus the new entry\n    long streamLen = jedis.xlen(streamKey);\n    assertTrue(streamLen >= 3);\n\n    // PEL references should be removed for trimmed entries (0-1, 0-2)\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    // Only entries that still exist in the stream should remain in PEL\n    assertTrue(pendingAfter.size() <= pendingBefore.size());\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithApproximateTrimmingAndTrimmingMode() {\n    String streamKey = \"xadd-approx-trim-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries\n    for (int i = 1; i <= 10; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(10L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(5), streamQuery);\n\n    // Add new entry with approximate trimming and KEEP_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"11-0\"))\n        .maxLen(5)\n        .approximateTrimming()\n        .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map);\n    assertNotNull(newId);\n\n    // With approximate trimming, the exact length may vary but should be around the target\n    long streamLen = jedis.xlen(streamKey);\n    assertTrue(streamLen >= 5); // Should be approximately 5, but may be more due to approximation\n\n    // PEL should preserve references\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithExactTrimmingAndTrimmingMode() {\n    String streamKey = \"xadd-exact-trim-mode-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(5L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Add new entry with exact trimming and DELETE_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"6-0\"))\n        .maxLen(3)\n        .exactTrimming()\n        .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map);\n    assertNotNull(newId);\n\n    // With exact trimming, stream should be exactly 3 entries\n    assertEquals(3L, jedis.xlen(streamKey));\n\n    // PEL references should be cleaned up for trimmed entries\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    // Only entries that still exist in the stream should remain in PEL\n    assertTrue(pendingAfter.size() <= 3);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithLimitAndTrimmingMode() {\n    String streamKey = \"xadd-limit-trim-mode-stream\";\n    String groupName = \"test-group\";\n    String consumerName = \"test-consumer\";\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries\n    for (int i = 1; i <= 10; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(10L, jedis.xlen(streamKey));\n\n    // Create consumer group and read messages\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(5), streamQuery);\n\n    // Add new entry with limit and KEEP_REFERENCES mode (limit requires approximate trimming)\n    StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"11-0\"))\n        .maxLen(5)\n        .approximateTrimming() // Required for limit to work\n        .limit(2) // Limit the number of entries to examine for trimming\n        .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map);\n    assertNotNull(newId);\n\n    // With limit, trimming may be less aggressive\n    long streamLen = jedis.xlen(streamKey);\n    assertTrue(streamLen >= 5); // Should be at least 5, but may be more due to limit\n\n    // PEL should preserve references\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10));\n    assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL\n  }\n\n  @Test\n  public void xdel() {\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n\n    StreamEntryID id1 = jedis.xadd(\"xdel-stream\", (StreamEntryID) null, map1);\n    assertNotNull(id1);\n\n    StreamEntryID id2 = jedis.xadd(\"xdel-stream\", (StreamEntryID) null, map1);\n    assertNotNull(id2);\n    assertEquals(2L, jedis.xlen(\"xdel-stream\"));\n\n    assertEquals(1L, jedis.xdel(\"xdel-stream\", id1));\n    assertEquals(1L, jedis.xlen(\"xdel-stream\"));\n  }\n\n  @Test\n  public void xlen() {\n    assertEquals(0L, jedis.xlen(\"xlen-stream\"));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xlen-stream\", (StreamEntryID) null, map);\n    assertEquals(1L, jedis.xlen(\"xlen-stream\"));\n\n    jedis.xadd(\"xlen-stream\", (StreamEntryID) null, map);\n    assertEquals(2L, jedis.xlen(\"xlen-stream\"));\n  }\n\n  @Test\n  public void xrange() {\n    List<StreamEntry> range = jedis.xrange(\"xrange-stream\", (StreamEntryID) null,\n      (StreamEntryID) null, Integer.MAX_VALUE);\n    assertEquals(0, range.size());\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map);\n    StreamEntryID id2 = jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map);\n    List<StreamEntry> range2 = jedis.xrange(\"xrange-stream\", (StreamEntryID) null,\n      (StreamEntryID) null, 3);\n    assertEquals(2, range2.size());\n    assertEquals(range2.get(0).toString(), id1 + \" \" + map);\n\n    List<StreamEntry> range3 = jedis.xrange(\"xrange-stream\", id1, null, 2);\n    assertEquals(2, range3.size());\n\n    List<StreamEntry> range4 = jedis.xrange(\"xrange-stream\", id1, id2, 2);\n    assertEquals(2, range4.size());\n\n    List<StreamEntry> range5 = jedis.xrange(\"xrange-stream\", id1, id2, 1);\n    assertEquals(1, range5.size());\n\n    List<StreamEntry> range6 = jedis.xrange(\"xrange-stream\", id2, null, 4);\n    assertEquals(1, range6.size());\n\n    StreamEntryID id3 = jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map);\n    List<StreamEntry> range7 = jedis.xrange(\"xrange-stream\", id3, id3, 4);\n    assertEquals(1, range7.size());\n\n    List<StreamEntry> range8 = jedis.xrange(\"xrange-stream\", (StreamEntryID) null, (StreamEntryID) null);\n    assertEquals(3, range8.size());\n    range8 = jedis.xrange(\"xrange-stream\", StreamEntryID.MINIMUM_ID, StreamEntryID.MAXIMUM_ID);\n    assertEquals(3, range8.size());\n  }\n\n  @Test\n  public void xrangeExclusive() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    String id1 = jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map).toString();\n    jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map);\n\n    List<StreamEntry> range2 = jedis.xrange(\"xrange-stream\", id1, \"+\", 2);\n    assertEquals(2, range2.size());\n\n    List<StreamEntry> range3 = jedis.xrange(\"xrange-stream\", \"(\" + id1, \"+\", 2);\n    assertEquals(1, range3.size());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void xreadWithParams() {\n\n    final String key1 = \"xread-stream1\";\n    final String key2 = \"xread-stream2\";\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(key1, new StreamEntryID());\n\n    // Before creating Stream\n    assertNull(jedis.xread(XReadParams.xReadParams().block(1), streamQeury1));\n    assertNull(jedis.xread(XReadParams.xReadParams(), streamQeury1));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(key1, (StreamEntryID) null, map);\n    StreamEntryID id2 = jedis.xadd(key2, (StreamEntryID) null, map);\n\n    // Read only a single Stream\n    List<Entry<String, List<StreamEntry>>> streams1 = jedis.xread(XReadParams.xReadParams().count(1).block(1), streamQeury1);\n    assertEquals(1, streams1.size());\n    assertEquals(key1, streams1.get(0).getKey());\n    assertEquals(1, streams1.get(0).getValue().size());\n    assertEquals(id1, streams1.get(0).getValue().get(0).getID());\n    assertEquals(map, streams1.get(0).getValue().get(0).getFields());\n\n    assertNull(jedis.xread(XReadParams.xReadParams().block(1), singletonMap(key1, id1)));\n    assertNull(jedis.xread(XReadParams.xReadParams(), singletonMap(key1, id1)));\n\n    // Read from two Streams\n    Map<String, StreamEntryID> streamQuery2 = new LinkedHashMap<>();\n    streamQuery2.put(key1, new StreamEntryID());\n    streamQuery2.put(key2, new StreamEntryID());\n    List<Entry<String, List<StreamEntry>>> streams2 = jedis.xread(XReadParams.xReadParams().count(2).block(1), streamQuery2);\n    assertEquals(2, streams2.size());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void xreadAsMap() {\n\n    final String stream1 = \"xread-stream1\";\n    final String stream2 = \"xread-stream2\";\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(stream1, new StreamEntryID());\n\n    // Before creating Stream\n    assertNull(jedis.xreadAsMap(XReadParams.xReadParams().block(1), streamQeury1));\n    assertNull(jedis.xreadAsMap(XReadParams.xReadParams(), streamQeury1));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = new StreamEntryID(1);\n    StreamEntryID id2 = new StreamEntryID(2);\n    StreamEntryID id3 = new StreamEntryID(3);\n\n    assertEquals(id1, jedis.xadd(stream1, id1, map));\n    assertEquals(id2, jedis.xadd(stream2, id2, map));\n    assertEquals(id3, jedis.xadd(stream1, id3, map));\n\n    // Read only a single Stream\n    Map<String, List<StreamEntry>> streams1 = jedis.xreadAsMap(XReadParams.xReadParams().count(2), streamQeury1);\n    assertEquals(singleton(stream1), streams1.keySet());\n    List<StreamEntry> list1 = streams1.get(stream1);\n    assertEquals(2, list1.size());\n    assertEquals(id1, list1.get(0).getID());\n    assertEquals(map, list1.get(0).getFields());\n    assertEquals(id3, list1.get(1).getID());\n    assertEquals(map, list1.get(1).getFields());\n\n    // Read from two Streams\n    Map<String, StreamEntryID> streamQuery2 = new LinkedHashMap<>();\n    streamQuery2.put(stream1, new StreamEntryID());\n    streamQuery2.put(stream2, new StreamEntryID());\n    Map<String, List<StreamEntry>> streams2 = jedis.xreadAsMap(XReadParams.xReadParams().count(1), streamQuery2);\n    assertEquals(2, streams2.size());\n    assertEquals(id1, streams2.get(stream1).get(0).getID());\n    assertEquals(id2, streams2.get(stream2).get(0).getID());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"From Redis 7.4, you can use the + sign as a special ID to request last entry\")\n  public void xreadAsMapLastEntry() {\n\n    final String stream1 = \"xread-stream1\";\n    final String stream2 = \"xread-stream2\";\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(stream1, new StreamEntryID());\n\n    // Before creating Stream\n    assertNull(jedis.xreadAsMap(XReadParams.xReadParams().block(1), streamQeury1));\n    assertNull(jedis.xreadAsMap(XReadParams.xReadParams(), streamQeury1));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = new StreamEntryID(1);\n    StreamEntryID id2 = new StreamEntryID(2);\n    StreamEntryID id3 = new StreamEntryID(3);\n\n    assertEquals(id1, jedis.xadd(stream1, id1, map));\n    assertEquals(id2, jedis.xadd(stream2, id2, map));\n    assertEquals(id3, jedis.xadd(stream1, id3, map));\n\n\n    // Read from last entry\n    Map<String, StreamEntryID> streamQueryLE = singletonMap(stream1, StreamEntryID.XREAD_LAST_ENTRY);\n    Map<String, List<StreamEntry>> streamsLE = jedis.xreadAsMap(XReadParams.xReadParams().count(1), streamQueryLE);\n    assertEquals(singleton(stream1), streamsLE.keySet());\n    assertEquals(1, streamsLE.get(stream1).size());\n    assertEquals(id3, streamsLE.get(stream1).get(0).getID());\n    assertEquals(map, streamsLE.get(stream1).get(0).getFields());\n  }\n\n  @Test\n  public void xreadBlockZero() throws InterruptedException {\n    final AtomicReference<StreamEntryID> readId = new AtomicReference<>();\n    Thread t = new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try (Jedis blockJedis = createJedis()) {\n          long startTime = System.currentTimeMillis();\n          List<Entry<String, List<StreamEntry>>> read = blockJedis.xread(XReadParams.xReadParams().block(0),\n              singletonMap(\"block0-stream\", new StreamEntryID()));\n          long endTime = System.currentTimeMillis();\n          assertTrue(endTime - startTime > 500);\n          assertNotNull(read);\n          readId.set(read.get(0).getValue().get(0).getID());\n        }\n      }\n    }, \"xread-block-0-thread\");\n    t.start();\n    Thread.sleep(1000);\n    StreamEntryID addedId = jedis.xadd(\"block0-stream\", (StreamEntryID) null, singletonMap(\"foo\", \"bar\"));\n    t.join();\n    assertEquals(addedId, readId.get());\n  }\n\n  @Test\n  public void xtrim() {\n    Map<String, String> map1 = new HashMap<String, String>();\n    map1.put(\"f1\", \"v1\");\n\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(\"xtrim-stream\", (StreamEntryID) null, map1);\n    }\n    assertEquals(5L, jedis.xlen(\"xtrim-stream\"));\n\n    jedis.xtrim(\"xtrim-stream\", 3, false);\n    assertEquals(3L, jedis.xlen(\"xtrim-stream\"));\n  }\n\n  @Test\n  public void xtrimWithParams() {\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(\"xtrim-stream\", new StreamEntryID(\"0-\" + i), map1);\n    }\n    assertEquals(5L, jedis.xlen(\"xtrim-stream\"));\n\n    jedis.xtrim(\"xtrim-stream\", XTrimParams.xTrimParams().maxLen(3).exactTrimming());\n    assertEquals(3L, jedis.xlen(\"xtrim-stream\"));\n\n    // minId\n    jedis.xtrim(\"xtrim-stream\", XTrimParams.xTrimParams().minId(\"0-4\").exactTrimming());\n    assertEquals(2L, jedis.xlen(\"xtrim-stream\"));\n  }\n\n  @Test\n  public void xrevrange() {\n    List<StreamEntry> range = jedis.xrevrange(\"xrevrange-stream\", (StreamEntryID) null,\n      (StreamEntryID) null, Integer.MAX_VALUE);\n    assertEquals(0, range.size());\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(\"xrevrange-stream\", (StreamEntryID) null, map);\n    StreamEntryID id2 = jedis.xadd(\"xrevrange-stream\", (StreamEntryID) null, map);\n    List<StreamEntry> range2 = jedis.xrange(\"xrevrange-stream\", (StreamEntryID) null,\n      (StreamEntryID) null, 3);\n    assertEquals(2, range2.size());\n\n    List<StreamEntry> range3 = jedis.xrevrange(\"xrevrange-stream\", null, id1, 2);\n    assertEquals(2, range3.size());\n\n    List<StreamEntry> range4 = jedis.xrevrange(\"xrevrange-stream\", id2, id1, 2);\n    assertEquals(2, range4.size());\n\n    List<StreamEntry> range5 = jedis.xrevrange(\"xrevrange-stream\", id2, id1, 1);\n    assertEquals(1, range5.size());\n\n    List<StreamEntry> range6 = jedis.xrevrange(\"xrevrange-stream\", null, id2, 4);\n    assertEquals(1, range6.size());\n\n    StreamEntryID id3 = jedis.xadd(\"xrevrange-stream\", (StreamEntryID) null, map);\n    List<StreamEntry> range7 = jedis.xrevrange(\"xrevrange-stream\", id3, id3, 4);\n    assertEquals(1, range7.size());\n\n    List<StreamEntry> range8 = jedis.xrevrange(\"xrevrange-stream\", (StreamEntryID) null, (StreamEntryID) null);\n    assertEquals(3, range8.size());\n    range8 = jedis.xrevrange(\"xrevrange-stream\", StreamEntryID.MAXIMUM_ID, StreamEntryID.MINIMUM_ID);\n    assertEquals(3, range8.size());\n  }\n\n  @Test\n  public void xrevrangeExclusive() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    String id1 = jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map).toString();\n    jedis.xadd(\"xrange-stream\", (StreamEntryID) null, map);\n\n    List<StreamEntry> range2 = jedis.xrevrange(\"xrange-stream\", \"+\", id1, 2);\n    assertEquals(2, range2.size());\n\n    List<StreamEntry> range3 = jedis.xrevrange(\"xrange-stream\", \"+\", \"(\" + id1, 2);\n    assertEquals(1, range3.size());\n  }\n\n  @Test\n  public void xgroup() {\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(\"xgroup-stream\", (StreamEntryID) null, map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(\"xgroup-stream\", \"consumer-group-name\", null, false));\n    assertEquals(\"OK\", jedis.xgroupSetID(\"xgroup-stream\", \"consumer-group-name\", id1));\n    assertEquals(\"OK\", jedis.xgroupCreate(\"xgroup-stream\", \"consumer-group-name1\", StreamEntryID.XGROUP_LAST_ENTRY, false));\n\n    jedis.xgroupDestroy(\"xgroup-stream\", \"consumer-group-name\");\n    assertEquals(0L, jedis.xgroupDelConsumer(\"xgroup-stream\", \"consumer-group-name1\",\"myconsumer1\"));\n    assertTrue(jedis.xgroupCreateConsumer(\"xgroup-stream\", \"consumer-group-name1\",\"myconsumer2\"));\n    assertEquals(0L, jedis.xgroupDelConsumer(\"xgroup-stream\", \"consumer-group-name1\",\"myconsumer2\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void xreadGroupWithParams() {\n\n    // Simple xreadGroup with NOACK\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xreadGroup-stream1\", (StreamEntryID) null, map);\n    jedis.xgroupCreate(\"xreadGroup-stream1\", \"xreadGroup-group\", null, false);\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(\"xreadGroup-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Entry<String, List<StreamEntry>>> range = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).noAck(), streamQeury1);\n    assertEquals(1, range.size());\n    assertEquals(1, range.get(0).getValue().size());\n\n    jedis.xadd(\"xreadGroup-stream1\", (StreamEntryID) null, map);\n    jedis.xadd(\"xreadGroup-stream2\", (StreamEntryID) null, map);\n    jedis.xgroupCreate(\"xreadGroup-stream2\", \"xreadGroup-group\", null, false);\n\n    // Read only a single Stream\n    List<Entry<String, List<StreamEntry>>> streams1 = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1).noAck(), streamQeury1);\n    assertEquals(1, streams1.size());\n    assertEquals(1, streams1.get(0).getValue().size());\n\n    // Read from two Streams\n    Map<String, StreamEntryID> streamQuery2 = new LinkedHashMap<>();\n    streamQuery2.put(\"xreadGroup-stream1\", new StreamEntryID());\n    streamQuery2.put(\"xreadGroup-stream2\", new StreamEntryID());\n    List<Entry<String, List<StreamEntry>>> streams2 = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1).noAck(), streamQuery2);\n    assertEquals(2, streams2.size());\n\n    // Read only fresh messages\n    StreamEntryID id4 = jedis.xadd(\"xreadGroup-stream1\", (StreamEntryID) null, map);\n    Map<String, StreamEntryID> streamQeuryFresh = singletonMap(\"xreadGroup-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Entry<String, List<StreamEntry>>> streamsFresh = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(4).block(100).noAck(), streamQeuryFresh);\n    assertEquals(1, streamsFresh.size());\n    assertEquals(id4, streamsFresh.get(0).getValue().get(0).getID());\n  }\n\n  @Test\n  public void xreadGroupAsMap() {\n\n    final String stream1 = \"xreadGroup-stream1\";\n    Map<String, String> map = singletonMap(\"f1\", \"v1\");\n\n    StreamEntryID id1 = jedis.xadd(stream1, StreamEntryID.NEW_ENTRY, map);\n    jedis.xgroupCreate(stream1, \"xreadGroup-group\", null, false);\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(stream1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    Map<String, List<StreamEntry>> range = jedis.xreadGroupAsMap(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().noAck(), streamQeury1);\n    assertEquals(singleton(stream1), range.keySet());\n    List<StreamEntry> list = range.get(stream1);\n    assertEquals(1, list.size());\n    assertEquals(id1, list.get(0).getID());\n    assertEquals(map, list.get(0).getFields());\n    assertNull(list.get(0).getMillisElapsedFromDelivery());\n    assertNull(list.get(0).getDeliveredCount());\n  }\n\n  @Test\n  public void xreadGroupWithParamsWhenPendingMessageIsDiscarded() {\n    // Add two message to stream\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n\n    Map<String, String> map2 = new HashMap<>();\n    map2.put(\"f2\", \"v2\");\n\n    XAddParams xAddParams = XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY).maxLen(2);\n    StreamEntryID firstMessageEntryId = jedis.xadd(\"xreadGroup-discard-stream1\", xAddParams, map1);\n    jedis.xadd(\"xreadGroup-discard-stream1\", xAddParams, map2);\n\n    jedis.xgroupCreate(\"xreadGroup-discard-stream1\", \"xreadGroup-group\", null, false);\n    Map<String, StreamEntryID> streamQuery1 = singletonMap(\"xreadGroup-discard-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Entry<String, List<StreamEntry>>> range = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n            XReadGroupParams.xReadGroupParams().count(1), streamQuery1);\n    assertEquals(1, range.size());\n    assertEquals(1, range.get(0).getValue().size());\n\n    assertEquals(firstMessageEntryId, range.get(0).getValue().get(0).getID());\n    assertEquals(map1, range.get(0).getValue().get(0).getFields());\n    assertNull(range.get(0).getValue().get(0).getMillisElapsedFromDelivery());\n    assertNull(range.get(0).getValue().get(0).getDeliveredCount());\n\n    // Add third message, the fields of pending message1 will be discarded by redis-server\n    Map<String, String> map3 = new HashMap<>();\n    map3.put(\"f3\", \"v3\");\n    jedis.xadd(\"xreadGroup-discard-stream1\", xAddParams, map3);\n\n    Map<String, StreamEntryID> streamQueryPending = singletonMap(\"xreadGroup-discard-stream1\", new StreamEntryID());\n    List<Entry<String, List<StreamEntry>>> pendingMessages = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n            XReadGroupParams.xReadGroupParams().count(1).noAck(), streamQueryPending);\n\n    assertEquals(1, pendingMessages.size());\n    assertEquals(1, pendingMessages.get(0).getValue().size());\n\n    assertEquals(firstMessageEntryId, pendingMessages.get(0).getValue().get(0).getID());\n    assertNull(pendingMessages.get(0).getValue().get(0).getFields());\n    assertNull(pendingMessages.get(0).getValue().get(0).getDeliveredCount());\n    assertNull(pendingMessages.get(0).getValue().get(0).getMillisElapsedFromDelivery());\n  }\n\n  @Test\n  public void xack() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xack-stream\", (StreamEntryID) null, map);\n\n    jedis.xgroupCreate(\"xack-stream\", \"xack-group\", null, false);\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(\"xack-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    // Empty Stream\n    List<Entry<String, List<StreamEntry>>> range = jedis.xreadGroup(\"xack-group\", \"xack-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1), streamQeury1);\n    assertEquals(1, range.size());\n\n    assertEquals(1L,\n      jedis.xack(\"xack-stream\", \"xack-group\", range.get(0).getValue().get(0).getID()));\n  }\n\n  @Test\n  public void xpendingWithParams() {\n    final String stream = \"xpendeing-stream\";\n\n    assertEquals(\"OK\", jedis.xgroupCreate(stream, \"xpendeing-group\", null, true));\n\n    // Get the summary from empty stream\n    StreamPendingSummary emptySummary = jedis.xpending(stream, \"xpendeing-group\");\n    assertEquals(0, emptySummary.getTotal());\n    assertNull(emptySummary.getMinId());\n    assertNull(emptySummary.getMaxId());\n    assertNull(emptySummary.getConsumerMessageCount());\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(stream, (StreamEntryID) null, map);\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(stream, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    // Read the event from Stream put it on pending\n    List<Entry<String, List<StreamEntry>>> range = jedis.xreadGroup(\"xpendeing-group\",\n            \"xpendeing-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1), streamQeury1);\n    assertEquals(1, range.size());\n    assertEquals(1, range.get(0).getValue().size());\n    assertEquals(map, range.get(0).getValue().get(0).getFields());\n\n    // Get the summary about the pending messages\n    StreamPendingSummary pendingSummary = jedis.xpending(stream, \"xpendeing-group\");\n    assertEquals(1, pendingSummary.getTotal());\n    assertEquals(id1, pendingSummary.getMinId());\n    assertEquals(1l, pendingSummary.getConsumerMessageCount().get(\"xpendeing-consumer\").longValue());\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(stream, \"xpendeing-group\",\n            new XPendingParams().count(3).consumer(\"xpendeing-consumer\"));\n    assertEquals(1, pendingRange.size());\n    assertEquals(id1, pendingRange.get(0).getID());\n    assertEquals(1, pendingRange.get(0).getDeliveredTimes());\n    assertEquals(\"xpendeing-consumer\", pendingRange.get(0).getConsumerName());\n    assertTrue(pendingRange.get(0).toString().contains(\"xpendeing-consumer\"));\n\n    // Without consumer\n    pendingRange = jedis.xpending(stream, \"xpendeing-group\", new XPendingParams().count(3));\n    assertEquals(1, pendingRange.size());\n    assertEquals(id1, pendingRange.get(0).getID());\n    assertEquals(1, pendingRange.get(0).getDeliveredTimes());\n    assertEquals(\"xpendeing-consumer\", pendingRange.get(0).getConsumerName());\n\n    // with idle\n    pendingRange = jedis.xpending(stream, \"xpendeing-group\",\n      new XPendingParams().idle(Duration.ofMinutes(1).toMillis()).count(3));\n    assertEquals(0, pendingRange.size());\n  }\n\n  @Test\n  public void xpendingRange() {\n    final String stream = \"xpendeing-stream\";\n    Map<String, String> map = new HashMap<>();\n    map.put(\"foo\", \"bar\");\n    StreamEntryID m1 = jedis.xadd(stream, (StreamEntryID) null, map);\n    StreamEntryID m2 = jedis.xadd(stream, (StreamEntryID) null, map);\n    jedis.xgroupCreate(stream, \"xpendeing-group\", null, false);\n\n    // read 1 message from the group with each consumer\n    Map<String, StreamEntryID> streamQeury = singletonMap(stream, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(\"xpendeing-group\", \"consumer1\", XReadGroupParams.xReadGroupParams().count(1), streamQeury);\n    jedis.xreadGroup(\"xpendeing-group\", \"consumer2\", XReadGroupParams.xReadGroupParams().count(1), streamQeury);\n\n    List<StreamPendingEntry> response = jedis.xpending(stream, \"xpendeing-group\",\n        XPendingParams.xPendingParams(\"(0\", \"+\", 5));\n    assertEquals(2, response.size());\n    assertEquals(m1, response.get(0).getID());\n    assertEquals(\"consumer1\", response.get(0).getConsumerName());\n    assertEquals(m2, response.get(1).getID());\n    assertEquals(\"consumer2\", response.get(1).getConsumerName());\n\n    response = jedis.xpending(stream, \"xpendeing-group\",\n        XPendingParams.xPendingParams(StreamEntryID.MINIMUM_ID, StreamEntryID.MAXIMUM_ID, 5));\n    assertEquals(2, response.size());\n    assertEquals(m1, response.get(0).getID());\n    assertEquals(\"consumer1\", response.get(0).getConsumerName());\n    assertEquals(m2, response.get(1).getID());\n    assertEquals(\"consumer2\", response.get(1).getConsumerName());\n  }\n\n  @Test\n  public void xclaimWithParams() {\n    final String stream = \"xpendeing-stream\";\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(stream, (StreamEntryID) null, map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(stream, \"xpendeing-group\", null, false));\n\n    // Read the event from Stream put it on pending\n    jedis.xreadGroup(\"xpendeing-group\", \"xpendeing-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n            singletonMap(stream, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(stream, \"xpendeing-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpendeing-consumer\"));\n\n    // Sleep for 100ms so we can claim events pending for more than 50ms\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n\n    List<StreamEntry> streamEntrys = jedis.xclaim(stream, \"xpendeing-group\",\n            \"xpendeing-consumer2\", 50, XClaimParams.xClaimParams().idle(0).retryCount(0),\n            pendingRange.get(0).getID());\n    assertEquals(1, streamEntrys.size());\n    assertEquals(pendingRange.get(0).getID(), streamEntrys.get(0).getID());\n    assertEquals(\"v1\", streamEntrys.get(0).getFields().get(\"f1\"));\n  }\n\n  @Test\n  public void xclaimJustId() {\n    final String stream = \"xpendeing-stream\";\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(stream, (StreamEntryID) null, map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(stream, \"xpendeing-group\", null, false));\n\n    // Read the event from Stream put it on pending\n    jedis.xreadGroup(\"xpendeing-group\", \"xpendeing-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(stream, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(stream, \"xpendeing-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpendeing-consumer\"));\n    // Sleep for 100ms so we can claim events pending for more than 50ms\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n\n    List<StreamEntryID> streamEntryIDS = jedis.xclaimJustId(stream, \"xpendeing-group\",\n      \"xpendeing-consumer2\", 50, XClaimParams.xClaimParams().idle(0).retryCount(0),\n      pendingRange.get(0).getID());\n    assertEquals(1, streamEntryIDS.size());\n    assertEquals(pendingRange.get(0).getID(), streamEntryIDS.get(0));\n  }\n\n  @Test\n  public void xautoclaim() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xpending-stream\", (StreamEntryID) null, map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false));\n\n    // Read the event from Stream put it on pending\n    jedis.xreadGroup(\"xpending-group\", \"xpending-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n\n    // Auto claim pending events to different consumer\n    Map.Entry<StreamEntryID, List<StreamEntry>> streamEntrys = jedis.xautoclaim(\"xpending-stream\", \"xpending-group\",\n            \"xpending-consumer2\", 50, new StreamEntryID(), new XAutoClaimParams().count(1));\n    assertEquals(1, streamEntrys.getValue().size());\n    assertEquals(pendingRange.get(0).getID(), streamEntrys.getValue().get(0).getID());\n    assertEquals(\"v1\", streamEntrys.getValue().get(0).getFields().get(\"f1\"));\n  }\n\n  @Test\n  public void xautoclaimBinary() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xpending-stream\", XAddParams.xAddParams(), map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false));\n\n    // Read the event from Stream put it on pending\n    jedis.xreadGroup(\"xpending-group\", \"xpending-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n\n    // Auto claim pending events to different consumer\n    List<Object> streamEntrys = jedis.xautoclaim(SafeEncoder.encode(\"xpending-stream\"),\n            SafeEncoder.encode(\"xpending-group\"), SafeEncoder.encode(\"xpending-consumer2\"),\n            50, SafeEncoder.encode(new StreamEntryID().toString()), new XAutoClaimParams().count(1));\n    Map.Entry<StreamEntryID, List<StreamEntry>> res = BuilderFactory.STREAM_AUTO_CLAIM_RESPONSE.build(streamEntrys);\n    assertEquals(1, res.getValue().size());\n    assertEquals(pendingRange.get(0).getID(), res.getValue().get(0).getID());\n    assertEquals(\"v1\", res.getValue().get(0).getFields().get(\"f1\"));\n  }\n\n  @Test\n  public void xautoclaimJustId() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xpending-stream\", (StreamEntryID) null, map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false));\n\n    // Read the event from Stream put it on pending\n    jedis.xreadGroup(\"xpending-group\", \"xpending-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n\n    // Auto claim pending events to different consumer\n    Map.Entry<StreamEntryID, List<StreamEntryID>> streamEntrys = jedis.xautoclaimJustId(\"xpending-stream\", \"xpending-group\",\n            \"xpending-consumer2\", 50, new StreamEntryID(), new XAutoClaimParams().count(1));\n    assertEquals(1, streamEntrys.getValue().size());\n    assertEquals(pendingRange.get(0).getID().getTime(), streamEntrys.getValue().get(0).getTime());\n    assertEquals(pendingRange.get(0).getID().getSequence(), streamEntrys.getValue().get(0).getSequence());\n  }\n\n  @Test\n  public void xautoclaimJustIdBinary() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    jedis.xadd(\"xpending-stream\", XAddParams.xAddParams(), map);\n\n    assertEquals(\"OK\", jedis.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false));\n\n    // Read the event from Stream put it on pending\n    jedis.xreadGroup(\"xpending-group\", \"xpending-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    List<StreamPendingEntry> pendingRange = jedis.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n\n    // Auto claim pending events to different consumer\n    List<Object> streamEntrys = jedis.xautoclaimJustId(SafeEncoder.encode(\"xpending-stream\"),\n            SafeEncoder.encode(\"xpending-group\"), SafeEncoder.encode(\"xpending-consumer2\"),\n            50, SafeEncoder.encode(new StreamEntryID().toString()), new XAutoClaimParams().count(1));\n    Map.Entry<StreamEntryID, List<StreamEntryID>> res = BuilderFactory.STREAM_AUTO_CLAIM_JUSTID_RESPONSE.build(streamEntrys);\n    assertEquals(1, res.getValue().size());\n    assertEquals(pendingRange.get(0).getID().getTime(), res.getValue().get(0).getTime());\n    assertEquals(pendingRange.get(0).getID().getSequence(), res.getValue().get(0).getSequence());\n  }\n\n  @Test\n  public void xinfo() throws InterruptedException {\n\n    final String STREAM_NAME = \"xadd-stream1\";\n    final String F1 = \"f1\";\n    final String V1 = \"v1\";\n    final String V2 = \"v2\";\n    final String G1 = \"G1\";\n    final String G2 = \"G2\";\n    final String MY_CONSUMER = \"myConsumer\";\n    final String MY_CONSUMER2 = \"myConsumer2\";\n\n    final RedisVersion redisVersion = RedisVersionUtil.getRedisVersion(jedis);\n\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(F1, V1);\n    StreamEntryID id1 = jedis.xadd(STREAM_NAME, (StreamEntryID) null, map1);\n    map1.put(F1, V2);\n    StreamEntryID id2 = jedis.xadd(STREAM_NAME, (StreamEntryID) null, map1);\n    assertNotNull(id1);\n    StreamInfo streamInfo = jedis.xinfoStream(STREAM_NAME);\n    assertNotNull(id2);\n\n    jedis.xgroupCreate(STREAM_NAME, G1, StreamEntryID.XGROUP_LAST_ENTRY, false);\n    Map<String, StreamEntryID> streamQeury11 = singletonMap(\n        STREAM_NAME, new StreamEntryID(\"0-0\"));\n    jedis.xreadGroup(G1, MY_CONSUMER, XReadGroupParams.xReadGroupParams().count(1), streamQeury11);\n\n    Thread.sleep(1);\n\n    List<StreamGroupInfo> groupInfo = jedis.xinfoGroups(STREAM_NAME);\n    List<StreamConsumersInfo> consumersInfo = jedis.xinfoConsumers(STREAM_NAME, G1);\n    List<StreamConsumerInfo> consumerInfo = jedis.xinfoConsumers2(STREAM_NAME, G1);\n\n    // Stream info test\n    assertEquals(2L, streamInfo.getStreamInfo().get(StreamInfo.LENGTH));\n    assertEquals(1L, streamInfo.getStreamInfo().get(StreamInfo.RADIX_TREE_KEYS));\n    assertEquals(2L, streamInfo.getStreamInfo().get(StreamInfo.RADIX_TREE_NODES));\n    assertEquals(0L, streamInfo.getStreamInfo().get(StreamInfo.GROUPS));\n    assertEquals(V1, ((StreamEntry) streamInfo.getStreamInfo().get(StreamInfo.FIRST_ENTRY)).getFields().get(F1));\n    assertEquals(V2, ((StreamEntry) streamInfo.getStreamInfo().get(StreamInfo.LAST_ENTRY)).getFields().get(F1));\n    assertEquals(id2, streamInfo.getStreamInfo().get(StreamInfo.LAST_GENERATED_ID));\n\n    // Using getters\n    assertEquals(2, streamInfo.getLength());\n    assertEquals(1, streamInfo.getRadixTreeKeys());\n    assertEquals(2, streamInfo.getRadixTreeNodes());\n    assertEquals(0, streamInfo.getGroups());\n    assertEquals(V1, streamInfo.getFirstEntry().getFields().get(F1));\n    assertEquals(V2, streamInfo.getLastEntry().getFields().get(F1));\n    assertEquals(id2, streamInfo.getLastGeneratedId());\n\n    // Group info test\n    assertEquals(1, groupInfo.size());\n    assertEquals(G1, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.NAME));\n    assertEquals(1L, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.CONSUMERS));\n    assertEquals(0L, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.PENDING));\n    assertEquals(id2, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.LAST_DELIVERED));\n\n    // Using getters\n    assertEquals(1, groupInfo.size());\n    assertEquals(G1, groupInfo.get(0).getName());\n    assertEquals(1, groupInfo.get(0).getConsumers());\n    assertEquals(0, groupInfo.get(0).getPending());\n    assertEquals(id2, groupInfo.get(0).getLastDeliveredId());\n\n    // Consumers info test\n    assertEquals(MY_CONSUMER,\n      consumersInfo.get(0).getConsumerInfo().get(StreamConsumersInfo.NAME));\n    assertEquals(0L, consumersInfo.get(0).getConsumerInfo().get(StreamConsumersInfo.PENDING));\n    assertTrue((Long) consumersInfo.get(0).getConsumerInfo().get(StreamConsumersInfo.IDLE) > 0);\n\n    // Using getters\n    assertEquals(MY_CONSUMER, consumersInfo.get(0).getName());\n    assertEquals(0L, consumersInfo.get(0).getPending());\n    assertThat(consumersInfo.get(0).getIdle(), Matchers.greaterThanOrEqualTo(0L));\n\n    if ( redisVersion.isGreaterThanOrEqualTo(RedisVersion.V7_2_0)) {\n      assertThat(consumersInfo.get(0).getInactive(), Matchers.any(Long.class));\n    }\n\n    // Consumer info test\n    assertEquals(MY_CONSUMER,\n      consumerInfo.get(0).getConsumerInfo().get(StreamConsumerInfo.NAME));\n    assertEquals(0L, consumerInfo.get(0).getConsumerInfo().get(StreamConsumerInfo.PENDING));\n    assertTrue((Long) consumerInfo.get(0).getConsumerInfo().get(StreamConsumerInfo.IDLE) > 0);\n\n    // Using getters\n    assertEquals(MY_CONSUMER, consumerInfo.get(0).getName());\n    assertEquals(0L, consumerInfo.get(0).getPending());\n    assertThat(consumerInfo.get(0).getIdle(), Matchers.greaterThanOrEqualTo(0L));\n    if (redisVersion.isGreaterThanOrEqualTo(RedisVersion.V7_2_0)) {\n      assertThat(consumerInfo.get(0).getInactive(), Matchers.any(Long.class));\n    }\n\n    // test with more groups and consumers\n    jedis.xgroupCreate(STREAM_NAME, G2, StreamEntryID.XGROUP_LAST_ENTRY, false);\n    jedis.xreadGroup(G1, MY_CONSUMER2, XReadGroupParams.xReadGroupParams().count(1), streamQeury11);\n    jedis.xreadGroup(G2, MY_CONSUMER, XReadGroupParams.xReadGroupParams().count(1), streamQeury11);\n    jedis.xreadGroup(G2, MY_CONSUMER2, XReadGroupParams.xReadGroupParams().count(1), streamQeury11);\n\n    List<StreamGroupInfo> manyGroupsInfo = jedis.xinfoGroups(STREAM_NAME);\n    List<StreamConsumersInfo> manyConsumersInfo = jedis.xinfoConsumers(STREAM_NAME, G2);\n    List<StreamConsumerInfo> manyConsumerInfo = jedis.xinfoConsumers2(STREAM_NAME, G2);\n\n    assertEquals(2, manyGroupsInfo.size());\n    assertEquals(2, manyConsumersInfo.size());\n    assertEquals(2, manyConsumerInfo.size());\n\n    StreamFullInfo streamInfoFull = jedis.xinfoStreamFull(STREAM_NAME);\n\n    assertEquals(2, streamInfoFull.getEntries().size());\n    assertEquals(2, streamInfoFull.getGroups().size());\n    assertEquals(2, streamInfoFull.getLength());\n    assertEquals(1, streamInfoFull.getRadixTreeKeys());\n    assertEquals(2, streamInfoFull.getRadixTreeNodes());\n    assertEquals(0, streamInfo.getGroups());\n    assertEquals(G1, streamInfoFull.getGroups().get(0).getName());\n    assertEquals(G2, streamInfoFull.getGroups().get(1).getName());\n    assertEquals(V1, streamInfoFull.getEntries().get(0).getFields().get(F1));\n    assertEquals(V2, streamInfoFull.getEntries().get(1).getFields().get(F1));\n    assertEquals(id2, streamInfoFull.getLastGeneratedId());\n\n    streamInfoFull = jedis.xinfoStreamFull(STREAM_NAME, 10);\n    assertEquals(G1, streamInfoFull.getGroups().get(0).getName());\n    assertEquals(G2, streamInfoFull.getGroups().get(1).getName());\n    assertEquals(V1, streamInfoFull.getEntries().get(0).getFields().get(F1));\n    assertEquals(V2, streamInfoFull.getEntries().get(1).getFields().get(F1));\n    assertEquals(id2, streamInfoFull.getLastGeneratedId());\n\n    // Not existing key - redis cli return error so we expect exception\n    try {\n      jedis.xinfoStream(\"random\");\n      fail(\"Command should fail\");\n    } catch (JedisException e) {\n      assertEquals(\"ERR no such key\", e.getMessage());\n    }\n  }\n\n  @Test\n  public void xinfoStreamFullWithPending() {\n\n    Map<String, String> map = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = jedis.xadd(\"streamfull2\", (StreamEntryID) null, map);\n    StreamEntryID id2 = jedis.xadd(\"streamfull2\", (StreamEntryID) null, map);\n    jedis.xgroupCreate(\"streamfull2\", \"xreadGroup-group\", null, false);\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(\"streamfull2\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Entry<String, List<StreamEntry>>> range = jedis.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1), streamQeury1);\n    assertEquals(1, range.size());\n    assertEquals(1, range.get(0).getValue().size());\n\n    StreamFullInfo full = jedis.xinfoStreamFull(\"streamfull2\");\n    assertEquals(1, full.getGroups().size());\n    StreamGroupFullInfo group = full.getGroups().get(0);\n    assertEquals(\"xreadGroup-group\", group.getName());\n\n    assertEquals(1, group.getPending().size());\n    List<Object> groupPendingEntry = group.getPending().get(0);\n    assertEquals(id1, groupPendingEntry.get(0));\n    assertEquals(\"xreadGroup-consumer\", groupPendingEntry.get(1));\n\n    assertEquals(1, group.getConsumers().size());\n    StreamConsumerFullInfo consumer = group.getConsumers().get(0);\n    assertEquals(\"xreadGroup-consumer\", consumer.getName());\n    assertThat(consumer.getSeenTime(), Matchers.greaterThanOrEqualTo(0L));\n    if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_2_0)) {\n      assertThat(consumer.getActiveTime(), Matchers.greaterThanOrEqualTo(0L));\n    }\n    assertEquals(1, consumer.getPending().size());\n    List<Object> consumerPendingEntry = consumer.getPending().get(0);\n    assertEquals(id1, consumerPendingEntry.get(0));\n  }\n\n  @Test\n  public void pipeline() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"a\", \"b\");\n    Pipeline p = jedis.pipelined();\n    Response<StreamEntryID> id1 = p.xadd(\"stream1\", StreamEntryID.NEW_ENTRY, map);\n    Response<StreamEntryID> id2 = p.xadd(\"stream1\", StreamEntryID.NEW_ENTRY, map);\n    Response<List<StreamEntry>> results = p.xrange(\"stream1\", (StreamEntryID) null, (StreamEntryID) null, 2);\n    p.sync();\n\n    List<StreamEntry> entries = results.get();\n    assertEquals(2, entries.size());\n    assertEquals(id1.get(), entries.get(0).getID());\n    assertEquals(map, entries.get(0).getFields());\n    assertEquals(id2.get(), entries.get(1).getID());\n    assertEquals(map, entries.get(1).getFields());\n\n    p = jedis.pipelined();\n    Response<List<StreamEntry>> results2 = p.xrevrange(\"stream1\", null, id1.get(), 2);\n    p.sync();\n    assertEquals(2, results2.get().size());\n  }\n\n  @Test\n  public void transaction() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"a\", \"b\");\n    Transaction t = jedis.multi();\n    Response<StreamEntryID> id1 = t.xadd(\"stream1\", StreamEntryID.NEW_ENTRY, map);\n    Response<StreamEntryID> id2 = t.xadd(\"stream1\", StreamEntryID.NEW_ENTRY, map);\n    Response<List<StreamEntry>> results = t.xrange(\"stream1\", (StreamEntryID) null, (StreamEntryID) null, 2);\n    t.exec();\n\n    List<StreamEntry> entries = results.get();\n    assertEquals(2, entries.size());\n    assertEquals(id1.get(), entries.get(0).getID());\n    assertEquals(map, entries.get(0).getFields());\n    assertEquals(id2.get(), entries.get(1).getID());\n    assertEquals(map, entries.get(1).getFields());\n  }\n\n  // ========== XREADGROUP CLAIM Tests ==========\n\n  private static final String STREAM_KEY = \"test-stream-claim\";\n  private static final String GROUP_NAME = \"test-group\";\n  private static final String CONSUMER_1 = \"consumer-1\";\n  private static final String CONSUMER_2 = \"consumer-2\";\n  private static final long IDLE_TIME_MS = 5;\n  private static final Map<String, String> HASH = singletonMap(\"field\", \"value\");\n\n  private Map<String, StreamEntryID> beforeEachClaimTest() throws InterruptedException {\n    jedis.del(STREAM_KEY);\n\n    // Produce two entries\n    Map<String, String> hash = singletonMap(\"field\", \"value\");\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, hash);\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, hash);\n\n    // Create group and consume with consumer-1\n    try {\n      jedis.xgroupCreate(STREAM_KEY, GROUP_NAME, new StreamEntryID(\"0-0\"), false);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY,\n        StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_1, XReadGroupParams.xReadGroupParams().count(10),\n        streams);\n\n    // Ensure idle time\n    Thread.sleep(IDLE_TIME_MS);\n    return streams;\n  }\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimReturnsMetadataOrdered() throws InterruptedException {\n    Map<String, StreamEntryID> streams = beforeEachClaimTest();\n\n    // Produce fresh entries\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, HASH);\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, HASH);\n\n    // Read with consumer-2 using CLAIM\n    List<Map.Entry<String, List<StreamEntry>>> consumer2Result = jedis.xreadGroup(GROUP_NAME,\n        CONSUMER_2, XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS - 1).count(10), streams);\n\n    assertNotNull(consumer2Result);\n    assertEquals(1, consumer2Result.size());\n\n    List<StreamEntry> entries = consumer2Result.get(0).getValue();\n    assertEquals(4, entries.size());\n\n    long claimedCount = entries.stream().filter(StreamEntry::isClaimed).count();\n    long freshCount = entries.size() - claimedCount;\n\n    assertEquals(2, claimedCount);\n    assertEquals(2, freshCount);\n\n    // Assert order: pending entries are first\n    StreamEntry first = entries.get(0);\n    StreamEntry second = entries.get(1);\n    StreamEntry third = entries.get(2);\n    StreamEntry fourth = entries.get(3);\n\n    // Claimed entries\n    assertTrue(first.isClaimed());\n    assertTrue(second.isClaimed());\n    assertTrue(first.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n    assertTrue(second.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n\n    // Fresh entries\n    assertFalse(third.isClaimed());\n    assertFalse(fourth.isClaimed());\n    assertEquals(Long.valueOf(0), third.getDeliveredCount());\n    assertEquals(Long.valueOf(0), fourth.getDeliveredCount());\n    assertEquals(Long.valueOf(0), third.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(0), fourth.getMillisElapsedFromDelivery());\n  }\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimMovesPendingFromC1ToC2AndRemainsPendingUntilAck()\n      throws InterruptedException {\n    Map<String, StreamEntryID> streams = beforeEachClaimTest();\n\n    // Verify pending belongs to consumer-1\n    StreamPendingSummary before = jedis.xpending(STREAM_KEY, GROUP_NAME);\n    assertEquals(2L, before.getTotal());\n    assertEquals(2L, before.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n\n    // Claim with consumer-2\n    List<Map.Entry<String, List<StreamEntry>>> res = jedis.xreadGroup(GROUP_NAME, CONSUMER_2,\n        XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).count(10), streams);\n\n    assertNotNull(res);\n    assertEquals(1, res.size());\n\n    List<StreamEntry> entries = res.get(0).getValue();\n    long claimed = entries.stream().filter(StreamEntry::isClaimed).count();\n    assertEquals(2, claimed);\n\n    // After claim: entries are pending for consumer-2\n    StreamPendingSummary afterClaim = jedis.xpending(STREAM_KEY, GROUP_NAME);\n    assertEquals(2L, afterClaim.getTotal());\n    assertEquals(0L, afterClaim.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n    assertEquals(2L, afterClaim.getConsumerMessageCount().getOrDefault(CONSUMER_2, 0L).longValue());\n\n    // XACK the claimed entries\n    long acked = jedis.xack(STREAM_KEY, GROUP_NAME, entries.get(0).getID(), entries.get(1).getID());\n    assertEquals(2, acked);\n\n    StreamPendingSummary afterAck = jedis.xpending(STREAM_KEY, GROUP_NAME);\n    assertEquals(0L, afterAck.getTotal());\n  }\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimWithNoackDoesNotCreatePendingAndRemovesClaimedFromPel()\n      throws InterruptedException {\n    Map<String, StreamEntryID> streams = beforeEachClaimTest();\n\n    // Verify pending belongs to consumer-1\n    StreamPendingSummary before = jedis.xpending(STREAM_KEY, GROUP_NAME);\n    assertEquals(2L, before.getTotal());\n    assertEquals(2L, before.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n    assertEquals(0L, before.getConsumerMessageCount().getOrDefault(CONSUMER_2, 0L).longValue());\n\n    // Produce fresh entries\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, HASH);\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, HASH);\n\n    // Claim with NOACK using consumer-2\n    List<Map.Entry<String, List<StreamEntry>>> res = jedis.xreadGroup(GROUP_NAME, CONSUMER_2,\n        XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).noAck().count(10), streams);\n\n    assertNotNull(res);\n    assertEquals(1, res.size());\n\n    List<StreamEntry> entries = res.get(0).getValue();\n    long claimedCount = entries.stream().filter(StreamEntry::isClaimed).count();\n    long freshCount = entries.size() - claimedCount;\n\n    assertEquals(2, claimedCount);\n    assertEquals(2, freshCount);\n\n    // After NOACK read, previously pending entries remain pending\n    StreamPendingSummary afterNoack = jedis.xpending(STREAM_KEY, GROUP_NAME);\n    assertEquals(2L, afterNoack.getTotal());\n\n    // Claimed entries are now owned by consumer-2\n    assertEquals(0L, afterNoack.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n    assertEquals(2L, afterNoack.getConsumerMessageCount().getOrDefault(CONSUMER_2, 0L).longValue());\n  }\n\n  @Test\n  public void xreadGroupPreservesFieldOrder() {\n    String streamKey = \"field-order-stream\";\n    String groupName = \"field-order-group\";\n    String consumerName = \"field-order-consumer\";\n\n    // Use LinkedHashMap to ensure insertion order: a, z, m\n    Map<String, String> fields = new LinkedHashMap<>();\n    fields.put(\"a\", \"1\");\n    fields.put(\"z\", \"4\");\n    fields.put(\"m\", \"2\");\n\n    jedis.xadd(streamKey, StreamEntryID.NEW_ENTRY, fields);\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Entry<String, List<StreamEntry>>> result = jedis.xreadGroup(groupName, consumerName,\n        XReadGroupParams.xReadGroupParams().count(1), streamQuery);\n\n    assertEquals(1, result.size());\n    assertEquals(1, result.get(0).getValue().size());\n\n    StreamEntry entry = result.get(0).getValue().get(0);\n    Map<String, String> returnedFields = entry.getFields();\n\n    // Verify field order is preserved - this will fail with HashMap, pass with LinkedHashMap\n    String[] expectedOrder = {\"a\", \"z\", \"m\"};\n    String[] actualOrder = returnedFields.keySet().toArray(new String[0]);\n\n    assertEquals(expectedOrder.length, actualOrder.length, \"Field count should match\");\n    for (int i = 0; i < expectedOrder.length; i++) {\n      assertEquals(expectedOrder[i], actualOrder[i],\n          String.format(\"Field order mismatch at position %d: expected '%s' but got '%s'. \" +\n              \"Full order: expected [a, z, m], actual %s\",\n              i, expectedOrder[i], actualOrder[i], java.util.Arrays.toString(actualOrder)));\n    }\n  }\n\n  @Test\n  public void xreadAsMapPreservesStreamOrder() {\n    // Test that xreadAsMap preserves the order of streams when reading from multiple streams\n    String streamKey1 = \"{stream-order}-test-1\";\n    String streamKey2 = \"{stream-order}-test-2\";\n    String streamKey3 = \"{stream-order}-test-3\";\n\n    // Add entries to streams in specific order\n    Map<String, String> fields = new LinkedHashMap<>();\n    fields.put(\"field\", \"value1\");\n    jedis.xadd(streamKey1, StreamEntryID.NEW_ENTRY, fields);\n\n    fields.put(\"field\", \"value2\");\n    jedis.xadd(streamKey2, StreamEntryID.NEW_ENTRY, fields);\n\n    fields.put(\"field\", \"value3\");\n    jedis.xadd(streamKey3, StreamEntryID.NEW_ENTRY, fields);\n\n    // Read from multiple streams in specific order\n    Map<String, StreamEntryID> streams = new LinkedHashMap<>();\n    streams.put(streamKey1, new StreamEntryID(\"0-0\"));\n    streams.put(streamKey2, new StreamEntryID(\"0-0\"));\n    streams.put(streamKey3, new StreamEntryID(\"0-0\"));\n\n    Map<String, List<StreamEntry>> result = jedis.xreadAsMap(\n        XReadParams.xReadParams().count(10), streams);\n\n    assertNotNull(result);\n    assertEquals(3, result.size());\n\n    // Verify that the order of streams in the result matches the order in the request\n    String[] expectedOrder = {streamKey1, streamKey2, streamKey3};\n    String[] actualOrder = result.keySet().toArray(new String[0]);\n\n    assertEquals(expectedOrder.length, actualOrder.length, \"Stream count should match\");\n    for (int i = 0; i < expectedOrder.length; i++) {\n      assertEquals(expectedOrder[i], actualOrder[i],\n          String.format(\"Stream order mismatch at position %d: expected '%s' but got '%s'\",\n              i, expectedOrder[i], actualOrder[i]));\n    }\n  }\n\n  // ========== Idempotent Producer Tests ==========\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpAuto() {\n    // Add entry with IDMPAUTO\n    Map<String, String> message = new HashMap<>();\n    message.put(\"order\", \"12345\");\n    message.put(\"amount\", \"100.00\");\n\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmpAuto(\"producer-1\"),\n            message);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY));\n\n    // Add same message again with same producer - should return same ID (duplicate detected)\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmpAuto(\"producer-1\"),\n            message);\n    assertEquals(id1, id2); // Duplicate returns same entry ID as before\n    assertEquals(1L, jedis.xlen(STREAM_KEY)); // Stream length unchanged\n\n    // Add same message with different producer - should succeed\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmpAuto(\"producer-2\"),\n            message);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY));\n\n    // Add different message with same producer - should succeed\n    Map<String, String> message2 = new HashMap<>();\n    message2.put(\"order\", \"67890\");\n    message2.put(\"amount\", \"200.00\");\n\n    StreamEntryID id4 = jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmpAuto(\"producer-1\"),\n            message2);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmp() {\n    Map<String, String> hash1 = singletonMap(\"field1\", \"value1\");\n    Map<String, String> hash2 = singletonMap(\"field2\", \"value2\");\n\n    // Add entry with explicit idempotent ID\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), hash1);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY));\n\n    // Add with same producer and idempotent ID - should return same ID (duplicate detected)\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), hash2);\n    assertEquals(id1, id2); // Duplicate returns same entry ID as before\n    assertEquals(1L, jedis.xlen(STREAM_KEY)); // Stream length unchanged\n\n    // Add with same producer but different idempotent ID - should succeed\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmp(\"producer-1\", \"iid-002\"), hash1);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY));\n\n    // Add with different producer but same idempotent ID - should succeed\n    StreamEntryID id4 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmp(\"producer-2\", \"iid-001\"), hash1);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXcfgset() {\n    // Add an entry to create the stream\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, singletonMap(\"field\", \"value\"));\n\n    // Configure idempotent producer settings\n    String result = jedis.xcfgset(STREAM_KEY,\n            XCfgSetParams.xCfgSetParams().idmpDuration(1000).idmpMaxsize(500));\n    assertEquals(\"OK\", result);\n\n    // Verify settings via XINFO STREAM\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY);\n    assertEquals(Long.valueOf(1000), info.getIdmpDuration());\n    assertEquals(Long.valueOf(500), info.getIdmpMaxsize());\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXinfoStreamIdempotentFields() {\n    Map<String, String> hash1 = singletonMap(\"field1\", \"value1\");\n    Map<String, String> hash2 = singletonMap(\"field2\", \"value2\");\n\n    // Add an entry to create the stream\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, hash1);\n\n    // Configure idempotent settings\n    jedis.xcfgset(STREAM_KEY,\n            XCfgSetParams.xCfgSetParams().idmpDuration(100).idmpMaxsize(100));\n\n    // Add some entries with idempotent IDs\n    jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), hash1);\n    jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmp(\"producer-1\", \"iid-002\"), hash2);\n    jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmp(\"producer-2\", \"iid-001\"), hash1);\n\n    // Try to add a duplicate\n    jedis.xadd(STREAM_KEY, XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), hash2);\n\n    // Check XINFO STREAM response\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY);\n\n    // Verify idempotent configuration fields\n    assertEquals(Long.valueOf(100), info.getIdmpDuration());\n    assertEquals(Long.valueOf(100), info.getIdmpMaxsize());\n\n    // Verify idempotent statistics fields\n    assertEquals(Long.valueOf(2), info.getPidsTracked()); // 2 producers\n    assertEquals(Long.valueOf(3), info.getIidsTracked()); // 3 unique IDs\n    assertEquals(Long.valueOf(3), info.getIidsAdded()); // 3 entries added\n    assertEquals(Long.valueOf(1), info.getIidsDuplicates()); // 1 duplicate rejected\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpWithTrimming() {\n    // Add first entry with IDMPAUTO and trimming\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmpAuto(\"producer1\").maxLen(2), singletonMap(\"field\", \"value1\"));\n    assertNotNull(id1);\n    assertEquals(1, jedis.xlen(STREAM_KEY));\n\n    // Add duplicate - should return same ID\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmpAuto(\"producer1\").maxLen(2), singletonMap(\"field\", \"value1\"));\n    assertEquals(id1, id2);\n    assertEquals(1, jedis.xlen(STREAM_KEY)); // Still 1 entry\n\n    // Add different message - should add new entry and trim\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY,\n            XAddParams.xAddParams().idmpAuto(\"producer1\").maxLen(2), singletonMap(\"field\", \"value2\"));\n    assertNotNull(id3);\n    assertNotEquals(id1, id3); // Different IDs\n    assertEquals(2, jedis.xlen(STREAM_KEY)); // Now 2 entries\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXcfgsetDefaults() {\n    jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, singletonMap(\"init\", \"value\"));\n\n    // Verify default values\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY);\n    assertEquals(Long.valueOf(100), info.getIdmpDuration());\n    assertEquals(Long.valueOf(100), info.getIdmpMaxsize());\n\n    assertEquals(\"OK\", jedis.xcfgset(STREAM_KEY,\n            XCfgSetParams.xCfgSetParams().idmpDuration(200).idmpMaxsize(200)));\n\n    StreamInfo infoAfter = jedis.xinfoStream(STREAM_KEY);\n    assertEquals(Long.valueOf(200), infoAfter.getIdmpDuration());\n    assertEquals(Long.valueOf(200), infoAfter.getIdmpMaxsize());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/StringValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.resps.LCSMatchResult;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class StringValuesCommandsTest extends JedisCommandsTestBase {\n\n  public StringValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void setAndGet() {\n    String status = jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    String value = jedis.get(\"foo\");\n    assertEquals(\"bar\", value);\n\n    assertNull(jedis.get(\"bar\"));\n  }\n\n  @Test\n  public void getSet() {\n    String value = jedis.getSet(\"foo\", \"bar\");\n    assertNull(value);\n    value = jedis.get(\"foo\");\n    assertEquals(\"bar\", value);\n  }\n\n  @Test\n  public void getDel() {\n    String status = jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    String value = jedis.getDel(\"foo\");\n    assertEquals(\"bar\", value);\n\n    assertNull(jedis.get(\"foo\"));\n  }\n\n  @Test\n  public void getEx() {\n    assertNull(jedis.getEx(\"foo\", GetExParams.getExParams().ex(1)));\n    jedis.set(\"foo\", \"bar\");\n\n    assertEquals(\"bar\", jedis.getEx(\"foo\", GetExParams.getExParams().ex(10)));\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 0 && ttl <= 10);\n\n    assertEquals(\"bar\", jedis.getEx(\"foo\", GetExParams.getExParams().px(20000l)));\n    ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 10 && ttl <= 20);\n\n    assertEquals(\"bar\",\n      jedis.getEx(\"foo\", GetExParams.getExParams().exAt(System.currentTimeMillis() / 1000 + 30)));\n    ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 20 && ttl <= 30);\n\n    assertEquals(\"bar\",\n      jedis.getEx(\"foo\", GetExParams.getExParams().pxAt(System.currentTimeMillis() + 40000l)));\n    ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 30 && ttl <= 40);\n\n    assertEquals(\"bar\", jedis.getEx(\"foo\", GetExParams.getExParams().persist()));\n    assertEquals(-1, jedis.ttl(\"foo\"));\n  }\n\n  @Test\n  public void mget() {\n    List<String> values = jedis.mget(\"foo\", \"bar\");\n    List<String> expected = new ArrayList<String>();\n    expected.add(null);\n    expected.add(null);\n\n    assertEquals(expected, values);\n\n    jedis.set(\"foo\", \"bar\");\n\n    expected = new ArrayList<String>();\n    expected.add(\"bar\");\n    expected.add(null);\n    values = jedis.mget(\"foo\", \"bar\");\n\n    assertEquals(expected, values);\n\n    jedis.set(\"bar\", \"foo\");\n\n    expected = new ArrayList<String>();\n    expected.add(\"bar\");\n    expected.add(\"foo\");\n    values = jedis.mget(\"foo\", \"bar\");\n\n    assertEquals(expected, values);\n  }\n\n  @Test\n  public void setnx() {\n    assertEquals(1, jedis.setnx(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n\n    assertEquals(0, jedis.setnx(\"foo\", \"bar2\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n  }\n\n  @Test\n  public void setex() {\n    String status = jedis.setex(\"foo\", 20, \"bar\");\n    assertEquals(\"OK\", status);\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 0 && ttl <= 20);\n  }\n\n  @Test\n  public void mset() {\n    String status = jedis.mset(\"foo\", \"bar\", \"bar\", \"foo\");\n    assertEquals(\"OK\", status);\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(\"foo\", jedis.get(\"bar\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void msetnx() {\n    assertEquals(1, jedis.msetnx(\"foo\", \"bar\", \"bar\", \"foo\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(\"foo\", jedis.get(\"bar\"));\n\n    assertEquals(0, jedis.msetnx(\"foo\", \"bar1\", \"bar2\", \"foo2\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(\"foo\", jedis.get(\"bar\"));\n  }\n\n  @Test\n  public void incr() {\n    assertEquals(1, jedis.incr(\"foo\"));\n    assertEquals(2, jedis.incr(\"foo\"));\n  }\n\n  @Test\n  public void incrWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.incr(\"foo\"));\n  }\n\n  @Test\n  public void incrBy() {\n    assertEquals(2, jedis.incrBy(\"foo\", 2));\n    assertEquals(5, jedis.incrBy(\"foo\", 3));\n  }\n\n  @Test\n  public void incrByWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.incrBy(\"foo\", 2));\n  }\n\n  @Test\n  public void incrByFloat() {\n    assertEquals(10.5, jedis.incrByFloat(\"foo\", 10.5), 0.0);\n    assertEquals(10.6, jedis.incrByFloat(\"foo\", 0.1), 0.0);\n  }\n\n  @Test\n  public void incrByFloatWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.incrByFloat(\"foo\", 2d));\n  }\n\n  @Test\n  public void decrWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.decr(\"foo\"));\n  }\n\n  @Test\n  public void decr() {\n    assertEquals(-1, jedis.decr(\"foo\"));\n    assertEquals(-2, jedis.decr(\"foo\"));\n  }\n\n  @Test\n  public void decrBy() {\n    assertEquals(-2, jedis.decrBy(\"foo\", 2));\n    assertEquals(-4, jedis.decrBy(\"foo\", 2));\n  }\n\n  @Test\n  public void decrByWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.decrBy(\"foo\", 2));\n  }\n\n  @Test\n  public void append() {\n    assertEquals(3, jedis.append(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(6, jedis.append(\"foo\", \"bar\"));\n    assertEquals(\"barbar\", jedis.get(\"foo\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void substr() {\n    jedis.set(\"s\", \"This is a string\");\n    assertEquals(\"This\", jedis.substr(\"s\", 0, 3));\n    assertEquals(\"ing\", jedis.substr(\"s\", -3, -1));\n    assertEquals(\"This is a string\", jedis.substr(\"s\", 0, -1));\n    assertEquals(\" string\", jedis.substr(\"s\", 9, 100000));\n  }\n\n  @Test\n  public void strlen() {\n    String str = \"This is a string\";\n    jedis.set(\"s\", str);\n    assertEquals(str.length(), jedis.strlen(\"s\"));\n  }\n\n  @Test\n  public void incrLargeNumbers() {\n    assertEquals(1, jedis.incr(\"foo\"));\n    assertEquals(1L + Integer.MAX_VALUE, jedis.incrBy(\"foo\", Integer.MAX_VALUE));\n  }\n\n  @Test\n  public void incrReallyLargeNumbers() {\n    jedis.set(\"foo\", Long.toString(Long.MAX_VALUE));\n    assertThrows(JedisDataException.class, () -> jedis.incr(\"foo\"));\n  }\n\n  @Test\n  public void psetex() {\n    String status = jedis.psetex(\"foo\", 20000, \"bar\");\n    assertEquals(\"OK\", status);\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 0 && ttl <= 20000);\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lcs() {\n    jedis.mset(\"key1\", \"ohmytext\", \"key2\", \"mynewtext\");\n\n    LCSMatchResult stringMatchResult = jedis.lcs(\"key1\", \"key2\", LCSParams.LCSParams());\n    assertEquals(\"mytext\", stringMatchResult.getMatchString());\n\n    stringMatchResult = jedis.lcs(\"key1\", \"key2\", LCSParams.LCSParams().idx().withMatchLen());\n    assertEquals(stringMatchResult.getLen(), 6);\n    assertEquals(2, stringMatchResult.getMatches().size());\n\n    stringMatchResult = jedis.lcs(\"key1\", \"key2\", LCSParams.LCSParams().idx().minMatchLen(10));\n    assertEquals(0, stringMatchResult.getMatches().size());\n  }\n\n  // MSETEX NX + expiration matrix\n  static Stream<Arguments> msetexNxArgsProvider() {\n    return Stream.of(Arguments.of(\"EX\", new MSetExParams().nx().ex(5)),\n      Arguments.of(\"PX\", new MSetExParams().nx().px(5000)),\n      Arguments.of(\"EXAT\", new MSetExParams().nx().exAt(System.currentTimeMillis() / 1000 + 5)),\n      Arguments.of(\"PXAT\", new MSetExParams().nx().pxAt(System.currentTimeMillis() + 5000)),\n      Arguments.of(\"KEEPTTL\", new MSetExParams().nx().keepTtl()));\n  }\n\n  @ParameterizedTest(name = \"MSETEX NX + {0}\")\n  @MethodSource(\"msetexNxArgsProvider\")\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetexNx_parametrized(String optionLabel, MSetExParams params) {\n    String k1 = \"{t}msetex:js:k1\";\n    String k2 = \"{t}msetex:js:k2\";\n\n    boolean result = jedis.msetex(params, k1, \"v1\", k2, \"v2\");\n    assertTrue(result);\n\n    long ttl = jedis.ttl(k1);\n    if (\"KEEPTTL\".equals(optionLabel)) {\n      assertEquals(-1L, ttl);\n    } else {\n      assertTrue(ttl > 0L);\n    }\n  }\n\n  @Test\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetexXxEx() {\n    String k1 = \"{t}msetex:js:xx:k1\";\n    String k2 = \"{t}msetex:js:xx:k2\";\n\n    // First set the keys so they exist (XX requires existing keys)\n    jedis.set(k1, \"initial1\");\n    jedis.set(k2, \"initial2\");\n\n    // Now use MSETEX with XX and EX\n    MSetExParams params = new MSetExParams().xx().ex(5);\n    boolean result = jedis.msetex(params, k1, \"v1\", k2, \"v2\");\n    assertTrue(result);\n\n    // Verify values were updated\n    assertEquals(\"v1\", jedis.get(k1));\n    assertEquals(\"v2\", jedis.get(k2));\n\n    // Verify TTL is set\n    long ttl = jedis.ttl(k1);\n    assertTrue(ttl > 0L);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.ArgumentMatchers.any;\n\nimport static redis.clients.jedis.Protocol.Command.INCR;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.SET;\n\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.Transaction;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class TransactionCommandsTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n\n  final byte[] bmykey = { 0x42, 0x02, 0x03, 0x04 };\n\n  Jedis nj;\n\n  public TransactionCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() throws Exception {\n    super.setUp();\n\n    nj = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(500).build());\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    nj.close();\n    super.tearDown();\n  }\n\n  @Test\n  public void multi() {\n    Transaction trans = jedis.multi();\n\n    trans.sadd(\"foo\", \"a\");\n    trans.sadd(\"foo\", \"b\");\n    trans.scard(\"foo\");\n\n    List<Object> response = trans.exec();\n\n    List<Object> expected = new ArrayList<Object>();\n    expected.add(1L);\n    expected.add(1L);\n    expected.add(2L);\n    assertEquals(expected, response);\n\n    // Binary\n    trans = jedis.multi();\n\n    trans.sadd(bfoo, ba);\n    trans.sadd(bfoo, bb);\n    trans.scard(bfoo);\n\n    response = trans.exec();\n\n    expected = new ArrayList<Object>();\n    expected.add(1L);\n    expected.add(1L);\n    expected.add(2L);\n    assertEquals(expected, response);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void watch() throws UnknownHostException, IOException {\n    jedis.watch(\"mykey\", \"somekey\");\n    Transaction t = jedis.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", \"foo\");\n    List<Object> resp = t.exec();\n    assertNull(resp);\n    assertEquals(\"bar\", jedis.get(\"mykey\"));\n\n    // Binary\n    jedis.watch(bmykey, \"foobar\".getBytes());\n    t = jedis.multi();\n\n    nj.set(bmykey, bbar);\n\n    t.set(bmykey, bfoo);\n    resp = t.exec();\n    assertNull(resp);\n    assertArrayEquals(bbar, jedis.get(bmykey));\n  }\n\n  @Test\n  public void unwatch() {\n    jedis.watch(\"mykey\");\n    jedis.get(\"mykey\");\n    String val = \"foo\";\n    assertEquals(\"OK\", jedis.unwatch());\n    Transaction t = jedis.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", val);\n    List<Object> resp = t.exec();\n    assertEquals(1, resp.size());\n    assertEquals(\"OK\", resp.get(0));\n\n    // Binary\n    jedis.watch(bmykey);\n    jedis.get(bmykey);\n    byte[] bval = bfoo;\n    assertEquals(\"OK\", jedis.unwatch());\n    t = jedis.multi();\n\n    nj.set(bmykey, bbar);\n\n    t.set(bmykey, bval);\n    resp = t.exec();\n    assertEquals(1, resp.size());\n    assertEquals(\"OK\", resp.get(0));\n  }\n\n  @Test\n  public void validateWhenInMulti() {\n    jedis.multi();\n    assertThrows(IllegalStateException.class, () -> jedis.ping());\n  }\n\n  @Test\n  public void discard() {\n    Transaction t = jedis.multi();\n    String status = t.discard();\n    assertEquals(\"OK\", status);\n  }\n\n  @Test\n  public void discardFail() {\n    Transaction trans = jedis.multi();\n    trans.set(\"a\", \"a\");\n    trans.set(\"b\", \"b\");\n\n    try (MockedStatic<Protocol> protocol = Mockito.mockStatic(Protocol.class)) {\n      protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class);\n\n      trans.discard();\n      fail(\"Should get mocked JedisConnectionException.\");\n    } catch (JedisConnectionException jce) {\n      // should be here\n    } finally {\n      // close() should pass\n      trans.close();\n    }\n    assertTrue(jedis.isBroken());\n  }\n\n  @Test\n  public void execFail() {\n    Transaction trans = jedis.multi();\n    trans.set(\"a\", \"a\");\n    trans.set(\"b\", \"b\");\n\n    try (MockedStatic<Protocol> protocol = Mockito.mockStatic(Protocol.class)) {\n      protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class);\n\n      trans.exec();\n      fail(\"Should get mocked JedisConnectionException.\");\n    } catch (JedisConnectionException jce) {\n      // should be here\n    } finally {\n      // close() should pass\n      trans.close();\n    }\n    assertTrue(jedis.isBroken());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transactionResponse() {\n    jedis.set(\"string\", \"foo\");\n    jedis.lpush(\"list\", \"foo\");\n    jedis.hset(\"hash\", \"foo\", \"bar\");\n    jedis.zadd(\"zset\", 1, \"foo\");\n    jedis.sadd(\"set\", \"foo\");\n\n    Transaction t = jedis.multi();\n    Response<String> string = t.get(\"string\");\n    Response<String> list = t.lpop(\"list\");\n    Response<String> hash = t.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = t.zrange(\"zset\", 0, -1);\n    Response<String> set = t.spop(\"set\");\n    t.exec();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transactionResponseBinary() {\n    jedis.set(\"string\", \"foo\");\n    jedis.lpush(\"list\", \"foo\");\n    jedis.hset(\"hash\", \"foo\", \"bar\");\n    jedis.zadd(\"zset\", 1, \"foo\");\n    jedis.sadd(\"set\", \"foo\");\n\n    Transaction t = jedis.multi();\n    Response<byte[]> string = t.get(\"string\".getBytes());\n    Response<byte[]> list = t.lpop(\"list\".getBytes());\n    Response<byte[]> hash = t.hget(\"hash\".getBytes(), \"foo\".getBytes());\n    Response<List<byte[]>> zset = t.zrange(\"zset\".getBytes(), 0, -1);\n    Response<byte[]> set = t.spop(\"set\".getBytes());\n    t.exec();\n\n    assertArrayEquals(\"foo\".getBytes(), string.get());\n    assertArrayEquals(\"foo\".getBytes(), list.get());\n    assertArrayEquals(\"bar\".getBytes(), hash.get());\n    assertArrayEquals(\"foo\".getBytes(), zset.get().iterator().next());\n    assertArrayEquals(\"foo\".getBytes(), set.get());\n  }\n\n  @Test\n  public void transactionResponseWithinPipeline() {\n    jedis.set(\"string\", \"foo\");\n\n    Transaction t = jedis.multi();\n    Response<String> string = t.get(\"string\");\n    assertThrows(IllegalStateException.class, string::get);\n    t.exec();\n  }\n\n  @Test\n  public void transactionResponseWithError() {\n    Transaction t = jedis.multi();\n    t.set(\"foo\", \"bar\");\n    Response<Set<String>> error = t.smembers(\"foo\");\n    Response<String> r = t.get(\"foo\");\n    List<Object> l = t.exec();\n    assertSame(JedisDataException.class, l.get(1).getClass());\n    try {\n      error.get();\n      fail(\"We expect exception here!\");\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(\"bar\", r.get());\n  }\n//\n//  @Test\n//  public void execGetResponse() {\n//    Transaction t = jedis.multi();\n//\n//    t.set(\"foo\", \"bar\");\n//    t.smembers(\"foo\");\n//    t.get(\"foo\");\n//\n//    List<Response<?>> lr = t.execGetResponse();\n//    try {\n//      lr.get(1).get();\n//      fail(\"We expect exception here!\");\n//    } catch (JedisDataException e) {\n//      // that is fine we should be here\n//    }\n//    assertEquals(\"bar\", lr.get(2).get());\n//  }\n//\n//  @Test\n//  public void select() {\n//    jedis.select(1);\n//    jedis.set(\"foo\", \"bar\");\n//    jedis.watch(\"foo\");\n//    Transaction t = jedis.multi();\n//    t.select(0);\n//    t.set(\"bar\", \"foo\");\n//\n//    Jedis jedis2 = createJedis();\n//    jedis2.select(1);\n//    jedis2.set(\"foo\", \"bar2\");\n//\n//    List<Object> results = t.exec();\n//    assertNull(results);\n//  }\n\n  @Test\n  public void testResetStateWhenInMulti() {\n    Transaction t = jedis.multi();\n    t.set(\"foooo\", \"barrr\");\n\n    jedis.resetState();\n    assertNull(jedis.get(\"foooo\"));\n  }\n//\n//  @Test\n//  public void testResetStateWhenInMultiWithinPipeline() {\n//    Pipeline p = jedis.pipelined();\n//    p.multi();\n//    p.set(\"foooo\", \"barrr\");\n//\n//    jedis.resetState();\n//    assertNull(jedis.get(\"foooo\"));\n//  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testResetStateWhenInWatch() {\n    jedis.watch(\"mykey\", \"somekey\");\n\n    // state reset : unwatch\n    jedis.resetState();\n\n    Transaction t = jedis.multi();\n\n    nj.set(\"mykey\", \"bar\");\n\n    t.set(\"mykey\", \"foo\");\n    List<Object> resp = t.exec();\n    assertNotNull(resp);\n    assertEquals(1, resp.size());\n    assertEquals(\"foo\", jedis.get(\"mykey\"));\n  }\n\n  @Test\n  public void testResetStateWithFullyExecutedTransaction() {\n    Jedis jedis2 = createJedis();\n\n    Transaction t = jedis2.multi();\n    t.set(\"mykey\", \"foo\");\n    t.get(\"mykey\");\n\n    List<Object> resp = t.exec();\n    assertNotNull(resp);\n    assertEquals(2, resp.size());\n\n    jedis2.resetState();\n    jedis2.close();\n  }\n\n  @Test\n  public void testCloseable() {\n    // we need to test with fresh instance of Jedis\n    Jedis jedis2 = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().timeoutMillis(500).build());;\n\n    Transaction transaction = jedis2.multi();\n    transaction.set(\"a\", \"1\");\n    transaction.set(\"b\", \"2\");\n\n    transaction.close();\n\n    try {\n      transaction.exec();\n      fail(\"close should discard transaction\");\n    } catch (IllegalStateException e) {\n      assertTrue(e.getMessage().contains(\"EXEC without MULTI\"));\n      // pass\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testTransactionWithGeneralCommand() {\n    Transaction t = jedis.multi();\n    t.set(\"string\", \"foo\");\n    t.lpush(\"list\", \"foo\");\n    t.hset(\"hash\", \"foo\", \"bar\");\n    t.zadd(\"zset\", 1, \"foo\");\n    t.sendCommand(SET, \"x\", \"1\");\n    t.sadd(\"set\", \"foo\");\n    t.sendCommand(INCR, \"x\");\n    Response<String> string = t.get(\"string\");\n    Response<String> list = t.lpop(\"list\");\n    Response<String> hash = t.hget(\"hash\", \"foo\");\n    Response<List<String>> zset = t.zrange(\"zset\", 0, -1);\n    Response<String> set = t.spop(\"set\");\n    Response<Object> x = t.sendCommand(GET, \"x\");\n    t.exec();\n\n    assertEquals(\"foo\", string.get());\n    assertEquals(\"foo\", list.get());\n    assertEquals(\"bar\", hash.get());\n    assertEquals(\"foo\", zset.get().iterator().next());\n    assertEquals(\"foo\", set.get());\n    assertEquals(\"2\", SafeEncoder.encode((byte[]) x.get()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transactionResponseWithErrorWithGeneralCommand() {\n    Transaction t = jedis.multi();\n    t.set(\"foo\", \"bar\");\n    t.sendCommand(SET, \"x\", \"1\");\n    Response<Set<String>> error = t.smembers(\"foo\");\n    Response<String> r = t.get(\"foo\");\n    Response<Object> x = t.sendCommand(GET, \"x\");\n    t.sendCommand(INCR, \"x\");\n    List<Object> l = t.exec();\n    assertSame(JedisDataException.class, l.get(2).getClass());\n    try {\n      error.get();\n      fail(\"We expect exception here!\");\n    } catch (JedisDataException e) {\n      // that is fine we should be here\n    }\n    assertEquals(\"bar\", r.get());\n    assertEquals(\"1\", SafeEncoder.encode((byte[]) x.get()));\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/VariadicCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class VariadicCommandsTest extends JedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x0A };\n  final byte[] bfoo2 = { 0x01, 0x02, 0x03, 0x04, 0x0B };\n\n  public VariadicCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void hdel() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    hash.put(\"foo2\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertEquals(0, jedis.hdel(\"bar\", \"foo\", \"foo1\"));\n    assertEquals(0, jedis.hdel(\"foo\", \"foo\", \"foo1\"));\n    assertEquals(2, jedis.hdel(\"foo\", \"bar\", \"foo2\"));\n    assertNull(jedis.hget(\"foo\", \"bar\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    bhash.put(bfoo2, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertEquals(0, jedis.hdel(bbar, bfoo, bfoo1));\n    assertEquals(0, jedis.hdel(bfoo, bfoo, bfoo1));\n    assertEquals(2, jedis.hdel(bfoo, bbar, bfoo2));\n    assertNull(jedis.hget(bfoo, bbar));\n\n  }\n\n  @Test\n  public void rpush() {\n    long size = jedis.rpush(\"foo\", \"bar\", \"foo\");\n    assertEquals(2, size);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"bar\");\n    expected.add(\"foo\");\n\n    List<String> values = jedis.lrange(\"foo\", 0, -1);\n    assertEquals(expected, values);\n\n    // Binary\n    size = jedis.rpush(bfoo, bbar, bfoo);\n    assertEquals(2, size);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bbar);\n    bexpected.add(bfoo);\n\n    List<byte[]> bvalues = jedis.lrange(bfoo, 0, -1);\n    assertByteArrayListEquals(bexpected, bvalues);\n\n  }\n\n  @Test\n  public void lpush() {\n    long size = jedis.lpush(\"foo\", \"bar\", \"foo\");\n    assertEquals(2, size);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"foo\");\n    expected.add(\"bar\");\n\n    List<String> values = jedis.lrange(\"foo\", 0, -1);\n    assertEquals(expected, values);\n\n    // Binary\n    size = jedis.lpush(bfoo, bbar, bfoo);\n    assertEquals(2, size);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bfoo);\n    bexpected.add(bbar);\n\n    List<byte[]> bvalues = jedis.lrange(bfoo, 0, -1);\n    assertByteArrayListEquals(bexpected, bvalues);\n\n  }\n\n  @Test\n  public void sadd() {\n    long status = jedis.sadd(\"foo\", \"bar\", \"foo1\");\n    assertEquals(2, status);\n\n    status = jedis.sadd(\"foo\", \"bar\", \"car\");\n    assertEquals(1, status);\n\n    status = jedis.sadd(\"foo\", \"bar\", \"foo1\");\n    assertEquals(0, status);\n\n    status = jedis.sadd(bfoo, bbar, bfoo1);\n    assertEquals(2, status);\n\n    status = jedis.sadd(bfoo, bbar, bcar);\n    assertEquals(1, status);\n\n    status = jedis.sadd(bfoo, bbar, bfoo1);\n    assertEquals(0, status);\n\n  }\n\n  @Test\n  public void zadd() {\n    Map<String, Double> scoreMembers = new HashMap<String, Double>();\n    scoreMembers.put(\"bar\", 1d);\n    scoreMembers.put(\"foo\", 10d);\n\n    long status = jedis.zadd(\"foo\", scoreMembers);\n    assertEquals(2, status);\n\n    scoreMembers.clear();\n    scoreMembers.put(\"car\", 0.1d);\n    scoreMembers.put(\"bar\", 2d);\n\n    status = jedis.zadd(\"foo\", scoreMembers);\n    assertEquals(1, status);\n\n    Map<byte[], Double> bscoreMembers = new HashMap<byte[], Double>();\n    bscoreMembers.put(bbar, 1d);\n    bscoreMembers.put(bfoo, 10d);\n\n    status = jedis.zadd(bfoo, bscoreMembers);\n    assertEquals(2, status);\n\n    bscoreMembers.clear();\n    bscoreMembers.put(bcar, 0.1d);\n    bscoreMembers.put(bbar, 2d);\n\n    status = jedis.zadd(bfoo, bscoreMembers);\n    assertEquals(1, status);\n\n  }\n\n  @Test\n  public void zrem() {\n    jedis.zadd(\"foo\", 1d, \"bar\");\n    jedis.zadd(\"foo\", 2d, \"car\");\n    jedis.zadd(\"foo\", 3d, \"foo1\");\n\n    long status = jedis.zrem(\"foo\", \"bar\", \"car\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"foo1\");\n\n    assertEquals(2, status);\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    status = jedis.zrem(\"foo\", \"bar\", \"car\");\n    assertEquals(0, status);\n\n    status = jedis.zrem(\"foo\", \"bar\", \"foo1\");\n    assertEquals(1, status);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, bbar);\n    jedis.zadd(bfoo, 2d, bcar);\n    jedis.zadd(bfoo, 3d, bfoo1);\n\n    status = jedis.zrem(bfoo, bbar, bcar);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bfoo1);\n\n    assertEquals(2, status);\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n\n    status = jedis.zrem(bfoo, bbar, bcar);\n    assertEquals(0, status);\n\n    status = jedis.zrem(bfoo, bbar, bfoo1);\n    assertEquals(1, status);\n\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/jedis/VectorSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.jedis;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.util.VectorTestUtils;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\nimport redis.clients.jedis.resps.VSimScoreAttribs;\nimport redis.clients.jedis.resps.VectorInfo;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static redis.clients.jedis.util.VectorTestUtils.floatArrayToFP32Bytes;\n\n/**\n * Integration tests for Vector Set commands using Jedis client.\n * <p>\n * Tests are parameterized to run against multiple RESP protocol versions. Repeating tests from\n * {@link redis.clients.jedis.commands.unified.VectorSetCommandsTestBase} against Jedis client.s\n * </p>\n */\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\n@Tag(\"vector-set\")\npublic class VectorSetCommandsTest extends JedisCommandsTestBase {\n\n  public VectorSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  /**\n   * Test the basic VADD method with float array.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFloatArray(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:F\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Add a new element\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n\n    // Test duplicate addition - should return false\n    result = jedis.vadd(testKey, vector, elementId);\n    assertFalse(result);\n\n    // Cardinality should remain the same\n    assertEquals(1L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VADD method with float array and parameters. Overload 2: vadd(String key, float[] vector,\n   * String element, VAddParams params)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFloatArrayAndParams(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:G\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Create parameters\n    VAddParams params = new VAddParams();\n\n    // Add a new element with parameters\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n  }\n\n  /**\n   * Test VADD method with FP32 byte blob. Overload 3: vaddFP32(String key, byte[] vectorBlob,\n   * String element)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFP32ByteBlob(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:H\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Convert float array to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(vector);\n\n    // Add a new element with FP32 byte blob\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n  }\n\n  /**\n   * Test VADD method with FP32 byte blob and parameters. Overload 4: vaddFP32(String key, byte[]\n   * vectorBlob, String element, VAddParams params)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFP32ByteBlobAndParams(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:I\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Convert float array to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(vector);\n\n    // Create parameters\n    VAddParams params = new VAddParams();\n\n    // Add a new element with FP32 byte blob and parameters\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n  }\n\n  /**\n   * Test VADD with quantization parameters. Demonstrates how quantization parameters can be used\n   * with VADD.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithQuantization(TestInfo testInfo) {\n    String baseKey = testInfo.getDisplayName() + \":test:vector:set\";\n    float[] vector = { 1.0f, 2.0f };\n    // Test with basic VADD first to establish a baseline\n    String defaultKey = baseKey + \":default\";\n    jedis.del(defaultKey);\n    boolean result = jedis.vadd(defaultKey, vector, \"point:DEFAULT\");\n    assertTrue(result);\n\n    List<Double> defaultVector = jedis.vemb(defaultKey, \"point:DEFAULT\");\n    assertEquals(2, defaultVector.size());\n    assertEquals(1.0, defaultVector.get(0), 0.01);\n    assertEquals(2.0, defaultVector.get(1), 0.01);\n    assertEquals(1L, jedis.vcard(defaultKey));\n\n    // Test with Q8 quantization parameters\n    String q8Key = baseKey + \":q8\";\n    VAddParams quantParams = new VAddParams().q8();\n    jedis.del(q8Key);\n    result = jedis.vadd(q8Key, vector, \"point:Q8\", quantParams);\n    assertTrue(result);\n\n    List<Double> quantVector = jedis.vemb(q8Key, \"point:Q8\");\n    assertEquals(2, quantVector.size());\n    assertEquals(1.0, quantVector.get(0), 0.01);\n    assertEquals(2.0, quantVector.get(1), 0.01);\n    assertEquals(1L, jedis.vcard(q8Key));\n\n    // Test with NOQUANT quantization parameters\n    String noQuantKey = baseKey + \":noQuant\";\n    VAddParams noQuantParams = new VAddParams().q8();\n    jedis.del(noQuantKey);\n    result = jedis.vadd(noQuantKey, vector, \"point:NOQUANT\", noQuantParams);\n    assertTrue(result);\n\n    List<Double> noQuantVector = jedis.vemb(noQuantKey, \"point:NOQUANT\");\n    assertEquals(2, noQuantVector.size());\n    assertEquals(1.0, noQuantVector.get(0), 0.01);\n    assertEquals(2.0, noQuantVector.get(1), 0.01);\n    assertEquals(1L, jedis.vcard(noQuantKey));\n  }\n\n  /**\n   * Test VADD with dimension reduction using float array. Verifies that high-dimensional vectors\n   * are reduced to target dimensions.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithReduceDimension(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:REDUCED\";\n    // Use a 4-dimensional vector that will be reduced to 2 dimensions\n    float[] highDimVector = { 1.0f, 2.0f, 3.0f, 4.0f };\n    int targetDim = 2;\n\n    // Create parameters for dimension reduction\n    VAddParams params = new VAddParams();\n\n    // Add element with dimension reduction\n    boolean result = jedis.vadd(testKey, highDimVector, elementId, targetDim, params);\n    assertTrue(result);\n\n    // Verify cardinality\n    assertEquals(1L, jedis.vcard(testKey));\n\n    // Verify the vector was reduced to target dimensions\n    assertEquals(targetDim, jedis.vdim(testKey));\n\n    // Retrieve and verify the reduced vector\n    List<Double> reducedVector = jedis.vemb(testKey, elementId);\n    assertEquals(targetDim, reducedVector.size());\n\n    // The values will be different due to random projection, but should exist\n    assertNotNull(reducedVector.get(0));\n    assertNotNull(reducedVector.get(1));\n  }\n\n  /**\n   * Test vaddFP32 with dimension reduction using byte blob. Verifies that FP32 format vectors are\n   * properly reduced.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddFP32WithReduceDimension(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:FP32_REDUCED\";\n    // Use a 4-dimensional vector that will be reduced to 2 dimensions\n    float[] highDimVector = { 1.0f, 2.0f, 3.0f, 4.0f };\n    int targetDim = 2;\n\n    // Convert to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(highDimVector);\n\n    // Create parameters for dimension reduction\n    VAddParams params = new VAddParams();\n\n    // Add element with dimension reduction using FP32 format\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId, targetDim, params);\n    assertTrue(result);\n\n    // Verify cardinality\n    assertEquals(1L, jedis.vcard(testKey));\n\n    // Verify the vector was reduced to target dimensions\n    assertEquals(targetDim, jedis.vdim(testKey));\n\n    // Retrieve and verify the reduced vector\n    List<Double> reducedVector = jedis.vemb(testKey, elementId);\n    assertEquals(targetDim, reducedVector.size());\n\n    // The values will be different due to random projection, but should exist\n    assertNotNull(reducedVector.get(0));\n    assertNotNull(reducedVector.get(1));\n  }\n\n  /**\n   * Test VADD with dimension reduction and additional parameters. Verifies that REDUCE works\n   * alongside other VAddParams.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithReduceDimensionAndParams(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:REDUCED_WITH_PARAMS\";\n    // Use a 6-dimensional vector that will be reduced to 3 dimensions\n    float[] highDimVector = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f };\n    int targetDim = 3;\n\n    // Create parameters with quantization and dimension reduction\n    VAddParams params = new VAddParams().q8().ef(100);\n\n    // Add element with dimension reduction and additional parameters\n    boolean result = jedis.vadd(testKey, highDimVector, elementId, targetDim, params);\n    assertTrue(result);\n\n    // Verify cardinality\n    assertEquals(1L, jedis.vcard(testKey));\n\n    // Verify the vector was reduced to target dimensions\n    assertEquals(targetDim, jedis.vdim(testKey));\n\n    // Retrieve and verify the reduced vector\n    List<Double> reducedVector = jedis.vemb(testKey, elementId);\n    assertEquals(targetDim, reducedVector.size());\n\n    // All dimensions should have values (may be quantized)\n    for (Double value : reducedVector) {\n      assertNotNull(value);\n    }\n  }\n\n  /**\n   * Test VADD with SETATTR parameter. Verifies that attributes can be set when adding elements to\n   * vector sets.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithSetAttr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:WITH_ATTR\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Create simple text attributes for the element\n    String attributes = \"category=test,priority=high,score=95.5\";\n\n    // Create parameters with attributes\n    VAddParams params = new VAddParams().setAttr(attributes);\n\n    // Add element with attributes\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n\n    // Verify the attributes were stored correctly using VGETATTR\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VADD with SETATTR and other parameters combined. Verifies that SETATTR works alongside\n   * quantization and other options.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithSetAttrAndQuantization(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:ATTR_QUANT\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Create simple text attributes\n    String attributes = \"type=quantized,method=Q8,timestamp=2024-01-01\";\n\n    // Create parameters with both attributes and quantization\n    VAddParams params = new VAddParams().setAttr(attributes).q8().ef(100);\n\n    // Add element with attributes and quantization\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored (may be quantized)\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.1); // Larger tolerance for quantization\n    assertEquals(2.0, storedVector.get(1), 0.1);\n\n    // Verify the attributes were stored correctly\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VADD with SETATTR using FP32 format. Verifies that attributes work with binary vector\n   * format.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddFP32WithSetAttr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:FP32_ATTR\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Convert to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(vector);\n\n    // Create simple text attributes\n    String attributes = \"format=FP32,source=binary,validated=true\";\n\n    // Create parameters with attributes\n    VAddParams params = new VAddParams().setAttr(attributes);\n\n    // Add element with FP32 format and attributes\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n\n    // Verify the attributes were stored correctly\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VGETATTR command functionality. Verifies that attributes can be retrieved from vector set\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVgetattr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:GETATTR_TEST\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // First add an element without attributes\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // VGETATTR should return null for element without attributes\n    String attrs = jedis.vgetattr(testKey, elementId);\n    assertNull(attrs);\n\n    // Now add an element with attributes\n    String elementWithAttrs = \"point:WITH_ATTRS\";\n    String attributes = \"name=test_point,value=42,active=true\";\n    VAddParams params = new VAddParams().setAttr(attributes);\n    result = jedis.vadd(testKey, vector, elementWithAttrs, params);\n    assertTrue(result);\n\n    // VGETATTR should return the attributes\n    String retrievedAttrs = jedis.vgetattr(testKey, elementWithAttrs);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n\n    // Test VGETATTR with non-existent element\n    String nonExistentAttrs = jedis.vgetattr(testKey, \"non_existent_element\");\n    assertNull(nonExistentAttrs);\n  }\n\n  /**\n   * Test VGETATTR with binary key and element. Verifies that VGETATTR works with byte array keys\n   * and elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVgetattrBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_element_with_attrs\".getBytes();\n    float[] vector = { 1.0f, 2.0f };\n\n    // VGETATTR should return null for element without attributes\n    assertNull(jedis.vgetattr(testKey, elementId));\n\n    // Now add an element with attributes using binary key and element\n    String attributes = \"name=binary_test_point,value=42,active=true\";\n    VAddParams params = new VAddParams().setAttr(attributes);\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // VGETATTR should return the attributes as byte array\n    byte[] retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n\n    // Convert byte array back to string and verify content\n    String retrievedAttrsString = SafeEncoder.encode(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrsString);\n  }\n\n  /**\n   * Test VSETATTR command functionality. Verifies that attributes can be set on vector set\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsetattr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:SETATTR_TEST\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // First add an element without attributes\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // Set attributes using VSETATTR\n    String attributes = \"name=test_point,value=42,active=true\";\n    boolean setResult = jedis.vsetattr(testKey, elementId, attributes);\n    assertTrue(setResult);\n\n    // Verify attributes were set using VGETATTR\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n\n    // Update attributes with new values\n    String updatedAttributes = \"name=updated_point,value=100,active=false,new_field=added\";\n    setResult = jedis.vsetattr(testKey, elementId, updatedAttributes);\n    assertTrue(setResult);\n\n    // Verify updated attributes\n    retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(updatedAttributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VSETATTR with binary key and element. Verifies that VSETATTR works with byte array keys\n   * and elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsetattrBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_setattr_element\".getBytes();\n    float[] vector = { 1.0f, 2.0f };\n\n    // First add an element without attributes\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // Set attributes using binary VSETATTR\n    String attributes = \"name=binary_test_point,value=42,active=true\";\n    byte[] attributesBytes = attributes.getBytes();\n    boolean setResult = jedis.vsetattr(testKey, elementId, attributesBytes);\n    assertTrue(setResult);\n\n    // Verify attributes were set using binary VGETATTR\n    byte[] retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n\n    // Convert back to string and verify\n    String retrievedAttrsString = SafeEncoder.encode(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrsString);\n\n    // Update attributes with new values using binary VSETATTR\n    String updatedAttributes = \"name=updated_binary_point,value=100,active=false,new_field=added\";\n    byte[] updatedAttributesBytes = updatedAttributes.getBytes();\n    setResult = jedis.vsetattr(testKey, elementId, updatedAttributesBytes);\n    assertTrue(setResult);\n\n    // Verify updated attributes using binary VGETATTR\n    retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n\n    String updatedRetrievedString = SafeEncoder.encode(retrievedAttrs);\n    assertEquals(updatedAttributes, updatedRetrievedString);\n  }\n\n  /**\n   * Test VLINKS command functionality. Verifies that vector set links can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinks(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to create a vector set with links\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Get links for element1\n    List<List<String>> links = jedis.vlinks(testKey, \"element1\");\n    assertNotNull(links);\n\n    assertFalse(links.isEmpty());\n    for (List<String> linkList : links) {\n      for (String rawLink : linkList) {\n        assertTrue(rawLink.equals(\"element2\") || rawLink.equals(\"element3\"));\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS command functionality. Verifies that vector set links can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksWithScores(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to create a vector set with links\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Get links for element1\n    List<Map<String, Double>> links = jedis.vlinksWithScores(testKey, \"element1\");\n    assertNotNull(links);\n\n    assertFalse(links.isEmpty());\n    for (Map<String, Double> scores : links) {\n      for (String element : scores.keySet()) {\n        assertTrue(element.equals(\"element2\") || element.equals(\"element3\"));\n        assertTrue(scores.get(element) > 0.0);\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS with binary key and element. Verifies that VLINKS works with byte array keys and\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_element\".getBytes();\n\n    // Add vectors using binary key and elements\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, elementId);\n    jedis.vadd(testKey, vector2, \"element2\".getBytes());\n\n    // Get links using binary VLINKS\n    List<List<byte[]>> binaryLinks = jedis.vlinks(testKey, elementId);\n    assertNotNull(binaryLinks);\n    assertThat(binaryLinks.size(), is(greaterThan(0)));\n\n    // If there are links, verify they are valid strings\n    for (List<byte[]> linkList : binaryLinks) {\n      for (byte[] rawLink : linkList) {\n        String link = SafeEncoder.encode(rawLink);\n        assertThat(link, is(notNullValue()));\n        assertThat(link, not(emptyString()));\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS command functionality. Verifies that vector set links can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksBinaryWithScores(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    // Add some vectors to create a vector set with links\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"element2\".getBytes());\n    jedis.vadd(testKey, vector3, \"element3\".getBytes());\n\n    // Get links for element1\n    List<Map<byte[], Double>> links = jedis.vlinksWithScores(testKey, \"element1\".getBytes());\n    assertNotNull(links);\n\n    assertFalse(links.isEmpty());\n    for (Map<byte[], Double> scores : links) {\n      for (byte[] element : scores.keySet()) {\n        assertTrue(Arrays.equals(element, \"element2\".getBytes())\n            || Arrays.equals(element, \"element3\".getBytes()));\n        assertTrue(scores.get(element) > 0.0);\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS with non-existent element. Verifies that VLINKS handles non-existent elements\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksNonExistent(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:nonexistent\";\n\n    // Add a vector first\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"existing_element\");\n\n    // Try to get links for non-existent element\n    List<List<String>> links = jedis.vlinks(testKey, \"non_existent_element\");\n    // Should return empty list or null for non-existent elements\n    // Exact behavior depends on Redis implementation\n    assertTrue(links == null || links.isEmpty());\n  }\n\n  /**\n   * Test VRANDMEMBER command functionality. Verifies that random vector set members can be\n   * retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmember(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to the set\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Get a single random member\n    String randomMember = jedis.vrandmember(testKey);\n    assertNotNull(randomMember);\n\n    // Should be one of the added elements\n    assertTrue(randomMember.equals(\"element1\") || randomMember.equals(\"element2\")\n        || randomMember.equals(\"element3\"));\n  }\n\n  /**\n   * Test VRANDMEMBER with count parameter. Verifies that multiple random members can be retrieved.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberWithCount(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:count\";\n\n    // Add multiple vectors\n    for (int i = 1; i <= 5; i++) {\n      float[] vector = { (float) i, (float) i };\n      jedis.vadd(testKey, vector, \"element\" + i);\n    }\n\n    // Get 3 random members\n    List<String> randomMembers = jedis.vrandmember(testKey, 3);\n    assertNotNull(randomMembers);\n    assertEquals(3, randomMembers.size());\n\n    // All returned members should be valid element IDs\n    String[] validElements = { \"element1\", \"element2\", \"element3\", \"element4\", \"element5\" };\n    for (String member : randomMembers) {\n      assertTrue(Arrays.asList(validElements).contains(member));\n    }\n\n    // Test with count larger than set size\n    List<String> allMembers = jedis.vrandmember(testKey, 10);\n    assertNotNull(allMembers);\n    assertTrue(allMembers.size() <= 5); // Should not exceed actual set size\n  }\n\n  /**\n   * Test VRANDMEMBER with binary key. Verifies that VRANDMEMBER works with byte array keys.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    // Add vectors using binary key\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"binary_element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"binary_element2\".getBytes());\n\n    // Get random member using binary key\n    byte[] randomMember = jedis.vrandmember(testKey);\n    assertNotNull(randomMember);\n\n    // Convert to string for comparison\n    String randomMemberStr = SafeEncoder.encode(randomMember);\n    assertTrue(\n      randomMemberStr.equals(\"binary_element1\") || randomMemberStr.equals(\"binary_element2\"));\n\n    // Test with count using binary key\n    List<byte[]> randomMembers = jedis.vrandmember(testKey, 2);\n    assertNotNull(randomMembers);\n    assertTrue(randomMembers.size() <= 2);\n\n    // Verify all returned members are valid\n    for (byte[] member : randomMembers) {\n      assertNotNull(member);\n      String memberStr = SafeEncoder.encode(member);\n      assertTrue(memberStr.equals(\"binary_element1\") || memberStr.equals(\"binary_element2\"));\n    }\n  }\n\n  /**\n   * Test VRANDMEMBER with empty vector set. Verifies that VRANDMEMBER handles empty sets correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberEmptySet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    // Try to get random member from empty/non-existent set\n    String randomMember = jedis.vrandmember(testKey);\n    // Should return null for empty set\n    assertNull(randomMember);\n\n    // Test with count on empty set\n    List<String> randomMembers = jedis.vrandmember(testKey, 5);\n    // Should return empty list for empty set\n    assertTrue(randomMembers.isEmpty());\n  }\n\n  /**\n   * Test VRANDMEMBER with single element. Verifies that VRANDMEMBER works correctly with only one\n   * element.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberSingleElement(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:single\";\n\n    // Add only one vector\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"single_element\");\n\n    // Get random member (should always be the single element)\n    String randomMember = jedis.vrandmember(testKey);\n    assertNotNull(randomMember);\n    assertEquals(\"single_element\", randomMember);\n\n    // Test with count\n    List<String> randomMembers = jedis.vrandmember(testKey, 3);\n    assertNotNull(randomMembers);\n    assertEquals(1, randomMembers.size()); // Should only return the single element\n    assertEquals(\"single_element\", randomMembers.get(0));\n  }\n\n  /**\n   * Test VRANDMEMBER with negative count. Verifies that VRANDMEMBER handles negative count (allows\n   * duplicates).\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberNegativeCount(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:negative\";\n\n    // Add some vectors\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n\n    // Get random members with negative count (allows duplicates)\n    List<String> randomMembers = jedis.vrandmember(testKey, -5);\n    assertNotNull(randomMembers);\n    assertEquals(5, randomMembers.size()); // Should return exactly 5 elements (with possible\n                                           // duplicates)\n\n    // All returned members should be valid element IDs\n    for (String member : randomMembers) {\n      assertTrue(member.equals(\"element1\") || member.equals(\"element2\"));\n    }\n  }\n\n  /**\n   * Test VREM command functionality. Verifies that vector set elements can be removed correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrem(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to the set\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Verify initial cardinality\n    assertEquals(3L, jedis.vcard(testKey));\n\n    // Remove one element\n    boolean removed = jedis.vrem(testKey, \"element2\");\n    assertTrue(removed);\n    assertEquals(2L, jedis.vcard(testKey));\n\n    // Try to remove the same element again (should return false)\n    removed = jedis.vrem(testKey, \"element2\");\n    assertFalse(removed);\n    assertEquals(2L, jedis.vcard(testKey));\n\n    // Remove remaining elements\n    removed = jedis.vrem(testKey, \"element1\");\n    assertTrue(removed);\n    assertEquals(1L, jedis.vcard(testKey));\n\n    removed = jedis.vrem(testKey, \"element3\");\n    assertTrue(removed);\n    assertEquals(0L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VREM with binary key and elements. Verifies that VREM works with byte array keys and\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVremBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    // Add vectors using binary key and elements\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"binary_element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"binary_element2\".getBytes());\n    jedis.vadd(testKey, vector3, \"binary_element3\".getBytes());\n\n    // Verify initial cardinality\n    assertEquals(3L, jedis.vcard(testKey));\n\n    // Remove element using binary VREM\n    boolean removed = jedis.vrem(testKey, \"binary_element2\".getBytes());\n    assertTrue(removed);\n    assertEquals(2L, jedis.vcard(testKey));\n\n    // Remove remaining elements using binary VREM\n    boolean removed1 = jedis.vrem(testKey, \"binary_element1\".getBytes());\n    boolean removed3 = jedis.vrem(testKey, \"binary_element3\".getBytes());\n    assertTrue(removed1);\n    assertTrue(removed3);\n    assertEquals(0L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VREM with non-existent elements. Verifies that VREM handles non-existent elements\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVremNonExistent(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:nonexistent\";\n\n    // Add one vector\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"existing_element\");\n\n    // Try to remove non-existent element\n    boolean removed = jedis.vrem(testKey, \"non_existent_element\");\n    assertFalse(removed);\n    assertEquals(1L, jedis.vcard(testKey)); // Cardinality should remain unchanged\n\n    // Try to remove from non-existent vector set\n    String nonExistentKey = testInfo.getDisplayName() + \":non:existent:key\";\n    removed = jedis.vrem(nonExistentKey, \"any_element\");\n    assertFalse(removed);\n  }\n\n  /**\n   * Test VSETATTR with empty attributes (attribute deletion). Verifies that setting empty\n   * attributes removes them.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsetattrDelete(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:DELETE_ATTR\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Add element with attributes\n    String attributes = \"category=test,priority=high\";\n    VAddParams params = new VAddParams().setAttr(attributes);\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify attributes exist\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n\n    // Delete attributes by setting empty string\n    boolean setResult = jedis.vsetattr(testKey, elementId, \"\");\n    assertTrue(setResult);\n\n    // Verify attributes are deleted (should return null or empty)\n    retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNull(retrievedAttrs);\n  }\n\n  /**\n   * Test VINFO command functionality. Verifies that vector set information can be retrieved.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVinfo(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    float[] vector1 = { 1.0f, 2.0f };\n    float[] vector2 = { 3.0f, 4.0f };\n\n    // Add some elements to the vector set\n    VAddParams params = new VAddParams().setAttr(\"{\\\"type\\\": \\\"fruit\\\", \\\"color\\\": \\\"red\\\"}\");\n    boolean result1 = jedis.vadd(testKey, vector1, \"element1\", params);\n    assertTrue(result1);\n\n    boolean result2 = jedis.vadd(testKey, vector2, \"element2\");\n    assertTrue(result2);\n\n    // Get vector set information\n    VectorInfo info = jedis.vinfo(testKey);\n    assertNotNull(info);\n\n    // Verify basic information is present\n    assertNotNull(info.getVectorInfo());\n    assertFalse(info.getVectorInfo().isEmpty());\n    assertEquals(2, info.getDimensionality());\n    assertEquals(\"int8\", info.getType());\n    assertEquals(2L, info.getSize());\n    assertEquals(16L, info.getMaxNodes());\n    assertThat(info.getMaxNodeUid(), greaterThan(0L));\n    assertThat(info.getVSetUid(), greaterThan(0L));\n    assertEquals(0L, info.getProjectionInputDim());\n    assertEquals(1L, info.getAttributesCount());\n    assertNotNull(info.getMaxLevel());\n  }\n\n  /**\n   * Test VINFO with empty vector set. Verifies behavior when vector set doesn't exist.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVinfoNotExistingSet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    VectorInfo info = jedis.vinfo(testKey);\n    assertNull(info);\n  }\n\n  /**\n   * Test VCARD command functionality. Verifies that vector set cardinality can be retrieved\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVcard(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    float[] vector1 = { 1.0f, 2.0f };\n    float[] vector2 = { 3.0f, 4.0f };\n\n    // Initially, cardinality should be 0 for non-existent vector set\n    assertEquals(0L, jedis.vcard(testKey));\n\n    // Add first element\n    boolean result1 = jedis.vadd(testKey, vector1, \"element1\");\n    assertTrue(result1);\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(1L, jedis.vcard(testKey.getBytes()));\n\n    // Add second element\n    boolean result2 = jedis.vadd(testKey, vector2, \"element2\");\n    assertTrue(result2);\n    assertEquals(2L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vcard(testKey.getBytes()));\n\n    // Try to add duplicate element (should not increase cardinality)\n    boolean result3 = jedis.vadd(testKey, vector1, \"element1\");\n    assertFalse(result3); // Should return false for duplicate\n    assertEquals(2L, jedis.vcard(testKey)); // Cardinality should remain 3\n    assertEquals(2L, jedis.vcard(testKey.getBytes()));\n\n    // Remove an element\n    boolean removed = jedis.vrem(testKey, \"element2\");\n    assertTrue(removed);\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(1L, jedis.vcard(testKey.getBytes()));\n\n    // Remove last element\n    removed = jedis.vrem(testKey, \"element1\");\n    assertTrue(removed);\n    assertEquals(0L, jedis.vcard(testKey));\n    assertEquals(0L, jedis.vcard(testKey.getBytes()));\n\n  }\n\n  /**\n   * Test VCARD with non-existent vector set.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVcardNotExistingSet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    // VCARD should return 0 for non-existent vector set\n    assertEquals(0L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VDIM command functionality. Verifies that vector set dimension can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdim(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add 2D vector\n    float[] vector2D = { 1.0f, 2.0f };\n    boolean result = jedis.vadd(testKey, vector2D, \"element1\");\n    assertTrue(result);\n    assertEquals(2L, jedis.vdim(testKey));\n    assertEquals(2L, jedis.vdim(testKey.getBytes()));\n\n    // Test different dimensions\n    String testKey3D = testInfo.getDisplayName() + \":test:vector:set:3d\";\n    float[] vector3D = { 1.0f, 2.0f, 3.0f };\n    jedis.vadd(testKey3D, vector3D, \"element3d\");\n    assertEquals(3L, jedis.vdim(testKey3D));\n    assertEquals(3L, jedis.vdim(testKey3D.getBytes()));\n\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdimNotExistingSet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    JedisDataException thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n\n    thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey.getBytes()));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n  }\n\n  // Test VDIM with empty set\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdimWithEmptySet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n    // Add 2D vector\n    float[] vector2D = { 1.0f, 2.0f };\n    assertTrue(jedis.vadd(testKey, vector2D, \"element1\"));\n    assertTrue(jedis.vrem(testKey, \"element1\"));\n    assertEquals(0L, (jedis.vcard(testKey)));\n\n    JedisDataException thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n\n    thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey.getBytes()));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n  }\n\n  /**\n   * Test VDIM with dimension reduction. Verifies that VDIM returns the reduced dimension when\n   * REDUCE is used.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdimWithDimensionReduction(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:reduced\";\n\n    // Add 4D vector with dimension reduction to 2D\n    float[] vector4D = { 1.0f, 2.0f, 3.0f, 4.0f };\n    VAddParams params = new VAddParams();\n\n    boolean result = jedis.vadd(testKey, vector4D, \"element_reduced\", 2, params);\n    assertTrue(result);\n\n    // VDIM should return the reduced dimension (2), not the original (4)\n    assertEquals(2L, jedis.vdim(testKey));\n    assertEquals(2L, jedis.vdim(testKey.getBytes()));\n  }\n\n  /**\n   * Test VEMB command functionality. Verifies that vector embeddings can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVemb(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add vector to the set\n    float[] originalVector = { 1.0f, 2.0f };\n    VAddParams params = new VAddParams().noQuant();\n    boolean result = jedis.vadd(testKey, originalVector, \"element1\", params);\n    assertTrue(result);\n\n    // Retrieve the vector using VEMB\n    List<Double> retrievedVector = jedis.vemb(testKey, \"element1\");\n    assertNotNull(retrievedVector);\n    assertEquals(2, retrievedVector.size());\n\n    // Verify vector values (with small tolerance for floating point precision)\n    assertEquals(1.0f, retrievedVector.get(0), 0.001);\n    assertEquals(2.0f, retrievedVector.get(1), 0.001);\n  }\n\n  /**\n   * Test VEMB with binary key and element. Verifies that VEMB works with byte array keys and\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_element\".getBytes();\n\n    // Add vector to the set using binary key and element\n    float[] originalVector = { 0.0f, 1.0f };\n    boolean result = jedis.vadd(testKey, originalVector, elementId);\n    assertTrue(result);\n\n    // Retrieve the vector using binary VEMB\n    List<Double> retrievedVector = jedis.vemb(testKey, elementId);\n    assertNotNull(retrievedVector);\n    assertEquals(2, retrievedVector.size());\n\n    // Verify vector values\n    assertEquals(0.0, retrievedVector.get(0), 0.001);\n    assertEquals(1.0, retrievedVector.get(1), 0.001);\n  }\n\n  /**\n   * Test VEMB with RAW option. Verifies that VEMB can return raw vector data when RAW flag is used\n   * with FP32 format.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembRaw(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:raw\";\n\n    // Add vector to the set using FP32 format\n    float[] originalVector = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };\n    byte[] vectorBlob = floatArrayToFP32Bytes(originalVector);\n    VAddParams params = new VAddParams().noQuant();\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, \"raw_element\", params);\n    assertTrue(result);\n\n    // Retrieve the vector using VEMB with RAW option\n    RawVector rawVector = jedis.vembRaw(testKey, \"raw_element\");\n    assertNotNull(rawVector);\n\n    // Verify the raw data length matches the original vector length\n    byte[] rawData = rawVector.getRawData();\n    int expectedLength = originalVector.length * 4; // 4 bytes per float\n    assertEquals(expectedLength, rawData.length);\n\n    // Verify the quantization type is FP32\n    assertEquals(\"f32\", rawVector.getQuantizationType());\n\n    // Verify the norm is present (L2 norm of the vector)\n    assertNotNull(rawVector.getNorm());\n    assertTrue(rawVector.getNorm() > 0);\n\n    // Verify the raw data contains the correct float values by converting back\n    // IEEE 754 32-bit floats are stored in little-endian format\n    List<Float> reconstructedVector = VectorTestUtils.fp32BytesToFloatArray(rawData);\n\n    // Verify the reconstructed vector matches the original\n    assertEquals(originalVector.length, reconstructedVector.size());\n    for (int i = 0; i < originalVector.length; i++) {\n      assertEquals(originalVector[i] / rawVector.getNorm(), reconstructedVector.get(i), 0.001f);\n    }\n  }\n\n  /**\n   * Test VEMB with RAW option. Verifies that VEMB can return raw vector data when RAW flag is used\n   * with FP32 format.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembRawBinary(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:raw\";\n\n    // Add vector to the set using FP32 format\n    float[] originalVector = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };\n    byte[] vectorBlob = floatArrayToFP32Bytes(originalVector);\n    VAddParams params = new VAddParams().noQuant();\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, \"raw_element\", params);\n    assertTrue(result);\n\n    // Retrieve the vector using VEMB with RAW option\n    RawVector rawVector = jedis.vembRaw(testKey.getBytes(), \"raw_element\".getBytes());\n    assertNotNull(rawVector);\n\n    // Verify the raw data length matches the original vector length\n    byte[] rawData = rawVector.getRawData();\n    int expectedLength = originalVector.length * 4; // 4 bytes per float\n    assertEquals(expectedLength, rawData.length);\n\n    // Verify the quantization type is FP32\n    assertEquals(\"f32\", rawVector.getQuantizationType());\n\n    // Verify the norm is present (L2 norm of the vector)\n    assertNotNull(rawVector.getNorm());\n    assertTrue(rawVector.getNorm() > 0);\n\n    // Verify the raw data contains the correct float values by converting back\n    // IEEE 754 32-bit floats are stored in little-endian format\n    List<Float> reconstructedVector = VectorTestUtils.fp32BytesToFloatArray(rawData);\n\n    // Verify the reconstructed vector matches the original\n    assertEquals(originalVector.length, reconstructedVector.size());\n    for (int i = 0; i < originalVector.length; i++) {\n      assertEquals(originalVector[i] / rawVector.getNorm(), reconstructedVector.get(i), 0.001f);\n    }\n  }\n\n  /**\n   * Test VEMB with non-existent element. Verifies that VEMB handles non-existent elements\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembNonExistent(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:nonexistent\";\n\n    // Add a vector first\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"existing_element\");\n\n    // Try to retrieve non-existent element\n    assertNull(jedis.vemb(testKey, \"non_existent_element\"));\n\n  }\n\n  /**\n   * Test VSIM command functionality. Verifies vector similarity search with vectors and elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsim(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    setupVSimTestSet(testKey);\n\n    // Test vsim with vector\n    List<String> similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f });\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(4));\n    assertThat(similar, hasItems(\"element1\", \"element2\", \"element3\", \"element4\"));\n\n    // Test vsim with element\n    similar = jedis.vsimByElement(testKey, \"element1\");\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(4));\n    assertThat(similar, hasItems(\"element1\", \"element2\", \"element3\", \"element4\"));\n\n    // Test vsim with vector and parameters\n    VSimParams params = new VSimParams().count(2);\n    similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n\n    // Test vsim with element and parameters\n    similar = jedis.vsimByElement(testKey, \"element1\", params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n  }\n\n  private void setupVSimTestSet(String testKey) {\n    // Add test vectors\n    float[] vector1 = { 0.1f, 0.2f, 0.3f };\n    float[] vector2 = { 0.2f, 0.3f, 0.4f };\n    float[] vector3 = { 0.3f, 0.4f, 0.5f };\n    float[] vector4 = { -0.1f, -0.2f, -0.3f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n    jedis.vadd(testKey, vector4, \"element4\");\n  }\n\n  /**\n   * Test VSIM command with scores functionality. Verifies vector similarity search returns scores\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsimWithScores(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:scores\";\n\n    setupVSimTestSet(testKey);\n\n    // Test vsim with vector and scores\n    VSimParams params = new VSimParams();\n    Map<String, Double> similarWithScores = jedis.vsimWithScores(testKey,\n      new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertThat(similarWithScores.keySet(), hasItems(\"element1\", \"element2\", \"element3\"));\n    assertThat(similarWithScores.values(), everyItem(greaterThanOrEqualTo(0.0)));\n\n    // Test vsim with element and scores\n    similarWithScores = jedis.vsimByElementWithScores(testKey, \"element1\", params);\n    assertThat(similarWithScores.keySet(), hasItems(\"element1\", \"element2\", \"element3\"));\n    assertThat(similarWithScores.get(\"element1\"), closeTo(1, 0.01));\n    assertThat(similarWithScores.get(\"element4\"), closeTo(0, 0.01));\n    assertEquals(1.0, similarWithScores.get(\"element1\"), 0.001);\n\n    // Test with count parameter\n    params = new VSimParams().count(2);\n    similarWithScores = jedis.vsimWithScores(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertThat(similarWithScores.keySet(), hasItems(\"element1\", \"element2\"));\n    assertThat(similarWithScores.values(), everyItem(greaterThan(0.0)));\n\n    // Test with epsilon parameter (distance-based filtering)\n    params = new VSimParams().epsilon(0.2); // Only elements with similarity >= 0.8\n    similarWithScores = jedis.vsimWithScores(testKey, new float[] { -0.1f, -0.2f, -0.3f }, params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores.keySet(), hasItems(\"element4\"));\n    // Verify all returned scores meet the epsilon threshold\n    for (Double score : similarWithScores.values()) {\n      assertTrue(score >= (1.0 - 0.2)); // score >= 0.8\n    }\n  }\n\n  /**\n   * Test VSIM with scores and attributes.\n   */\n  @Test\n  @SinceRedisVersion(\"8.2.0\")\n  public void testVsimWithScoresAndAttribs(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:scores:attribs\";\n\n    // Add test vectors with attributes\n    VAddParams addParams1 = new VAddParams().setAttr(\"category=test,priority=high\");\n    jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, \"element1\", addParams1);\n\n    VAddParams addParams2 = new VAddParams().setAttr(\"category=prod,priority=low\");\n    jedis.vadd(testKey, new float[] { 0.15f, 0.25f, 0.35f }, \"element2\", addParams2);\n\n    // Add element without attributes\n    jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, \"element3\");\n\n    VSimParams params = new VSimParams();\n    Map<String, VSimScoreAttribs> similarWithScoresAndAttribs = jedis\n        .vsimWithScoresAndAttribs(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScoresAndAttribs);\n    assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap())));\n\n    // Verify scores and attributes are present\n    for (Map.Entry<String, VSimScoreAttribs> entry : similarWithScoresAndAttribs.entrySet()) {\n      String element = entry.getKey();\n      VSimScoreAttribs data = entry.getValue();\n\n      assertNotNull(data);\n      assertNotNull(data.getScore());\n      assertThat(data.getScore(), is(both(greaterThanOrEqualTo(0.0)).and(lessThanOrEqualTo(1.0))));\n\n      // Check attributes based on element\n      if (\"element1\".equals(element)) {\n        assertEquals(\"category=test,priority=high\", data.getAttributes());\n      } else if (\"element2\".equals(element)) {\n        assertEquals(\"category=prod,priority=low\", data.getAttributes());\n      } else if (\"element3\".equals(element)) {\n        assertNull(data.getAttributes()); // No attributes set\n      }\n    }\n  }\n\n  /**\n   * Test VSIM by element with scores and attributes.\n   */\n  @Test\n  @SinceRedisVersion(\"8.2.0\")\n  public void testVsimByElementWithScoresAndAttribs(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:element:scores:attribs\";\n\n    // Add test vectors with attributes\n    VAddParams addParams1 = new VAddParams().setAttr(\"type=reference,quality=high\");\n    jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, \"reference\", addParams1);\n\n    VAddParams addParams2 = new VAddParams().setAttr(\"type=similar,quality=medium\");\n    jedis.vadd(testKey, new float[] { 0.12f, 0.22f, 0.32f }, \"similar1\", addParams2);\n\n    VAddParams addParams3 = new VAddParams().setAttr(\"type=different,quality=low\");\n    jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, \"different\", addParams3);\n\n    VSimParams params = new VSimParams();\n    Map<String, VSimScoreAttribs> similarWithScoresAndAttribs = jedis\n        .vsimByElementWithScoresAndAttribs(testKey, \"reference\", params);\n    assertNotNull(similarWithScoresAndAttribs);\n    assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap())));\n\n    // Reference element should have perfect similarity with itself\n    assertTrue(similarWithScoresAndAttribs.containsKey(\"reference\"));\n    VSimScoreAttribs referenceData = similarWithScoresAndAttribs.get(\"reference\");\n    assertThat(referenceData.getScore(), is(closeTo(1.0, 0.001)));\n    assertEquals(\"type=reference,quality=high\", referenceData.getAttributes());\n  }\n\n  /**\n   * Test VSIM command with binary keys and elements. Verifies vector similarity search works with\n   * byte arrays.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsimBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    setupVSimTestSetBinary(testKey);\n\n    // Test vsim with vector (binary)\n    List<byte[]> similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f });\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(3));\n    assertThat(getBinaryElementNames(similar), hasItems(\"element1\", \"element2\", \"element3\"));\n\n    // Test vsim with element (binary)\n    similar = jedis.vsimByElement(testKey, \"element1\".getBytes());\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(3));\n    assertThat(getBinaryElementNames(similar), hasItems(\"element1\", \"element2\", \"element3\"));\n\n    // Test vsim with vector and parameters (binary)\n    VSimParams params = new VSimParams().count(2);\n    similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n\n    // Test vsim with element and parameters (binary)\n    similar = jedis.vsimByElement(testKey, \"element1\".getBytes(), params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n  }\n\n  /**\n   * Test VSIM command with binary keys and scores. Verifies vector similarity search returns scores\n   * with binary data.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsimBinaryWithScores(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary:scores\").getBytes();\n\n    setupVSimTestSetBinary(testKey);\n\n    // Test vsim with vector and scores (binary)\n    VSimParams params = new VSimParams();\n    Map<byte[], Double> similarWithScores = jedis.vsimWithScores(testKey,\n      new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores, is(not(anEmptyMap())));\n\n    // Verify scores are present and valid\n    for (Map.Entry<byte[], Double> entry : similarWithScores.entrySet()) {\n      assertNotNull(entry.getKey());\n      assertNotNull(entry.getValue());\n      assertThat(entry.getValue(), is(greaterThan(0.0)));\n    }\n\n    // Test vsim with element and scores (binary)\n    similarWithScores = jedis.vsimByElementWithScores(testKey, \"element1\".getBytes(), params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores, is(not(anEmptyMap())));\n\n    // Element1 should have perfect similarity with itself\n    Double element1Score = getBinaryScoreForElement(similarWithScores, \"element1\");\n    assertNotNull(element1Score);\n    assertThat(element1Score, is(closeTo(1.0, 0.001)));\n\n    // Test with count parameter (binary)\n    params = new VSimParams().count(2);\n    similarWithScores = jedis.vsimWithScores(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores.size(), is(2));\n  }\n\n  /**\n   * Helper method to set up test vector set for binary VSIM tests.\n   */\n  private void setupVSimTestSetBinary(byte[] testKey) {\n    // Add test vectors - same as non-binary version\n    float[] vector1 = { 0.1f, 0.2f, 0.3f };\n    float[] vector2 = { 0.15f, 0.25f, 0.35f };\n    float[] vector3 = { 0.9f, 0.8f, 0.7f };\n\n    jedis.vadd(testKey, vector1, \"element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"element2\".getBytes());\n    jedis.vadd(testKey, vector3, \"element3\".getBytes());\n  }\n\n  /**\n   * Helper method to convert binary element list to string names for assertions.\n   */\n  private List<String> getBinaryElementNames(List<byte[]> binaryElements) {\n    return binaryElements.stream().map(String::new).collect(java.util.stream.Collectors.toList());\n  }\n\n  /**\n   * Helper method to get score for a specific element from binary score map.\n   */\n  private Double getBinaryScoreForElement(Map<byte[], Double> scoreMap, String elementName) {\n    byte[] elementBytes = elementName.getBytes();\n    for (Map.Entry<byte[], Double> entry : scoreMap.entrySet()) {\n      if (java.util.Arrays.equals(entry.getKey(), elementBytes)) {\n        return entry.getValue();\n      }\n    }\n    return null;\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/AllKindOfValuesCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static redis.clients.jedis.Protocol.Command.BLPOP;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.HGETALL;\nimport static redis.clients.jedis.Protocol.Command.LRANGE;\nimport static redis.clients.jedis.Protocol.Command.PING;\nimport static redis.clients.jedis.Protocol.Command.RPUSH;\nimport static redis.clients.jedis.Protocol.Command.SET;\nimport static redis.clients.jedis.Protocol.Command.XINFO;\nimport static redis.clients.jedis.util.SafeEncoder.encode;\nimport static redis.clients.jedis.util.SafeEncoder.encodeObject;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.hamcrest.Matchers;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.util.*;\n\n@Tag(\"integration\")\npublic abstract class AllKindOfValuesCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  protected final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  protected final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x0A };\n  protected final byte[] bfoo2 = { 0x01, 0x02, 0x03, 0x04, 0x0B };\n  protected final byte[] bfoo3 = { 0x01, 0x02, 0x03, 0x04, 0x0C };\n  protected final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  protected final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  protected final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  protected final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n\n  protected final byte[] bfoobar = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };\n  protected final byte[] bfoostar = { 0x01, 0x02, 0x03, 0x04, '*' };\n  protected final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  protected final byte[] bnx = { 0x6E, 0x78 };\n  protected final byte[] bex = { 0x65, 0x78 };\n  final int expireSeconds = 2;\n\n  public AllKindOfValuesCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void exists() {\n    String status = jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.set(bfoo, bbar);\n    assertEquals(\"OK\", status);\n\n    assertTrue(jedis.exists(\"foo\"));\n\n    assertTrue(jedis.exists(bfoo));\n\n    assertEquals(1L, jedis.del(\"foo\"));\n\n    assertEquals(1L, jedis.del(bfoo));\n\n    assertFalse(jedis.exists(\"foo\"));\n\n    assertFalse(jedis.exists(bfoo));\n  }\n\n  @Test\n  public void existsMany() {\n    String status = jedis.set(\"foo1\", \"bar1\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.set(\"foo2\", \"bar2\");\n    assertEquals(\"OK\", status);\n\n    assertEquals(2L, jedis.exists(\"foo1\", \"foo2\"));\n\n    assertEquals(1L, jedis.del(\"foo1\"));\n\n    assertEquals(1L, jedis.exists(\"foo1\", \"foo2\"));\n  }\n\n  @Test\n  public void del() {\n    jedis.set(\"foo1\", \"bar1\");\n    jedis.set(\"foo2\", \"bar2\");\n    jedis.set(\"foo3\", \"bar3\");\n\n    assertEquals(3L, jedis.del(\"foo1\", \"foo2\", \"foo3\"));\n\n    assertFalse(jedis.exists(\"foo1\"));\n    assertFalse(jedis.exists(\"foo2\"));\n    assertFalse(jedis.exists(\"foo3\"));\n\n    jedis.set(\"foo1\", \"bar1\");\n\n    assertEquals(1L, jedis.del(\"foo1\", \"foo2\"));\n\n    assertEquals(0L, jedis.del(\"foo1\", \"foo2\"));\n\n    // Binary ...\n    jedis.set(bfoo1, bbar1);\n    jedis.set(bfoo2, bbar2);\n    jedis.set(bfoo3, bbar3);\n\n    assertEquals(3L, jedis.del(bfoo1, bfoo2, bfoo3));\n\n    assertFalse(jedis.exists(bfoo1));\n    assertFalse(jedis.exists(bfoo2));\n    assertFalse(jedis.exists(bfoo3));\n\n    jedis.set(bfoo1, bbar1);\n\n    assertEquals(1, jedis.del(bfoo1, bfoo2));\n\n    assertEquals(0, jedis.del(bfoo1, bfoo2));\n  }\n\n  @Test\n  public void unlink() {\n    jedis.set(\"foo1\", \"bar1\");\n    jedis.set(\"foo2\", \"bar2\");\n    jedis.set(\"foo3\", \"bar3\");\n\n    assertEquals(3, jedis.unlink(\"foo1\", \"foo2\", \"foo3\"));\n\n    assertEquals(0, jedis.exists(\"foo1\", \"foo2\", \"foo3\"));\n\n    jedis.set(\"foo1\", \"bar1\");\n\n    assertEquals(1, jedis.unlink(\"foo1\", \"foo2\"));\n\n    assertEquals(0, jedis.unlink(\"foo1\", \"foo2\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.unlink(\"foo\"));\n    assertFalse(jedis.exists(\"foo\"));\n\n    // Binary\n    jedis.set(bfoo1, bbar1);\n    jedis.set(bfoo2, bbar2);\n    jedis.set(bfoo3, bbar3);\n\n    assertEquals(3, jedis.unlink(bfoo1, bfoo2, bfoo3));\n\n    assertEquals(0, jedis.exists(bfoo1, bfoo2, bfoo3));\n\n    jedis.set(bfoo1, bbar1);\n\n    assertEquals(1, jedis.unlink(bfoo1, bfoo2));\n\n    assertEquals(0, jedis.unlink(bfoo1, bfoo2));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.unlink(bfoo));\n    assertFalse(jedis.exists(bfoo));\n  }\n\n  @Test\n  public void type() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(\"string\", jedis.type(\"foo\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(\"string\", jedis.type(bfoo));\n  }\n\n  @Test\n  public void keys() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.set(\"foobar\", \"bar\");\n\n    Set<String> keys = jedis.keys(\"foo*\");\n    AssertUtil.assertCollectionContains(keys, \"foo\");\n    AssertUtil.assertCollectionContains(keys, \"foobar\");\n\n    assertEquals(Collections.emptySet(), jedis.keys(\"bar*\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    jedis.set(bfoobar, bbar);\n\n    Set<byte[]> bkeys = jedis.keys(bfoostar);\n    AssertUtil.assertByteArrayCollectionContains(bkeys, bfoo);\n    AssertUtil.assertByteArrayCollectionContains(bkeys, bfoobar);\n\n    assertEquals(Collections.emptySet(), jedis.keys(bbarstar));\n  }\n\n  @Test\n  public void randomKey() {\n    assertNull(jedis.randomKey());\n\n    for (int i = 0; i < 100; i++) {\n      jedis.set(\"foo\" + i, \"bar\"+i);\n    }\n\n    String key = jedis.randomKey();\n    assertNotNull(key);\n    assertTrue(key.startsWith(\"foo\"));\n    assertEquals(key.replace(\"foo\", \"bar\"), jedis.get(key));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void rename() {\n    jedis.set(\"foo\", \"bar\");\n    String status = jedis.rename(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    assertNull(jedis.get(\"foo\"));\n\n    assertEquals(\"bar\", jedis.get(\"bar\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    String bstatus = jedis.rename(bfoo, bbar);\n    assertEquals(\"OK\", bstatus);\n\n    assertNull(jedis.get(bfoo));\n\n    assertArrayEquals(bbar, jedis.get(bbar));\n  }\n\n  @Test\n  public void renameOldAndNewAreTheSame() {\n    assertEquals(\"OK\", jedis.set(\"foo\", \"bar\"));\n    assertEquals(\"OK\", jedis.rename(\"foo\", \"foo\"));\n\n    // Binary\n    assertEquals(\"OK\", jedis.set(bfoo, bbar));\n    assertEquals(\"OK\", jedis.rename(bfoo, bfoo));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void renamenx() {\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.renamenx(\"foo\", \"bar\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(0, jedis.renamenx(\"foo\", \"bar\"));\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.renamenx(bfoo, bbar));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(0, jedis.renamenx(bfoo, bbar));\n  }\n\n  @Test\n  public void dbSize() {\n    assertEquals(0, jedis.dbSize());\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.dbSize());\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(2, jedis.dbSize());\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void expire() {\n    assertEquals(0, jedis.expire(\"foo\", 20L));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.expire(\"foo\", 20L));\n    assertEquals(0, jedis.expire(\"foo\", 20L, ExpiryOption.NX));\n\n    // Binary\n    assertEquals(0, jedis.expire(bfoo, 20L));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.expire(bfoo, 20L));\n    assertEquals(0, jedis.expire(bfoo, 20L, ExpiryOption.NX));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void expireAt() {\n    long unixTime = (System.currentTimeMillis() / 1000L) + 20;\n\n    assertEquals(0, jedis.expireAt(\"foo\", unixTime));\n\n    jedis.set(\"foo\", \"bar\");\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    assertEquals(1, jedis.expireAt(\"foo\", unixTime));\n    assertEquals(1, jedis.expireAt(\"foo\", unixTime, ExpiryOption.XX));\n\n    // Binary\n    assertEquals(0, jedis.expireAt(bfoo, unixTime));\n\n    jedis.set(bfoo, bbar);\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    assertEquals(1, jedis.expireAt(bfoo, unixTime));\n    assertEquals(1, jedis.expireAt(bfoo, unixTime, ExpiryOption.XX));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void expireTime() {\n    long unixTime;\n\n    jedis.set(\"foo\", \"bar\");\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    jedis.expireAt(\"foo\", unixTime);\n    assertEquals(unixTime, jedis.expireTime(\"foo\"), 0.0001);\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    unixTime = (System.currentTimeMillis() / 1000L) + 20;\n    jedis.expireAt(bfoo, unixTime);\n    assertEquals(unixTime, jedis.expireTime(bfoo), 0.0001);\n  }\n\n  @Test\n  public void ttl() {\n    assertEquals(-2, jedis.ttl(\"foo\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(-1, jedis.ttl(\"foo\"));\n\n    jedis.expire(\"foo\", 20);\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl >= 0 && ttl <= 20);\n\n    // Binary\n    assertEquals(-2, jedis.ttl(bfoo));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(-1, jedis.ttl(bfoo));\n\n    jedis.expire(bfoo, 20);\n    long bttl = jedis.ttl(bfoo);\n    assertTrue(bttl >= 0 && bttl <= 20);\n  }\n\n  @Test\n  public void touch() throws Exception {\n    assertEquals(0, jedis.touch(\"foo1\", \"foo2\", \"foo3\"));\n\n    jedis.set(\"foo1\", \"bar1\");\n\n    Thread.sleep(1100); // little over 1 sec\n    assertTrue(jedis.objectIdletime(\"foo1\") > 0);\n\n    assertEquals(1, jedis.touch(\"foo1\"));\n    assertEquals(0L, jedis.objectIdletime(\"foo1\").longValue());\n\n    assertEquals(1, jedis.touch(\"foo1\", \"foo2\", \"foo3\"));\n\n    jedis.set(\"foo2\", \"bar2\");\n\n    jedis.set(\"foo3\", \"bar3\");\n\n    assertEquals(3, jedis.touch(\"foo1\", \"foo2\", \"foo3\"));\n\n    // Binary\n    assertEquals(0, jedis.touch(bfoo1, bfoo2, bfoo3));\n\n    jedis.set(bfoo1, bbar1);\n\n    Thread.sleep(1100); // little over 1 sec\n    assertTrue(jedis.objectIdletime(bfoo1) > 0);\n\n    assertEquals(1, jedis.touch(bfoo1));\n    assertEquals(0L, jedis.objectIdletime(bfoo1).longValue());\n\n    assertEquals(1, jedis.touch(bfoo1, bfoo2, bfoo3));\n\n    jedis.set(bfoo2, bbar2);\n\n    jedis.set(bfoo3, bbar3);\n\n    assertEquals(3, jedis.touch(bfoo1, bfoo2, bfoo3));\n  }\n\n  @Test\n  public void persist() {\n    jedis.setex(\"foo\", 60 * 60, \"bar\");\n    assertTrue(jedis.ttl(\"foo\") > 0);\n    assertEquals(1, jedis.persist(\"foo\"));\n    assertEquals(-1, jedis.ttl(\"foo\"));\n\n    // Binary\n    jedis.setex(bfoo, 60 * 60, bbar);\n    assertTrue(jedis.ttl(bfoo) > 0);\n    assertEquals(1, jedis.persist(bfoo));\n    assertEquals(-1, jedis.ttl(bfoo));\n  }\n\n  @Test\n  public void dumpAndRestore() {\n    jedis.set(\"foo1\", \"bar\");\n    byte[] sv = jedis.dump(\"foo1\");\n    jedis.restore(\"foo2\", 0, sv);\n    assertEquals(\"bar\", jedis.get(\"foo2\"));\n\n    jedis.set(bfoo1, bbar);\n    sv = jedis.dump(bfoo1);\n    jedis.restore(bfoo2, 0, sv);\n    assertArrayEquals(bbar, jedis.get(bfoo2));\n  }\n\n  @Test\n  @Disabled(value = \"TODO: Regression in 8.0-M02 discarding restore idle time.\")\n  public void restoreParams() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.set(\"from\", \"a\");\n    byte[] serialized = jedis.dump(\"from\");\n\n    try {\n      jedis.restore(\"foo\", 0, serialized);\n      fail(\"Simple restore on a existing key should fail\");\n    } catch (JedisDataException e) {\n      // should be here\n    }\n    try {\n      jedis.restore(\"foo\", 0, serialized, RestoreParams.restoreParams());\n      fail(\"Simple restore on a existing key should fail\");\n    } catch (JedisDataException e) {\n      // should be here\n    }\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n\n    jedis.restore(\"foo\", 1000, serialized, RestoreParams.restoreParams().replace());\n    assertEquals(\"a\", jedis.get(\"foo\"));\n    assertTrue(jedis.pttl(\"foo\") <= 1000);\n\n    jedis.restore(\"bar\", System.currentTimeMillis() + 1000, serialized, RestoreParams.restoreParams().replace().absTtl());\n    assertTrue(jedis.pttl(\"bar\") <= 1000);\n\n    jedis.restore(\"bar1\", 1000, serialized, RestoreParams.restoreParams().replace().idleTime(1000));\n    assertEquals(1000, jedis.objectIdletime(\"bar1\").longValue());\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\", message = \"Starting with Redis version 7.0.0: Added options: NX, XX, GT and LT.\")\n  public void pexpire() {\n    assertEquals(0, jedis.pexpire(\"foo\", 10000));\n\n    jedis.set(\"foo1\", \"bar1\");\n    assertEquals(1, jedis.pexpire(\"foo1\", 10000));\n\n    jedis.set(\"foo2\", \"bar2\");\n    assertEquals(1, jedis.pexpire(\"foo2\", 200000000000L));\n    assertEquals(0, jedis.pexpire(\"foo2\", 10000000, ExpiryOption.NX));\n    assertEquals(1, jedis.pexpire(\"foo2\", 10000000, ExpiryOption.XX));\n    assertEquals(0, jedis.pexpire(\"foo2\", 10000, ExpiryOption.GT));\n    assertEquals(1, jedis.pexpire(\"foo2\", 10000, ExpiryOption.LT));\n\n    long pttl = jedis.pttl(\"foo2\");\n    assertTrue(pttl > 100L);\n\n    // Binary\n    assertEquals(0, jedis.pexpire(bfoo, 10000));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.pexpire(bfoo, 10000));\n    assertEquals(0, jedis.pexpire(bfoo, 10000, ExpiryOption.NX));\n  }\n\n  @Test\n  public void pexpireAt() {\n    long unixTime = (System.currentTimeMillis()) + 10000;\n\n    assertEquals(0, jedis.pexpireAt(\"foo\", unixTime));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.pexpireAt(\"foo\", unixTime));\n\n    // Binary\n    assertEquals(0, jedis.pexpireAt(bfoo, unixTime));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(1, jedis.pexpireAt(bfoo, unixTime));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void pexpireTime() {\n    long unixTime = (System.currentTimeMillis()) + 10000;\n\n    jedis.set(\"foo\", \"bar\");\n    jedis.pexpireAt(\"foo\", unixTime);\n    assertEquals(unixTime, jedis.pexpireTime(\"foo\"), 0.0001);\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    jedis.pexpireAt(bfoo, unixTime);\n    assertEquals(unixTime, jedis.pexpireTime(bfoo), 0.0001);\n  }\n\n  @Test\n  public void pttl() {\n    assertEquals(-2, jedis.pttl(\"foo\"));\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(-1, jedis.pttl(\"foo\"));\n\n    jedis.pexpire(\"foo\", 20000);\n    long pttl = jedis.pttl(\"foo\");\n    assertTrue(pttl >= 0 && pttl <= 20000);\n\n    // Binary\n    assertEquals(-2, jedis.pttl(bfoo));\n\n    jedis.set(bfoo, bbar);\n    assertEquals(-1, jedis.pttl(bfoo));\n\n    jedis.pexpire(bfoo, 20000);\n    pttl = jedis.pttl(bfoo);\n    assertTrue(pttl >= 0 && pttl <= 20000);\n  }\n\n  @Test\n  public void psetex() {\n    long pttl;\n\n    jedis.psetex(\"foo\", 200000000000L, \"bar\");\n    pttl = jedis.pttl(\"foo\");\n    assertTrue(pttl > 100000000000L);\n\n    // Binary\n    jedis.psetex(bfoo, 200000000000L, bbar);\n    pttl = jedis.pttl(bfoo);\n    assertTrue(pttl > 100000000000L);\n  }\n\n  @Test\n  public void scan() {\n    jedis.set(\"b\", \"b\");\n    jedis.set(\"a\", \"a\");\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    ScanResult<byte[]> bResult = jedis.scan(SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void scanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.set(\"b\", \"b\");\n    jedis.set(\"a\", \"a\");\n    jedis.set(\"aa\", \"aa\");\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.match(bfoostar);\n\n    jedis.set(bfoo1, bbar);\n    jedis.set(bfoo2, bbar);\n    jedis.set(bfoo3, bbar);\n\n    ScanResult<byte[]> bResult = jedis.scan(SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void scanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.set(\"a\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.set(bfoo1, bbar);\n    jedis.set(bfoo2, bbar);\n    jedis.set(bfoo3, bbar);\n\n    ScanResult<byte[]> bResult = jedis.scan(SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void scanType() {\n    ScanParams noParams = new ScanParams();\n    ScanParams pagingParams = new ScanParams().count(4);\n\n    jedis.set(\"a\", \"a\");\n    jedis.hset(\"b\", \"b\", \"b\");\n    jedis.set(\"c\", \"c\");\n    jedis.sadd(\"d\", \"d\");\n    jedis.set(\"e\", \"e\");\n    jedis.zadd(\"f\", 0d, \"f\");\n    jedis.set(\"g\", \"g\");\n\n    // string\n    ScanResult<String> scanResult;\n\n    scanResult = jedis.scan(SCAN_POINTER_START, pagingParams, \"string\");\n    assertFalse(scanResult.isCompleteIteration());\n    int page1Count = scanResult.getResult().size();\n    scanResult = jedis.scan(scanResult.getCursor(), pagingParams, \"string\");\n    assertTrue(scanResult.isCompleteIteration());\n    int page2Count = scanResult.getResult().size();\n    assertEquals(4, page1Count + page2Count);\n\n\n    scanResult = jedis.scan(SCAN_POINTER_START, noParams, \"hash\");\n    assertEquals(Collections.singletonList(\"b\"), scanResult.getResult());\n    scanResult = jedis.scan(SCAN_POINTER_START, noParams, \"set\");\n    assertEquals(Collections.singletonList(\"d\"), scanResult.getResult());\n    scanResult = jedis.scan(SCAN_POINTER_START, noParams, \"zset\");\n    assertEquals(Collections.singletonList(\"f\"), scanResult.getResult());\n\n    // binary\n    final byte[] string = \"string\".getBytes();\n    final byte[] hash = \"hash\".getBytes();\n    final byte[] set = \"set\".getBytes();\n    final byte[] zset = \"zset\".getBytes();\n\n    ScanResult<byte[]> binaryResult;\n\n    jedis.set(\"a\", \"a\");\n    jedis.hset(\"b\", \"b\", \"b\");\n    jedis.set(\"c\", \"c\");\n    jedis.sadd(\"d\", \"d\");\n    jedis.set(\"e\", \"e\");\n    jedis.zadd(\"f\", 0d, \"f\");\n    jedis.set(\"g\", \"g\");\n\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, pagingParams, string);\n    assertFalse(binaryResult.isCompleteIteration());\n    page1Count = binaryResult.getResult().size();\n    binaryResult = jedis.scan(binaryResult.getCursorAsBytes(), pagingParams, string);\n    assertTrue(binaryResult.isCompleteIteration());\n    page2Count = binaryResult.getResult().size();\n    assertEquals(4, page1Count + page2Count);\n\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, noParams, hash);\n    AssertUtil.assertByteArrayListEquals(Collections.singletonList(new byte[]{98}), binaryResult.getResult());\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, noParams, set);\n    AssertUtil.assertByteArrayListEquals(Collections.singletonList(new byte[]{100}), binaryResult.getResult());\n    binaryResult = jedis.scan(SCAN_POINTER_START_BINARY, noParams, zset);\n    AssertUtil.assertByteArrayListEquals(Collections.singletonList(new byte[]{102}), binaryResult.getResult());\n  }\n\n  @Test\n  public void scanIsCompleteIteration() {\n    for (int i = 0; i < 100; i++) {\n      jedis.set(\"a\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START);\n    // note: in theory Redis would be allowed to already return all results on the 1st scan,\n    // but in practice this never happens for data sets greater than a few tens\n    // see: https://redis.io/commands/scan#number-of-elements-returned-at-every-scan-call\n    assertFalse(result.isCompleteIteration());\n\n    result = scanCompletely(result.getCursor());\n\n    assertNotNull(result);\n    assertTrue(result.isCompleteIteration());\n  }\n\n  private ScanResult<String> scanCompletely(String cursor) {\n    ScanResult<String> scanResult;\n    do {\n      scanResult = jedis.scan(cursor);\n      cursor = scanResult.getCursor();\n    } while (!SCAN_POINTER_START.equals(scanResult.getCursor()));\n\n    return scanResult;\n  }\n\n  @Test\n  public void setNxExAndGet() {\n    assertEquals(\"OK\", jedis.set(\"hello\", \"world\", SetParams.setParams().nx().ex(expireSeconds)));\n    assertEquals(\"world\", jedis.get(\"hello\"));\n\n    assertNull(jedis.set(\"hello\", \"bar\", SetParams.setParams().nx().ex(expireSeconds)));\n    assertEquals(\"world\", jedis.get(\"hello\"));\n\n    long ttl = jedis.ttl(\"hello\");\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n\n    // binary\n    byte[] bworld = { 0x77, 0x6F, 0x72, 0x6C, 0x64 };\n    byte[] bhello = { 0x68, 0x65, 0x6C, 0x6C, 0x6F };\n\n    assertEquals(\"OK\", jedis.set(bworld, bhello, SetParams.setParams().nx().ex(expireSeconds)));\n    assertArrayEquals(bhello, jedis.get(bworld));\n\n    assertNull(jedis.set(bworld, bbar, SetParams.setParams().nx().ex(expireSeconds)));\n    assertArrayEquals(bhello, jedis.get(bworld));\n\n    long bttl = jedis.ttl(bworld);\n    assertTrue(bttl > 0 && bttl <= expireSeconds);\n  }\n\n  @Test\n  public void setGetOptionTest() {\n    assertEquals(\"OK\", jedis.set(\"hello\", \"world\"));\n\n    // GET old value\n    assertEquals(\"world\", jedis.setGet(\"hello\", \"jedis\"));\n\n    assertEquals(\"jedis\", jedis.get(\"hello\"));\n\n    // GET null value\n    assertNull(jedis.setGet(\"key\", \"value\"));\n  }\n\n  @Test\n  public void setGet() {\n    assertEquals(\"OK\", jedis.set(\"hello\", \"world\"));\n\n    // GET old value\n    assertEquals(\"world\", jedis.setGet(\"hello\", \"jedis\", SetParams.setParams()));\n\n    assertEquals(\"jedis\", jedis.get(\"hello\"));\n\n    // GET null value\n    assertNull(jedis.setGet(\"key\", \"value\", SetParams.setParams()));\n  }\n\n  /**\n   * Tests the executeCommand method with CommandArguments for proper cluster routing.\n   * This test uses explicit key marking through CommandArguments.key() for cluster compatibility.\n   */\n  @Test\n  public void executeCommandTest() {\n    // Test SET command with proper key marking\n    Object obj = jedis.executeCommand(new CommandArguments(SET).key(\"x\").add(\"1\"));\n    String returnValue = encode((byte[]) obj);\n    assertEquals(\"OK\", returnValue);\n\n    // Test GET command with proper key marking\n    obj = jedis.executeCommand(new CommandArguments(GET).key(\"x\"));\n    returnValue = encode((byte[]) obj);\n    assertEquals(\"1\", returnValue);\n\n    // Test RPUSH commands with proper key marking\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\").add(\"a\"));\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\").add(\"b\"));\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\").add(\"c\"));\n\n    // Test LRANGE command with proper key marking\n    obj = jedis.executeCommand(new CommandArguments(LRANGE).key(\"foo\").add(\"0\").add(\"2\"));\n    List<byte[]> list = (List<byte[]>) obj;\n    List<byte[]> expected = new ArrayList<>(3);\n    expected.add(\"a\".getBytes());\n    expected.add(\"b\".getBytes());\n    expected.add(\"c\".getBytes());\n    for (int i = 0; i < 3; i++)\n      assertArrayEquals(expected.get(i), list.get(i));\n\n    // Test PING command (keyless command)\n    assertEquals(\"PONG\", encode((byte[]) jedis.executeCommand(new CommandArguments(PING))));\n  }\n\n  /**\n   * Tests the executeCommand method with blocking CommandArguments for proper cluster routing.\n   * This test uses explicit key marking through CommandArguments.key() and .blocking() for\n   * cluster compatibility with blocking operations.\n   */\n  @Test\n  public void executeBlockingCommandTest() {\n    // Test BLPOP on empty list - should return null after timeout\n    assertNull(jedis.executeCommand(\n        new CommandArguments(BLPOP).key(\"foo\").add(Long.toString(1L)).blocking()));\n\n    // Setup: push an element to the list using executeCommand with proper key marking\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\").add(\"bar\"));\n\n    // Test BLPOP with data - should return the key and value\n    assertEquals(Arrays.asList(\"foo\", \"bar\"),\n        encodeObject(jedis.executeCommand(\n            new CommandArguments(BLPOP).key(\"foo\").add(Long.toString(1L)).blocking())));\n\n    // Test BLPOP on now-empty list - should return null after timeout\n    assertNull(jedis.executeCommand(\n        new CommandArguments(BLPOP).key(\"foo\").add(Long.toString(1L)).blocking()));\n  }\n\n  @Test\n  public void encodeCompleteResponsePing() {\n    assertEquals(\"PONG\", SafeEncoder.encodeObject(jedis.sendCommand(PING)));\n  }\n\n  @Test\n  public void encodeCompleteResponseHgetall() {\n    assumeFalse(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entries = new HashMap<>();\n    entries.put(\"foo\", \"bar\");\n    entries.put(\"foo2\", \"bar2\");\n    jedis.hset(\"hash:test:encode\", entries);\n\n    List encodeObj = (List) SafeEncoder.encodeObject(jedis.executeCommand(new CommandArguments(HGETALL).key(\"hash:test:encode\")));\n\n    assertEquals(4, encodeObj.size());\n    entries.forEach((k, v) -> {\n      assertThat((Iterable<String>) encodeObj, Matchers.hasItem(k));\n      assertEquals(v, findValueFromMapAsList(encodeObj, k));\n    });\n  }\n\n  @Test\n  public void encodeCompleteResponseHgetallResp3() {\n    assumeTrue(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entries = new HashMap<>();\n    entries.put(\"foo\", \"bar\");\n    entries.put(\"foo2\", \"bar2\");\n    jedis.hset(\"hash:test:encode\", entries);\n\n    List<KeyValue> encodeObj = (List<KeyValue>) SafeEncoder.encodeObject(jedis.executeCommand(new CommandArguments(HGETALL).key(\"hash:test:encode\")));\n\n    assertEquals(2, encodeObj.size());\n    encodeObj.forEach(kv -> {\n      assertThat(entries, Matchers.hasEntry(kv.getKey(), kv.getValue()));\n    });\n  }\n\n  @Test\n  public void encodeCompleteResponseXinfoStream() {\n    assumeFalse(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entry = new HashMap<>();\n    entry.put(\"foo\", \"bar\");\n    StreamEntryID entryID = jedis.xadd(\"mystream\", StreamEntryID.NEW_ENTRY, entry);\n    jedis.xgroupCreate(\"mystream\", \"mygroup\", null, false);\n\n    Object obj = jedis.executeCommand(new CommandArguments(Protocol.Command.XINFO).add(\"STREAM\").key(\"mystream\"));\n\n    List encodeObj = (List) SafeEncoder.encodeObject(obj);\n\n    assertThat(encodeObj.size(), Matchers.greaterThanOrEqualTo(14));\n    assertEquals( 0, encodeObj.size() % 2, \"must have even number of elements\"); // must be even\n\n    assertEquals(1L, findValueFromMapAsList(encodeObj, \"length\"));\n    assertEquals(entryID.toString(), findValueFromMapAsList(encodeObj, \"last-generated-id\"));\n\n    List<String> entryAsList = new ArrayList<>(2);\n    entryAsList.add(\"foo\");\n    entryAsList.add(\"bar\");\n\n    assertEquals(entryAsList, ((List) findValueFromMapAsList(encodeObj, \"first-entry\")).get(1));\n    assertEquals(entryAsList, ((List) findValueFromMapAsList(encodeObj, \"last-entry\")).get(1));\n  }\n\n  @Test\n  public void encodeCompleteResponseXinfoStreamResp3() {\n    assumeTrue(protocol == RedisProtocol.RESP3);\n\n    HashMap<String, String> entry = new HashMap<>();\n    entry.put(\"foo\", \"bar\");\n    StreamEntryID entryID = jedis.xadd(\"mystream\", StreamEntryID.NEW_ENTRY, entry);\n    jedis.xgroupCreate(\"mystream\", \"mygroup\", null, false);\n\n    Object obj = jedis.executeCommand(new CommandArguments(XINFO).add(\"STREAM\").key(\"mystream\"));\n\n    List<KeyValue> encodeObj = (List<KeyValue>) SafeEncoder.encodeObject(obj);\n\n    assertThat(encodeObj.size(), Matchers.greaterThanOrEqualTo(7));\n\n    assertEquals(1L, findValueFromMapAsKeyValueList(encodeObj, \"length\"));\n    assertEquals(entryID.toString(), findValueFromMapAsKeyValueList(encodeObj, \"last-generated-id\"));\n\n    List<String> entryAsList = new ArrayList<>(2);\n    entryAsList.add(\"foo\");\n    entryAsList.add(\"bar\");\n\n    assertEquals(entryAsList, ((List) findValueFromMapAsKeyValueList(encodeObj, \"first-entry\")).get(1));\n    assertEquals(entryAsList, ((List) findValueFromMapAsKeyValueList(encodeObj, \"last-entry\")).get(1));\n  }\n\n  private Object findValueFromMapAsList(List list, Object key) {\n    for (int i = 0; i < list.size(); i += 2) {\n      if (key.equals(list.get(i))) {\n        return list.get(i + 1);\n      }\n    }\n    return null;\n  }\n\n  private Object findValueFromMapAsKeyValueList(List<KeyValue> list, Object key) {\n    for (KeyValue kv : list) {\n      if (key.equals(kv.getKey())) {\n        return kv.getValue();\n      }\n    }\n    return null;\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void copy() {\n    assertFalse(jedis.copy(\"unknown\", \"foo\", false));\n\n    jedis.set(\"foo1\", \"bar\");\n    assertTrue(jedis.copy(\"foo1\", \"foo2\", false));\n    assertEquals(\"bar\", jedis.get(\"foo2\"));\n\n    // replace\n    jedis.set(\"foo1\", \"bar1\");\n    assertTrue(jedis.copy(\"foo1\", \"foo2\", true));\n    assertEquals(\"bar1\", jedis.get(\"foo2\"));\n\n    // Binary\n    assertFalse(jedis.copy(bfoobar, bfoo, false));\n\n    jedis.set(bfoo1, bbar);\n    assertTrue(jedis.copy(bfoo1, bfoo2, false));\n    assertArrayEquals(bbar, jedis.get(bfoo2));\n\n    // replace\n    jedis.set(bfoo1, bbar1);\n    assertTrue(jedis.copy(bfoo1, bfoo2, true));\n    assertArrayEquals(bbar1, jedis.get(bfoo2));\n  }\n\n  @Test\n  public void scanIteration() {\n    Set<String> allIn = new HashSet<>(26 * 26);\n    char[] arr = new char[2];\n    for (int i = 0; i < 26; i++) {\n      arr[0] = (char) ('a' + i);\n      for (int j = 0; j < 26; j++) {\n        arr[1] = (char) ('a' + j);\n        String str = new String(arr);\n        jedis.incr(str);\n        allIn.add(str);\n      }\n    }\n\n    Set<String> allScan = new HashSet<>();\n    ScanIteration scan = jedis.scanIteration(10, \"*\");\n    while (!scan.isIterationCompleted()) {\n      ScanResult<String> batch = scan.nextBatch();\n      allScan.addAll(batch.getResult());\n    }\n    assertEquals(allIn, allScan);\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void set_ex_ifeq_then_delex() {\n    String k = \"k:set-ex-ifeq\";\n    // Initial set with EX\n    assertEquals(\"OK\", jedis.set(k, \"v1\", SetParams.setParams().ex(100)));\n    assertTrue(jedis.ttl(k) > 0);\n\n    // Conditional update with IFEQ + EX\n    assertEquals(\"OK\", jedis.set(k, \"v2\",\n        SetParams.setParams().ex(200).condition(CompareCondition.valueEq(\"v1\"))));\n    assertEquals(\"v2\", jedis.get(k));\n    assertTrue(jedis.ttl(k) > 100);\n\n    // Delete with DELEX using value condition\n    assertEquals(0, jedis.delex(k, CompareCondition.valueEq(\"wrong\")));\n    assertEquals(1, jedis.delex(k, CompareCondition.valueEq(\"v2\")));\n    assertFalse(jedis.exists(k));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void set_exAt_ifne_then_delex() {\n    String k = \"k:set-exat-ifne\";\n    long expiryTimestamp = (System.currentTimeMillis() / 1000) + 300;\n\n    // Initial set\n    jedis.set(k, \"v1\");\n\n    // Conditional update with IFNE + EXAT\n    assertEquals(\"OK\", jedis.set(k, \"v2\",\n        SetParams.setParams().exAt(expiryTimestamp).condition(CompareCondition.valueNe(\"v2\"))));\n    assertEquals(\"v2\", jedis.get(k));\n    assertTrue(jedis.ttl(k) > 200);\n\n    // Delete with DELEX using value condition\n    assertEquals(0, jedis.delex(k, CompareCondition.valueNe(\"v2\")));\n    assertEquals(1, jedis.delex(k, CompareCondition.valueNe(\"wrong\")));\n    assertFalse(jedis.exists(k));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void setGet_px_ifdne_then_delex() {\n    String k = \"k:setget-px-ifdne\";\n    String wrongKey = \"wrong\";\n    // Initial set\n    jedis.set(k, \"A\");\n    jedis.set(wrongKey, \"wrong\");\n    String digestBefore = jedis.digestKey(k);\n    String digestWrong = jedis.digestKey(wrongKey); // digest for different value\n\n    // Conditional setGet with IFDNE + PX (digest not equal)\n    assertEquals(\"A\", jedis.setGet(k, \"B\",\n        SetParams.setParams().px(100000).condition(CompareCondition.digestNe(digestWrong))));\n    assertEquals(\"B\", jedis.get(k));\n    assertTrue(jedis.pttl(k) > 90000);\n\n    // Delete with DELEX using digest condition\n    String digestAfter = jedis.digestKey(k);\n    assertEquals(0, jedis.delex(k, CompareCondition.digestNe(digestAfter)));\n    assertEquals(1, jedis.delex(k, CompareCondition.digestNe(digestBefore)));\n    assertFalse(jedis.exists(k));\n  }\n\n  @Test\n  @EnabledOnCommand(\"DELEX\")\n  public void setGet_pxAt_ifdeq_then_delex() {\n    String k = \"k:setget-pxat-ifdeq\";\n    long expiryTimestampMs = System.currentTimeMillis() + 300000;\n\n    // Initial set\n    jedis.set(k, \"X\");\n    String digestX = jedis.digestKey(k);\n\n    // Conditional setGet with IFDEQ + PXAT (digest equal)\n    assertEquals(\"X\", jedis.setGet(k, \"Y\", SetParams.setParams().pxAt(expiryTimestampMs)\n        .condition(CompareCondition.digestEq(digestX))));\n    assertEquals(\"Y\", jedis.get(k));\n    assertTrue(jedis.pttl(k) > 200000);\n\n    // Delete with DELEX using digest condition\n    String digestY = jedis.digestKey(k);\n    assertEquals(0, jedis.delex(k, CompareCondition.digestEq(digestX)));\n    assertEquals(1, jedis.delex(k, CompareCondition.digestEq(digestY)));\n    assertFalse(jedis.exists(k));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/BinaryValuesCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.Protocol.Command.BLPOP;\nimport static redis.clients.jedis.Protocol.Command.GET;\nimport static redis.clients.jedis.Protocol.Command.LRANGE;\nimport static redis.clients.jedis.Protocol.Command.RPUSH;\nimport static redis.clients.jedis.Protocol.Command.SET;\nimport static redis.clients.jedis.params.SetParams.setParams;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\npublic abstract class BinaryValuesCommandsTestBase extends UnifiedJedisCommandsTestBase {\n  protected byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  protected byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  protected byte[] bxx = { 0x78, 0x78 };\n  protected byte[] bnx = { 0x6E, 0x78 };\n  protected byte[] bex = { 0x65, 0x78 };\n  protected byte[] bpx = { 0x70, 0x78 };\n  protected int expireSeconds = 2;\n  protected long expireMillis = expireSeconds * 1000;\n  protected byte[] binaryValue;\n\n  public BinaryValuesCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  public void startUp() {\n    StringBuilder sb = new StringBuilder();\n\n    for (int n = 0; n < 1000; n++) {\n      sb.append(\"A\");\n    }\n\n    binaryValue = sb.toString().getBytes();\n  }\n\n  @Test\n  public void setAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setNxExAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setIfNotExistAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue));\n    // nx should fail if value exists\n    assertNull(jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setIfExistAndGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue));\n    // nx should fail if value exists\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().xx().ex(expireSeconds)));\n\n    byte[] value = jedis.get(bfoo);\n    assertArrayEquals(binaryValue, value);\n\n    assertNull(jedis.get(bbar));\n  }\n\n  @Test\n  public void setFailIfNotExistAndGet() {\n    // xx should fail if value does NOT exists\n    assertNull(jedis.set(bfoo, binaryValue, setParams().xx().ex(expireSeconds)));\n  }\n\n  @Test\n  public void setAndExpireMillis() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().px(expireMillis)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void setAndExpire() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void setAndKeepttl() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().nx().ex(expireSeconds)));\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().keepttl()));\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue, setParams().keepTtl()));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(0 < ttl && ttl <= expireSeconds);\n    jedis.set(bfoo, binaryValue);\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl < 0);\n  }\n\n  @Test\n  public void setAndPxat() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue,\n      setParams().nx().pxAt(System.currentTimeMillis() + expireMillis)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void setAndExat() {\n    assertEquals(\"OK\", jedis.set(bfoo, binaryValue,\n      setParams().nx().exAt(System.currentTimeMillis() / 1000 + expireSeconds)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= expireSeconds);\n  }\n\n  @Test\n  public void getSet() {\n    assertNull(jedis.getSet(bfoo, binaryValue));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n  }\n\n  @Test\n  public void getDel() {\n    assertEquals(\"OK\", jedis.set(bfoo, bbar));\n\n    assertArrayEquals(bbar, jedis.getDel(bfoo));\n\n    assertNull(jedis.get(bfoo));\n  }\n\n  @Test\n  public void getEx() {\n    assertNull(jedis.getEx(bfoo, GetExParams.getExParams().ex(1)));\n    jedis.set(bfoo, bbar);\n\n    assertArrayEquals(bbar, jedis.getEx(bfoo, GetExParams.getExParams().ex(10)));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= 10);\n\n    assertArrayEquals(bbar, jedis.getEx(bfoo, GetExParams.getExParams().px(20000l)));\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 10 && ttl <= 20);\n\n    assertArrayEquals(bbar,\n      jedis.getEx(bfoo, GetExParams.getExParams().exAt(System.currentTimeMillis() / 1000 + 30)));\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 20 && ttl <= 30);\n\n    assertArrayEquals(bbar,\n      jedis.getEx(bfoo, GetExParams.getExParams().pxAt(System.currentTimeMillis() + 40000l)));\n    ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 30 && ttl <= 40);\n\n    assertArrayEquals(bbar, jedis.getEx(bfoo, GetExParams.getExParams().persist()));\n    assertEquals(-1L, jedis.ttl(bfoo));\n  }\n\n  @Test\n  public void mget() {\n    List<byte[]> values = jedis.mget(bfoo, bbar);\n    List<byte[]> expected = new ArrayList<>();\n    expected.add(null);\n    expected.add(null);\n\n    assertByteArrayListEquals(expected, values);\n\n    jedis.set(bfoo, binaryValue);\n\n    expected = new ArrayList<>();\n    expected.add(binaryValue);\n    expected.add(null);\n    assertByteArrayListEquals(expected, jedis.mget(bfoo, bbar));\n\n    jedis.set(bbar, bfoo);\n\n    expected = new ArrayList<>();\n    expected.add(binaryValue);\n    expected.add(bfoo);\n    assertByteArrayListEquals(expected, jedis.mget(bfoo, bbar));\n  }\n\n  @Test\n  public void setnx() {\n    assertEquals(1, jedis.setnx(bfoo, binaryValue));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    assertEquals(0, jedis.setnx(bfoo, bbar));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n  }\n\n  @Test\n  public void setex() {\n    assertEquals(\"OK\", jedis.setex(bfoo, 20, binaryValue));\n    long ttl = jedis.ttl(bfoo);\n    assertTrue(ttl > 0 && ttl <= 20);\n  }\n\n  @Test\n  public void mset() {\n    assertEquals(\"OK\", jedis.mset(bfoo, binaryValue, bbar, bfoo));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n    assertArrayEquals(bfoo, jedis.get(bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void msetnx() {\n    assertEquals(1, jedis.msetnx(bfoo, binaryValue, bbar, bfoo));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n    assertArrayEquals(bfoo, jedis.get(bbar));\n\n    assertEquals(0, jedis.msetnx(bfoo, bbar, \"bar2\".getBytes(), \"foo2\".getBytes()));\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n    assertArrayEquals(bfoo, jedis.get(bbar));\n  }\n\n  @Test\n  public void incr() {\n    assertEquals(1, jedis.incr(bfoo));\n    assertEquals(2, jedis.incr(bfoo));\n  }\n\n  @Test\n  public void incrWrongValue() {\n    jedis.set(bfoo, binaryValue);\n    assertThrows(JedisDataException.class, () -> jedis.incr(bfoo));\n  }\n\n  @Test\n  public void incrBy() {\n    assertEquals(2, jedis.incrBy(bfoo, 2));\n    assertEquals(4, jedis.incrBy(bfoo, 2));\n  }\n\n  @Test\n  public void incrByWrongValue() {\n    jedis.set(bfoo, binaryValue);\n    assertThrows(JedisDataException.class, () -> jedis.incrBy(bfoo, 2));\n  }\n\n  @Test\n  public void incrByFloat() {\n    assertEquals(10.5, jedis.incrByFloat(bfoo, 10.5), 0.0);\n    assertEquals(10.6, jedis.incrByFloat(bfoo, 0.1), 0.0);\n  }\n\n  @Test\n  public void decr() {\n    assertEquals(-1, jedis.decr(bfoo));\n    assertEquals(-2, jedis.decr(bfoo));\n  }\n\n  @Test\n  public void decrWrongValue() {\n    jedis.set(bfoo, binaryValue);\n    assertThrows(JedisDataException.class, () -> jedis.decr(bfoo));\n  }\n\n  @Test\n  public void decrBy() {\n    assertEquals(-2, jedis.decrBy(bfoo, 2));\n    assertEquals(-4, jedis.decrBy(bfoo, 2));\n  }\n\n  @Test\n  public void decrByWrongValue() {\n    jedis.set(bfoo, binaryValue);\n    assertThrows(JedisDataException.class, () -> jedis.decrBy(bfoo, 2));\n  }\n\n  @Test\n  public void append() {\n    byte[] first512 = new byte[512];\n    System.arraycopy(binaryValue, 0, first512, 0, 512);\n    assertEquals(512, jedis.append(bfoo, first512));\n    assertArrayEquals(first512, jedis.get(bfoo));\n\n    byte[] rest = new byte[binaryValue.length - 512];\n    System.arraycopy(binaryValue, 512, rest, 0, binaryValue.length - 512);\n    assertEquals(binaryValue.length, jedis.append(bfoo, rest));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void substr() {\n    jedis.set(bfoo, binaryValue);\n\n    byte[] first512 = new byte[512];\n    System.arraycopy(binaryValue, 0, first512, 0, 512);\n    byte[] rfirst512 = jedis.substr(bfoo, 0, 511);\n    assertArrayEquals(first512, rfirst512);\n\n    byte[] last512 = new byte[512];\n    System.arraycopy(binaryValue, binaryValue.length - 512, last512, 0, 512);\n    assertArrayEquals(last512, jedis.substr(bfoo, -512, -1));\n\n    assertArrayEquals(binaryValue, jedis.substr(bfoo, 0, -1));\n\n    assertArrayEquals(last512, jedis.substr(bfoo, binaryValue.length - 512, 100000));\n  }\n\n  @Test\n  public void strlen() {\n    jedis.set(bfoo, binaryValue);\n    assertEquals(binaryValue.length, jedis.strlen(bfoo));\n  }\n\n  @Test\n  public void setGet() {\n    assertEquals(\"OK\", jedis.set(bfoo, bbar));\n\n    // GET old value\n    assertArrayEquals(bbar, jedis.setGet(bfoo, binaryValue));\n\n    assertArrayEquals(binaryValue, jedis.get(bfoo));\n\n    // GET null value\n    assertNull(jedis.setGet(bbar, bfoo));\n  }\n\n  @Test\n  public void setGetWithParams() {\n    jedis.del(bfoo);\n\n    // no previous, return null\n    assertNull(jedis.setGet(bfoo, bbar, setParams().nx()));\n\n    // key already exists, new value should not be set, previous value should be bbar\n    assertArrayEquals(bbar, jedis.setGet(bfoo, binaryValue, setParams().nx()));\n\n    assertArrayEquals(bbar, jedis.setGet(bfoo, binaryValue, setParams().xx()));\n  }\n\n  /**\n   * Tests the executeCommand method with CommandArguments for proper cluster routing. This test\n   * uses explicit key marking through CommandArguments.key() for cluster compatibility.\n   */\n  @Test\n  public void executeCommandTest() {\n    // Test SET command with proper key marking\n    Object obj = jedis\n        .executeCommand(new CommandArguments(SET).key(\"x\".getBytes()).add(\"1\".getBytes()));\n    String returnValue = SafeEncoder.encode((byte[]) obj);\n    assertEquals(\"OK\", returnValue);\n\n    // Test GET command with proper key marking\n    obj = jedis.executeCommand(new CommandArguments(GET).key(\"x\".getBytes()));\n    returnValue = SafeEncoder.encode((byte[]) obj);\n    assertEquals(\"1\", returnValue);\n\n    // Test RPUSH commands with proper key marking\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\".getBytes()).add(\"a\".getBytes()));\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\".getBytes()).add(\"b\".getBytes()));\n    jedis.executeCommand(new CommandArguments(RPUSH).key(\"foo\".getBytes()).add(\"c\".getBytes()));\n\n    // Test LRANGE command with proper key marking\n    obj = jedis.executeCommand(\n      new CommandArguments(LRANGE).key(\"foo\".getBytes()).add(\"0\".getBytes()).add(\"2\".getBytes()));\n    List<byte[]> list = (List<byte[]>) obj;\n    List<byte[]> expected = new ArrayList<>(3);\n    expected.add(\"a\".getBytes());\n    expected.add(\"b\".getBytes());\n    expected.add(\"c\".getBytes());\n    for (int i = 0; i < 3; i++)\n      assertArrayEquals(expected.get(i), list.get(i));\n  }\n\n  /**\n   * Tests the executeCommand method with blocking CommandArguments for proper cluster routing. This\n   * test uses explicit key marking through CommandArguments.key() and .blocking() for cluster\n   * compatibility with blocking operations.\n   */\n  @Test\n  public void executeBlockingCommandTest() {\n    // Test BLPOP on empty list - should return null after timeout\n    assertNull(jedis.executeCommand(\n      new CommandArguments(BLPOP).key(bfoo).add(Protocol.toByteArray(1L)).blocking()));\n\n    // Setup: push an element to the list using executeCommand with proper key marking\n    jedis.executeCommand(new CommandArguments(RPUSH).key(bfoo).add(bbar));\n\n    // Test BLPOP with data - should return the key and value\n    List<byte[]> blpop = (List<byte[]>) jedis.executeCommand(\n      new CommandArguments(BLPOP).key(bfoo).add(Protocol.toByteArray(1L)).blocking());\n    assertEquals(2, blpop.size());\n    assertArrayEquals(bfoo, blpop.get(0));\n    assertArrayEquals(bbar, blpop.get(1));\n\n    // Test BLPOP on now-empty list - should return null after timeout\n    assertNull(jedis.executeCommand(\n      new CommandArguments(BLPOP).key(bfoo).add(Protocol.toByteArray(1L)).blocking()));\n  }\n\n  // MSETEX NX + expiration matrix (binary)\n  static Stream<Arguments> msetexNxArgsProvider() {\n    return Stream.of(Arguments.of(\"EX\", new MSetExParams().nx().ex(5)),\n      Arguments.of(\"PX\", new MSetExParams().nx().px(5000)),\n      Arguments.of(\"EXAT\", new MSetExParams().nx().exAt(System.currentTimeMillis() / 1000 + 5)),\n      Arguments.of(\"PXAT\", new MSetExParams().nx().pxAt(System.currentTimeMillis() + 5000)),\n      Arguments.of(\"KEEPTTL\", new MSetExParams().nx().keepTtl()));\n  }\n\n  @ParameterizedTest(name = \"MSETEX NX + {0} (binary)\")\n  @MethodSource(\"msetexNxArgsProvider\")\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetexNx_binary_parametrized(String optionLabel, MSetExParams params) {\n    byte[] k1 = \"{t}msetex:unifiedb:k1\".getBytes();\n    byte[] k2 = \"{t}msetex:unifiedb:k2\".getBytes();\n\n    boolean result = jedis.msetex(params, k1, \"v1\".getBytes(), k2, \"v2\".getBytes());\n    assertTrue(result);\n\n    long ttl = jedis.ttl(k1);\n    if (\"KEEPTTL\".equals(optionLabel)) {\n      assertEquals(-1L, ttl);\n    } else {\n      assertTrue(ttl > 0L);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/BitCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.BitPosParams;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\npublic abstract class BitCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  public BitCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void setAndgetbit() {\n    assertFalse(jedis.setbit(\"foo\", 0, true));\n\n    assertTrue(jedis.getbit(\"foo\", 0));\n\n    // Binary\n    assertFalse(jedis.setbit(\"bfoo\".getBytes(), 0, true));\n\n    assertTrue(jedis.getbit(\"bfoo\".getBytes(), 0));\n  }\n\n  @Test\n  public void bitpos() {\n    String foo = \"foo\";\n\n    jedis.set(foo, String.valueOf(0));\n    //  string \"0\" with bits: 0011 0000\n\n    jedis.setbit(foo, 3, true);\n    jedis.setbit(foo, 7, true);\n    jedis.setbit(foo, 13, true);\n    jedis.setbit(foo, 39, true);\n\n    /*\n     * bit:  00110001 / 00000100 / 00000000 / 00000000 / 00000001\n     * byte: 0          1          2          3          4\n     */\n    long offset = jedis.bitpos(foo, true);\n    assertEquals(2, offset);\n    offset = jedis.bitpos(foo, false);\n    assertEquals(0, offset);\n\n    offset = jedis.bitpos(foo, true, new BitPosParams(1));\n    assertEquals(13, offset);\n    offset = jedis.bitpos(foo, false, new BitPosParams(1));\n    assertEquals(8, offset);\n\n    offset = jedis.bitpos(foo, true, new BitPosParams(2, 3));\n    assertEquals(-1, offset);\n    offset = jedis.bitpos(foo, false, new BitPosParams(2, 3));\n    assertEquals(16, offset);\n\n    offset = jedis.bitpos(foo, true, new BitPosParams(3, 4));\n    assertEquals(39, offset);\n  }\n\n  @Test\n  public void bitposBinary() {\n    // binary\n    byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n\n    jedis.set(bfoo, Protocol.toByteArray(0));\n    // bits: 0011 0000\n\n    jedis.setbit(bfoo, 3, true);\n    jedis.setbit(bfoo, 7, true);\n    jedis.setbit(bfoo, 13, true);\n    jedis.setbit(bfoo, 39, true);\n\n    /*\n     * bit:  00110001 / 00000100 / 00000000 / 00000000 / 00000001\n     * byte: 0          1          2          3          4\n     */\n    long offset = jedis.bitpos(bfoo, true);\n    assertEquals(2, offset);\n    offset = jedis.bitpos(bfoo, false);\n    assertEquals(0, offset);\n\n    offset = jedis.bitpos(bfoo, true, new BitPosParams(1));\n    assertEquals(13, offset);\n    offset = jedis.bitpos(bfoo, false, new BitPosParams(1));\n    assertEquals(8, offset);\n\n    offset = jedis.bitpos(bfoo, true, new BitPosParams(2, 3));\n    assertEquals(-1, offset);\n    offset = jedis.bitpos(bfoo, false, new BitPosParams(2, 3));\n    assertEquals(16, offset);\n\n    offset = jedis.bitpos(bfoo, true, new BitPosParams(3, 4));\n    assertEquals(39, offset);\n  }\n\n  @Test\n  public void bitposWithNoMatchingBitExist() {\n    String foo = \"foo\";\n\n    jedis.set(foo, String.valueOf(0));\n    for (int idx = 0; idx < 8; idx++) {\n      jedis.setbit(foo, idx, true);\n    }\n\n    /*\n     * bit:  11111111\n     * byte: 0\n     */\n    long offset = jedis.bitpos(foo, false);\n    // offset should be last index + 1\n    assertEquals(8, offset);\n  }\n\n  @Test\n  public void bitposWithNoMatchingBitExistWithinRange() {\n    String foo = \"foo\";\n\n    jedis.set(foo, String.valueOf(0));\n    for (int idx = 0; idx < 8 * 5; idx++) {\n      jedis.setbit(foo, idx, true);\n    }\n\n    /*\n     * bit:  11111111 / 11111111 / 11111111 / 11111111 / 11111111\n     * byte: 0          1          2          3          4\n     */\n    long offset = jedis.bitpos(foo, false, new BitPosParams(2, 3));\n    // offset should be -1\n    assertEquals(-1, offset);\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\", message=\"Starting with Redis version 7.0.0: Added the BYTE|BIT option.\")\n  public void bitposModifier() {\n    jedis.set(\"mykey\", \"\\\\x00\\\\xff\\\\xf0\");\n    assertEquals(0, jedis.bitpos(\"mykey\", false));\n    assertEquals(1, jedis.bitpos(\"mykey\", true));\n    assertEquals(1, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams()));\n    assertEquals(18, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(2)));\n    assertEquals(18, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(2).end(-1)));\n    assertEquals(18, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(2).end(-1)\n        .modifier(BitCountOption.BYTE)));\n    assertEquals(9, jedis.bitpos(\"mykey\", true, BitPosParams.bitPosParams().start(7).end(15)\n        .modifier(BitCountOption.BIT)));\n  }\n\n  @Test\n  public void setAndgetrange() {\n    jedis.set(\"key1\", \"Hello World\");\n    assertEquals(11, jedis.setrange(\"key1\", 6, \"Jedis\"));\n\n    assertEquals(\"Hello Jedis\", jedis.get(\"key1\"));\n\n    assertEquals(\"Hello\", jedis.getrange(\"key1\", 0, 4));\n    assertEquals(\"Jedis\", jedis.getrange(\"key1\", 6, 11));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\", message=\"Starting with Redis version 7.0.0: Added the BYTE|BIT option.\")\n  public void bitCount() {\n    jedis.setbit(\"foo\", 16, true);\n    jedis.setbit(\"foo\", 24, true);\n    jedis.setbit(\"foo\", 40, true);\n    jedis.setbit(\"foo\", 56, true);\n\n    assertEquals(4, (long) jedis.bitcount(\"foo\"));\n    assertEquals(4, (long) jedis.bitcount(\"foo\".getBytes()));\n\n    assertEquals(3, (long) jedis.bitcount(\"foo\", 2L, 5L));\n    assertEquals(3, (long) jedis.bitcount(\"foo\".getBytes(), 2L, 5L));\n\n    assertEquals(3, (long) jedis.bitcount(\"foo\", 2L, 5L, BitCountOption.BYTE));\n    assertEquals(3, (long) jedis.bitcount(\"foo\".getBytes(), 2L, 5L, BitCountOption.BYTE));\n\n    assertEquals(0, (long) jedis.bitcount(\"foo\", 2L, 5L, BitCountOption.BIT));\n    assertEquals(0, (long) jedis.bitcount(\"foo\".getBytes(), 2L, 5L, BitCountOption.BIT));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOp() {\n    jedis.set(\"key1\", \"\\u0060\");\n    jedis.set(\"key2\", \"\\u0044\");\n\n    jedis.bitop(BitOP.AND, \"resultAnd\", \"key1\", \"key2\");\n    String resultAnd = jedis.get(\"resultAnd\");\n    assertEquals(\"\\u0040\", resultAnd);\n\n    jedis.bitop(BitOP.OR, \"resultOr\", \"key1\", \"key2\");\n    String resultOr = jedis.get(\"resultOr\");\n    assertEquals(\"\\u0064\", resultOr);\n\n    jedis.bitop(BitOP.XOR, \"resultXor\", \"key1\", \"key2\");\n    String resultXor = jedis.get(\"resultXor\");\n    assertEquals(\"\\u0024\", resultXor);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpNot() {\n    jedis.setbit(\"key\", 0, true);\n    jedis.setbit(\"key\", 4, true);\n\n    jedis.bitop(BitOP.NOT, \"resultNot\", \"key\");\n    String resultNot = jedis.get(\"resultNot\");\n    assertEquals(\"\\u0077\", resultNot);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bitOpBinary() {\n    byte[] dest = {0x0};\n    byte[] key1 = {0x1};\n    byte[] key2 = {0x2};\n\n    jedis.set(key1, new byte[]{0x6});\n    jedis.set(key2, new byte[]{0x3});\n\n    jedis.bitop(BitOP.AND, dest, key1, key2);\n    assertArrayEquals(new byte[]{0x2}, jedis.get(dest));\n\n    jedis.bitop(BitOP.OR, dest, key1, key2);\n    assertArrayEquals(new byte[]{0x7}, jedis.get(dest));\n\n    jedis.bitop(BitOP.XOR, dest, key1, key2);\n    assertArrayEquals(new byte[]{0x5}, jedis.get(dest));\n\n    jedis.setbit(key1, 0, true);\n    jedis.bitop(BitOP.NOT, dest, key1);\n    assertArrayEquals(new byte[]{0x79}, jedis.get(dest));\n  }\n\n  @Test\n  public void bitOpNotMultiSourceShouldFail() {\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.NOT, \"dest\", \"src1\", \"src2\"));\n  }\n\n  @Test\n  public void testBitfield() {\n    List<Long> responses = jedis.bitfield(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n  }\n\n  @Test\n  public void testBitfieldReadonly() {\n    List<Long> responses = jedis.bitfield(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n\n    List<Long> responses2 = jedis.bitfieldReadonly(\"mykey\", \"GET\", \"i5\", \"100\");\n    assertEquals(1L, responses2.get(0).longValue());\n\n    try {\n      jedis.bitfieldReadonly(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n      fail(\"Readonly command shouldn't allow INCRBY\");\n    } catch (JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void testBinaryBitfield() {\n    List<Long> responses = jedis.bitfield(SafeEncoder.encode(\"mykey\"),\n      SafeEncoder.encode(\"INCRBY\"), SafeEncoder.encode(\"i5\"), SafeEncoder.encode(\"100\"),\n      SafeEncoder.encode(\"1\"), SafeEncoder.encode(\"GET\"), SafeEncoder.encode(\"u4\"),\n      SafeEncoder.encode(\"0\"));\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n  }\n\n  @Test\n  public void testBinaryBitfieldReadonly() {\n    List<Long> responses = jedis.bitfield(\"mykey\", \"INCRBY\", \"i5\", \"100\", \"1\", \"GET\", \"u4\", \"0\");\n    assertEquals(1L, responses.get(0).longValue());\n    assertEquals(0L, responses.get(1).longValue());\n\n    List<Long> responses2 = jedis.bitfieldReadonly(SafeEncoder.encode(\"mykey\"),\n      SafeEncoder.encode(\"GET\"), SafeEncoder.encode(\"i5\"), SafeEncoder.encode(\"100\"));\n    assertEquals(1L, responses2.get(0).longValue());\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"{bitOp:key:}resultDiff\";\n\n    // Set keys using byte arrays\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // DIFF(key1, key2, key3) = key1 AND NOT(key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key2 OR key3) = 11111001 (all bits except 1,2 set)\n    // key1 AND NOT(key2 OR key3) = 00000001 (only bit 0 set)\n    jedis.bitop(BitOP.DIFF, destKey, \"{bitOp:key:}1\", \"{bitOp:key:}2\", \"{bitOp:key:}3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000001 (only bit 0 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x01 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff1() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"{bitOp:key:}resultDiff1\";\n\n    // Set keys using byte arrays\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // DIFF1(key1, key2, key3) = NOT(key1) AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key1) = 11111000 (all bits except 0,1,2 set)\n    // NOT(key1) AND (key2 OR key3) = 00000000 (no bits set)\n    jedis.bitop(BitOP.DIFF1, destKey, \"{bitOp:key:}1\", \"{bitOp:key:}2\", \"{bitOp:key:}3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000000 (no bits set)\n    byte[] expectedBytes = new byte[] { (byte) 0x00 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpAndor() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"{bitOp:key:}resultAndor\";\n\n    // Set keys using byte arrays\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // ANDOR(key1, key2, key3) = key1 AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // key1 AND (key2 OR key3) = 00000110 (bits 1,2 set)\n    jedis.bitop(BitOP.ANDOR, destKey, \"{bitOp:key:}1\", \"{bitOp:key:}2\", \"{bitOp:key:}3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000110 (bits 1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x06 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpOne() {\n    // Use single-byte values for simplicity\n    byte[] key1 = new byte[] { (byte) 0x01 }; // 00000001 - bit 0 set\n    byte[] key2 = new byte[] { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = new byte[] { (byte) 0x04 }; // 00000100 - bit 2 set\n    String destKey = \"{bitOp:key:}resultOne\";\n\n    // Set keys using byte arrays\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // ONE(key1, key2, key3) = bits set in exactly one of the inputs\n    // key1 = 00000001 (bit 0 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // Result = 00000111 (bits 0,1,2 set - each in exactly one input)\n    jedis.bitop(BitOP.ONE, destKey, \"{bitOp:key:}1\", \"{bitOp:key:}2\", \"{bitOp:key:}3\");\n\n    // Get result as bytes\n    byte[] resultBytes = jedis.get(destKey).getBytes();\n\n    // Expected result: 00000111 (bits 0,1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x07 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, resultBytes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiffBinary() {\n    byte[] key1 = { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"{bitOp:key:}resultDiffBinary\".getBytes();\n\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // DIFF(key1, key2, key3) = key1 AND NOT(key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key2 OR key3) = 11111001 (all bits except 1,2 set)\n    // key1 AND NOT(key2 OR key3) = 00000001 (only bit 0 set)\n    jedis.bitop(BitOP.DIFF, dest, \"{bitOp:key:}1\".getBytes(), \"{bitOp:key:}2\".getBytes(),\n        \"{bitOp:key:}3\".getBytes());\n\n    // Expected result: 00000001 (only bit 0 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x01 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff1Binary() {\n    byte[] key1 = { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"{bitOp:key:}resultDiff1Binary\".getBytes();\n\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // DIFF1(key1, key2, key3) = NOT(key1) AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // NOT(key1) = 11111000 (all bits except 0,1,2 set)\n    // NOT(key1) AND (key2 OR key3) = 00000000 (no bits set)\n    jedis.bitop(BitOP.DIFF1, dest, \"{bitOp:key:}1\".getBytes(), \"{bitOp:key:}2\".getBytes(),\n        \"{bitOp:key:}3\".getBytes());\n\n    // Expected result: 00000000 (no bits set)\n    byte[] expectedBytes = new byte[] { (byte) 0x00 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpAndorBinary() {\n    byte[] key1 = { (byte) 0x07 }; // 00000111 - bits 0,1,2 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"{bitOp:key:}resultAndorBinary\".getBytes();\n\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // ANDOR(key1, key2, key3) = key1 AND (key2 OR key3)\n    // key1 = 00000111 (bits 0,1,2 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // key2 OR key3 = 00000110 (bits 1,2 set)\n    // key1 AND (key2 OR key3) = 00000110 (bits 1,2 set)\n    jedis.bitop(BitOP.ANDOR, dest, \"{bitOp:key:}1\".getBytes(), \"{bitOp:key:}2\".getBytes(),\n        \"{bitOp:key:}3\".getBytes());\n\n    // Expected result: 00000110 (bits 1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x06 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpOneBinary() {\n    byte[] key1 = { (byte) 0x01 }; // 00000001 - bit 0 set\n    byte[] key2 = { (byte) 0x02 }; // 00000010 - bit 1 set\n    byte[] key3 = { (byte) 0x04 }; // 00000100 - bit 2 set\n    byte[] dest = \"{bitOp:key:}resultOneBinary\".getBytes();\n\n    jedis.set(\"{bitOp:key:}1\".getBytes(), key1);\n    jedis.set(\"{bitOp:key:}2\".getBytes(), key2);\n    jedis.set(\"{bitOp:key:}3\".getBytes(), key3);\n\n    // ONE(key1, key2, key3) = bits set in exactly one of the inputs\n    // key1 = 00000001 (bit 0 set)\n    // key2 = 00000010 (bit 1 set)\n    // key3 = 00000100 (bit 2 set)\n    // Result = 00000111 (bits 0,1,2 set - each in exactly one input)\n    jedis.bitop(BitOP.ONE, dest, \"{bitOp:key:}1\".getBytes(), \"{bitOp:key:}2\".getBytes(),\n        \"{bitOp:key:}3\".getBytes());\n\n    // Expected result: 00000111 (bits 0,1,2 set)\n    byte[] expectedBytes = new byte[] { (byte) 0x07 };\n\n    // Verify the result\n    assertArrayEquals(expectedBytes, jedis.get(dest));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiffSingleSourceShouldFail() {\n    assertThrows(JedisDataException.class,\n        () -> jedis.bitop(BitOP.DIFF, \"{bitOp:key:}dest\", \"{bitOp:key:}{bitOp:key:}src1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff1SingleSourceShouldFail() {\n    assertThrows(JedisDataException.class,\n        () -> jedis.bitop(BitOP.DIFF1, \"{bitOp:key:}dest\", \"{bitOp:key:}src1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpAndorSingleSourceShouldFail() {\n    assertThrows(JedisDataException.class,\n        () -> jedis.bitop(BitOP.ANDOR, \"{bitOp:key:}dest\", \"{bitOp:key:}src1\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiffBinarySingleSourceShouldFail() {\n    byte[] dest = \"{bitOp:key:}dest\".getBytes();\n    byte[] src1 = \"{bitOp:key:}src1\".getBytes();\n\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.DIFF, dest, src1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpDiff1BinarySingleSourceShouldFail() {\n    byte[] dest = \"{bitOp:key:}dest\".getBytes();\n    byte[] src1 = \"{bitOp:key:}src1\".getBytes();\n\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.DIFF1, dest, src1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void bitOpAndorBinarySingleSourceShouldFail() {\n    byte[] dest = \"{bitOp:key:}dest\".getBytes();\n    byte[] src1 = \"{bitOp:key:}src1\".getBytes();\n\n    assertThrows(JedisDataException.class, () -> jedis.bitop(BitOP.ANDOR, dest, src1));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/ExtendedVectorSetCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\nimport org.junit.jupiter.api.TestInfo;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\n\n/**\n * Integration tests for Redis Vector Sets based on the examples from the Redis documentation.\n * @see <a href=\"https://redis.io/docs/latest/develop/data-types/vector-sets/\">Redis Vector Sets\n *      Documentation</a>\n */\n@Tag(\"integration\")\n@Tag(\"vector-set\")\npublic abstract class ExtendedVectorSetCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  private String POINTS_KEY;\n\n  public ExtendedVectorSetCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  public void setUp(TestInfo testInfo) {\n    POINTS_KEY = testInfo.getDisplayName() + \":points\";\n    jedis.flushAll();\n\n    // Add the example points from the Redis documentation\n    // A: (1.0, 1.0), B: (-1.0, -1.0), C: (-1.0, 1.0), D: (1.0, -1.0), and E: (1.0, 0)\n    jedis.vadd(POINTS_KEY, new float[] { 1.0f, 1.0f }, \"pt:A\");\n    jedis.vadd(POINTS_KEY, new float[] { -1.0f, -1.0f }, \"pt:B\");\n    jedis.vadd(POINTS_KEY, new float[] { -1.0f, 1.0f }, \"pt:C\");\n    jedis.vadd(POINTS_KEY, new float[] { 1.0f, -1.0f }, \"pt:D\");\n    jedis.vadd(POINTS_KEY, new float[] { 1.0f, 0.0f }, \"pt:E\");\n  }\n\n  /**\n   * Test basic vector set operations as shown in the Redis documentation.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testBasicOperations() {\n\n    jedis.type(POINTS_KEY);\n    assertEquals(\"vectorset\", jedis.type(POINTS_KEY));\n\n    // Check the cardinality of the vector set\n    long count = jedis.vcard(POINTS_KEY);\n    assertEquals(5L, count);\n\n    // Check the dimensionality of the vectors\n    long dim = jedis.vdim(POINTS_KEY);\n    assertEquals(2L, dim);\n\n    // Retrieve the vectors for each point\n    List<Double> vectorA = jedis.vemb(POINTS_KEY, \"pt:A\");\n    assertEquals(2, vectorA.size());\n    assertEquals(1.0, vectorA.get(0), 0.001);\n    assertEquals(1.0, vectorA.get(1), 0.001);\n\n    List<Double> vectorB = jedis.vemb(POINTS_KEY, \"pt:B\");\n    assertEquals(2, vectorB.size());\n    assertEquals(-1.0, vectorB.get(0), 0.001);\n    assertEquals(-1.0, vectorB.get(1), 0.001);\n\n    List<Double> vectorC = jedis.vemb(POINTS_KEY, \"pt:C\");\n    assertEquals(2, vectorC.size());\n    assertEquals(-1.0, vectorC.get(0), 0.001);\n    assertEquals(1.0, vectorC.get(1), 0.001);\n\n    List<Double> vectorD = jedis.vemb(POINTS_KEY, \"pt:D\");\n    assertEquals(2, vectorD.size());\n    assertEquals(1.0, vectorD.get(0), 0.001);\n    assertEquals(-1.0, vectorD.get(1), 0.001);\n\n    List<Double> vectorE = jedis.vemb(POINTS_KEY, \"pt:E\");\n    assertEquals(2, vectorE.size());\n    assertEquals(1.0, vectorE.get(0), 0.001);\n    assertEquals(0.0, vectorE.get(1), 0.001);\n  }\n\n  /**\n   * Test adding and removing elements from a vector set.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testAddAndRemoveElements() {\n    // Add a new point F at (0, 0)\n    boolean result = jedis.vadd(POINTS_KEY, new float[] { 0.0f, 0.0f }, \"pt:F\");\n    assertTrue(result);\n\n    // Check the updated cardinality\n    long count = jedis.vcard(POINTS_KEY);\n    assertEquals(6L, count);\n\n    // Remove point F\n    result = jedis.vrem(POINTS_KEY, \"pt:F\");\n    assertTrue(result);\n\n    // Check the cardinality after removal\n    count = jedis.vcard(POINTS_KEY);\n    assertEquals(5L, count);\n  }\n\n  /**\n   * Test vector similarity search as shown in the Redis documentation.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVectorSimilaritySearch() {\n    // Search for vectors similar to (0.9, 0.1)\n    List<String> similar = jedis.vsim(POINTS_KEY, new float[] { 0.9f, 0.1f });\n    assertNotNull(similar);\n    assertFalse(similar.isEmpty());\n\n    // The expected order based on similarity to (0.9, 0.1) should be:\n    // E (1.0, 0.0), A (1.0, 1.0), D (1.0, -1.0), C (-1.0, 1.0), B (-1.0, -1.0)\n    assertEquals(\"pt:E\", similar.get(0));\n    assertEquals(\"pt:A\", similar.get(1));\n    assertEquals(\"pt:D\", similar.get(2));\n    assertEquals(\"pt:C\", similar.get(3));\n    assertEquals(\"pt:B\", similar.get(4));\n\n    // Search for vectors similar to point A with scores\n    VSimParams params = new VSimParams();\n    Map<String, Double> similarWithScores = jedis.vsimByElementWithScores(POINTS_KEY, \"pt:A\",\n      params);\n    assertNotNull(similarWithScores);\n    assertFalse(similarWithScores.isEmpty());\n\n    // Point A should have a perfect similarity score of 1.0 with itself\n    assertEquals(1.0, similarWithScores.get(\"pt:A\"), 0.001);\n\n    // Limit the number of results to 4\n    params = new VSimParams().count(4);\n    similar = jedis.vsimByElement(POINTS_KEY, \"pt:A\", params);\n    assertEquals(4, similar.size());\n  }\n\n  /**\n   * Test random sampling from a vector set.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testRandomSampling() {\n    // Get a single random element\n    String randomElement = jedis.vrandmember(POINTS_KEY);\n    assertNotNull(randomElement);\n    assertTrue(randomElement.startsWith(\"pt:\"));\n\n    // Get multiple random elements\n    List<String> randomElements = jedis.vrandmember(POINTS_KEY, 3);\n    assertEquals(3, randomElements.size());\n    for (String element : randomElements) {\n      assertTrue(element.startsWith(\"pt:\"));\n    }\n  }\n\n  /**\n   * Test HNSW graph links.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testHnswGraphLinks() {\n    // Get links for point A\n    List<List<String>> links = jedis.vlinks(POINTS_KEY, \"pt:A\");\n    assertNotNull(links);\n\n    // Get links with scores\n    List<Map<String, Double>> linksWithScores = jedis.vlinksWithScores(POINTS_KEY, \"pt:A\");\n    assertNotNull(linksWithScores);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testAttributeOperations() {\n    // Set attributes for point A\n    String attributes = \"{\\\"name\\\":\\\"Point A\\\",\\\"description\\\":\\\"First point added\\\"}\";\n    boolean result = jedis.vsetattr(POINTS_KEY, \"pt:A\", attributes);\n    assertTrue(result);\n\n    // Get attributes for point A\n    String retrievedAttributes = jedis.vgetattr(POINTS_KEY, \"pt:A\");\n    assertTrue(retrievedAttributes.contains(\"\\\"name\\\":\\\"Point A\\\"\"));\n    assertTrue(retrievedAttributes.contains(\"\\\"description\\\":\\\"First point added\\\"\"));\n\n    // Delete attributes by setting an empty string\n    result = jedis.vsetattr(POINTS_KEY, \"pt:A\", \"\");\n    assertTrue(result);\n\n    // Verify attributes are deleted\n    retrievedAttributes = jedis.vgetattr(POINTS_KEY, \"pt:A\");\n    assertNull(retrievedAttributes);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testFilteredVectorSimilaritySearch() {\n    // Set attributes for all points\n    jedis.vsetattr(POINTS_KEY, \"pt:A\", \"{\\\"size\\\":\\\"large\\\",\\\"price\\\":18.99}\");\n    jedis.vsetattr(POINTS_KEY, \"pt:B\", \"{\\\"size\\\":\\\"large\\\",\\\"price\\\":35.99}\");\n    jedis.vsetattr(POINTS_KEY, \"pt:C\", \"{\\\"size\\\":\\\"large\\\",\\\"price\\\":25.99}\");\n    jedis.vsetattr(POINTS_KEY, \"pt:D\", \"{\\\"size\\\":\\\"small\\\",\\\"price\\\":21.00}\");\n    jedis.vsetattr(POINTS_KEY, \"pt:E\", \"{\\\"size\\\":\\\"small\\\",\\\"price\\\":17.75}\");\n\n    // Filter by size = \"large\"\n    VSimParams params = new VSimParams().filter(\".size == \\\"large\\\"\");\n    List<String> similar = jedis.vsimByElement(POINTS_KEY, \"pt:A\", params);\n    assertEquals(3, similar.size());\n    assertEquals(Arrays.asList(\"pt:A\", \"pt:C\", \"pt:B\"), similar);\n\n    // Filter by size = \"large\" AND price > 20.00\n    params = new VSimParams().filter(\".size == \\\"large\\\" && .price > 20.00\");\n    similar = jedis.vsimByElement(POINTS_KEY, \"pt:A\", params);\n    assertEquals(2, similar.size());\n    assertEquals(Arrays.asList(\"pt:C\", \"pt:B\"), similar);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testQuantizationTypes() {\n    // Test Q8 quantization\n    VAddParams q8Params = new VAddParams().q8();\n    jedis.vadd(\"quantSetQ8\", new float[] { 1.262185f, 1.958231f }, \"quantElement\", q8Params);\n    List<Double> q8Vector = jedis.vemb(\"quantSetQ8\", \"quantElement\");\n    assertEquals(2, q8Vector.size());\n\n    // Test NOQUANT (no quantization)\n    VAddParams noQuantParams = new VAddParams().noQuant();\n    jedis.vadd(\"quantSetNoQ\", new float[] { 1.262185f, 1.958231f }, \"quantElement\", noQuantParams);\n    List<Double> noQuantVector = jedis.vemb(\"quantSetNoQ\", \"quantElement\");\n    assertEquals(2, noQuantVector.size());\n    assertEquals(1.262185, noQuantVector.get(0), 0.0001);\n    assertEquals(1.958231, noQuantVector.get(1), 0.0001);\n\n    // Test BIN (binary) quantization\n    VAddParams binParams = new VAddParams().bin();\n    jedis.vadd(\"quantSetBin\", new float[] { 1.262185f, 1.958231f }, \"quantElement\", binParams);\n    List<Double> binVector = jedis.vemb(\"quantSetBin\", \"quantElement\");\n    assertEquals(2, binVector.size());\n    // Binary quantization will convert values to either 1 or -1\n    assertTrue(binVector.get(0) == 1.0 || binVector.get(0) == -1.0);\n    assertTrue(binVector.get(1) == 1.0 || binVector.get(1) == -1.0);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/FunctionCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.TestInfo;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasKey;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@Tag(\"integration\")\npublic abstract class FunctionCommandsTestBase extends UnifiedJedisCommandsTestBase {\n  final String libraryName = \"mylib\";\n  final String TEST_LUA_SCRIPT_TMPL = \"#!lua name=%s\\n\"\n      + \"redis.register_function('%s', function(keys, args) return %s end)\";\n\n  private String functionName;\n\n  public FunctionCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  public void setUp(TestInfo info) {\n    functionName = info.getDisplayName().replaceAll(\"[^a-zA-Z0-9]\", \"_\");\n    jedis.functionLoad(String.format(TEST_LUA_SCRIPT_TMPL, libraryName, functionName, \"42\"));\n  }\n\n  @AfterEach\n  public void cleanUpFunctions() {\n    if (jedis == null) {\n      return;\n    }\n\n    try {\n      jedis.functionDelete(libraryName);\n    } catch (JedisException e) {\n      // ignore\n    }\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDeletion() {\n    List<LibraryInfo> listResponse = jedis.functionList();\n\n    assertThat(listResponse, hasSize(1));\n    assertThat(listResponse.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listResponse.get(0).getFunctions(), hasSize(1));\n    assertThat(listResponse.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n\n    String delete = jedis.functionDelete(libraryName);\n    assertThat(delete, equalTo(\"OK\"));\n\n    listResponse = jedis.functionList();\n    assertThat(listResponse, empty());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDeletionBinary() {\n    List<LibraryInfo> listResponse = jedis.functionList();\n\n    assertThat(listResponse, hasSize(1));\n    assertThat(listResponse.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listResponse.get(0).getFunctions(), hasSize(1));\n    assertThat(listResponse.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n\n    String deleteBinary = jedis.functionDelete(libraryName.getBytes());\n    assertThat(deleteBinary, equalTo(\"OK\"));\n\n    listResponse = jedis.functionList();\n    assertThat(listResponse, empty());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionListing() {\n\n    List<LibraryInfo> list = jedis.functionList();\n\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n    assertThat(list.get(0).getLibraryCode(), nullValue());\n\n    List<Object> listBinary = jedis.functionListBinary();\n\n    assertThat(listBinary, hasSize(1));\n\n    List<LibraryInfo> listLibrary = jedis.functionList(libraryName);\n\n    assertThat(listLibrary, hasSize(1));\n    assertThat(listLibrary.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listLibrary.get(0).getFunctions(), hasSize(1));\n    assertThat(listLibrary.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n    assertThat(listLibrary.get(0).getLibraryCode(), nullValue());\n\n    List<Object> listLibraryBinary = jedis.functionList(libraryName.getBytes());\n\n    assertThat(listLibraryBinary, hasSize(1));\n\n    List<LibraryInfo> listWithCode = jedis.functionListWithCode();\n\n    assertThat(listWithCode, hasSize(1));\n    assertThat(listWithCode.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listWithCode.get(0).getFunctions(), hasSize(1));\n    assertThat(listWithCode.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n    assertThat(listWithCode.get(0).getLibraryCode(), notNullValue());\n\n    List<Object> listWithCodeBinary = jedis.functionListWithCodeBinary();\n\n    assertThat(listWithCodeBinary, hasSize(1));\n\n    List<LibraryInfo> listWithCodeLibrary = jedis.functionListWithCode(libraryName);\n\n    assertThat(listWithCodeLibrary, hasSize(1));\n    assertThat(listWithCodeLibrary.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(listWithCodeLibrary.get(0).getFunctions(), hasSize(1));\n    assertThat(listWithCodeLibrary.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n    assertThat(listWithCodeLibrary.get(0).getLibraryCode(), notNullValue());\n\n    List<Object> listWithCodeLibraryBinary = jedis.functionListWithCode(libraryName.getBytes());\n\n    assertThat(listWithCodeLibraryBinary, hasSize(1));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionReload() {\n    Object result = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>());\n    assertThat(result, equalTo(42L));\n\n    String luaScriptChanged = String.format(TEST_LUA_SCRIPT_TMPL, libraryName, functionName, \"52\");\n    String replaceResult = jedis.functionLoadReplace(luaScriptChanged);\n    assertThat(replaceResult, equalTo(\"mylib\"));\n\n    Object resultAfter = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>());\n    assertThat(resultAfter, equalTo(52L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionReloadBinary() {\n    Object result = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>());\n    assertThat(result, equalTo(42L));\n\n    String luaScriptChanged = String.format(TEST_LUA_SCRIPT_TMPL, libraryName, functionName, \"52\");\n    String replaceResult = jedis.functionLoadReplace(luaScriptChanged.getBytes());\n    assertThat(replaceResult, equalTo(\"mylib\"));\n\n    Object resultAfter = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>());\n    assertThat(resultAfter, equalTo(52L));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionStats() {\n\n    for (int i = 0; i < 5; i++) {\n      Object result = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>());\n      assertThat(result, equalTo(42L));\n    }\n\n    FunctionStats stats = jedis.functionStats();\n\n    assertThat(stats, notNullValue());\n    assertThat(stats.getEngines(), hasKey(\"LUA\"));\n    Map<String, Object> luaStats = stats.getEngines().get(\"LUA\");\n    assertThat(luaStats, hasEntry(\"libraries_count\", 1L));\n    assertThat(luaStats, hasEntry(\"functions_count\", 1L));\n\n    Object statsBinary = jedis.functionStatsBinary();\n\n    assertThat(statsBinary, notNullValue());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDumpFlushRestore() {\n\n    List<LibraryInfo> list = jedis.functionList();\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n\n    byte[] dump = jedis.functionDump();\n    assertThat(dump, notNullValue());\n\n    String flush = jedis.functionFlush();\n    assertThat(flush, equalTo(\"OK\"));\n\n    list = jedis.functionList();\n    assertThat(list, empty());\n\n    String restore = jedis.functionRestore(dump);\n    assertThat(restore, equalTo(\"OK\"));\n\n    list = jedis.functionList();\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionDumpFlushRestoreWithPolicy() {\n\n    List<LibraryInfo> list = jedis.functionList();\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n\n    byte[] dump = jedis.functionDump();\n    assertThat(dump, notNullValue());\n\n    String flush = jedis.functionFlush();\n    assertThat(flush, equalTo(\"OK\"));\n\n    list = jedis.functionList();\n    assertThat(list, empty());\n\n    String restore = jedis.functionRestore(dump, FunctionRestorePolicy.REPLACE);\n    assertThat(restore, equalTo(\"OK\"));\n\n    list = jedis.functionList();\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionFlushWithMode() {\n\n    List<LibraryInfo> list = jedis.functionList();\n\n    assertThat(list, hasSize(1));\n    assertThat(list.get(0).getLibraryName(), equalTo(libraryName));\n    assertThat(list.get(0).getFunctions(), hasSize(1));\n    assertThat(list.get(0).getFunctions().get(0), hasEntry(\"name\", functionName));\n\n    String flush = jedis.functionFlush(FlushMode.SYNC);\n    assertThat(flush, equalTo(\"OK\"));\n\n    list = jedis.functionList();\n    assertThat(list, empty());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void testFunctionKill() {\n    JedisException e = assertThrows(JedisException.class, () -> jedis.functionKill());\n    assertThat(e.getMessage(), containsString(\"No scripts in execution right now\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/GeoCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.GeoCoordinateMatcher;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic abstract class GeoCommandsTestBase extends UnifiedJedisCommandsTestBase {\n  protected final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  protected final byte[] bA = { 0x0A };\n  protected final byte[] bB = { 0x0B };\n  protected final byte[] bC = { 0x0C };\n  protected final byte[] bD = { 0x0D };\n  protected final byte[] bNotexist = { 0x0F };\n\n  private static final double EPSILON = 1e-5;\n\n  public GeoCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void geoadd() {\n    assertEquals(1, jedis.geoadd(\"foo\", 1, 2, \"a\"));\n    assertEquals(0, jedis.geoadd(\"foo\", 2, 3, \"a\"));\n\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    coordinateMap.put(\"b\", new GeoCoordinate(2, 3));\n    coordinateMap.put(\"c\", new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(2, jedis.geoadd(\"foo\", coordinateMap));\n\n    // binary\n    assertEquals(1, jedis.geoadd(bfoo, 1, 2, bA));\n    assertEquals(0, jedis.geoadd(bfoo, 2, 3, bA));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    bcoordinateMap.put(bB, new GeoCoordinate(2, 3));\n    bcoordinateMap.put(bC, new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(2, jedis.geoadd(bfoo, bcoordinateMap));\n  }\n\n  @Test\n  public void geoaddWithParams() {\n    assertEquals(1, jedis.geoadd(\"foo\", 1, 2, \"a\"));\n\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    assertEquals(0, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().nx(), coordinateMap));\n    assertEquals(1, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().xx().ch(), coordinateMap));\n\n    coordinateMap.clear();\n    coordinateMap.put(\"b\", new GeoCoordinate(6, 7));\n    // never add elements.\n    assertEquals(0, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().xx(), coordinateMap));\n    assertEquals(1, jedis.geoadd(\"foo\", GeoAddParams.geoAddParams().nx(), coordinateMap));\n\n    // binary\n    assertEquals(1, jedis.geoadd(bfoo, 1, 2, bA));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    assertEquals(0, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().nx(), bcoordinateMap));\n    assertEquals(1, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().xx().ch(), bcoordinateMap));\n\n    bcoordinateMap.clear();\n    bcoordinateMap.put(bB, new GeoCoordinate(6, 7));\n    // never add elements.\n    assertEquals(0, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().xx(), bcoordinateMap));\n    assertEquals(1, jedis.geoadd(bfoo, GeoAddParams.geoAddParams().nx(), bcoordinateMap));\n  }\n\n  @Test\n  public void geodist() {\n    prepareGeoData();\n\n    Double dist = jedis.geodist(\"foo\", \"a\", \"b\");\n    assertEquals(157149, dist.intValue());\n\n    dist = jedis.geodist(\"foo\", \"a\", \"b\", GeoUnit.KM);\n    assertEquals(157, dist.intValue());\n\n    dist = jedis.geodist(\"foo\", \"a\", \"b\", GeoUnit.MI);\n    assertEquals(97, dist.intValue());\n\n    dist = jedis.geodist(\"foo\", \"a\", \"b\", GeoUnit.FT);\n    assertEquals(515583, dist.intValue());\n\n    // binary\n    dist = jedis.geodist(bfoo, bA, bB);\n    assertEquals(157149, dist.intValue());\n\n    dist = jedis.geodist(bfoo, bA, bB, GeoUnit.KM);\n    assertEquals(157, dist.intValue());\n\n    dist = jedis.geodist(bfoo, bA, bB, GeoUnit.MI);\n    assertEquals(97, dist.intValue());\n\n    dist = jedis.geodist(bfoo, bA, bB, GeoUnit.FT);\n    assertEquals(515583, dist.intValue());\n  }\n\n  @Test\n  public void geohash() {\n    prepareGeoData();\n\n    List<String> hashes = jedis.geohash(\"foo\", \"a\", \"b\", \"notexist\");\n    assertEquals(3, hashes.size());\n    assertEquals(\"s0dnu20t9j0\", hashes.get(0));\n    assertEquals(\"s093jd0k720\", hashes.get(1));\n    assertNull(hashes.get(2));\n\n    // binary\n    List<byte[]> bhashes = jedis.geohash(bfoo, bA, bB, bNotexist);\n    assertEquals(3, bhashes.size());\n    assertArrayEquals(SafeEncoder.encode(\"s0dnu20t9j0\"), bhashes.get(0));\n    assertArrayEquals(SafeEncoder.encode(\"s093jd0k720\"), bhashes.get(1));\n    assertNull(bhashes.get(2));\n  }\n\n  @Test\n  public void geopos() {\n    prepareGeoData();\n\n    List<GeoCoordinate> coordinates = jedis.geopos(\"foo\", \"a\", \"b\", \"notexist\");\n    assertEquals(3, coordinates.size());\n    assertEquals(3.0, coordinates.get(0).getLongitude(), EPSILON);\n    assertEquals(4.0, coordinates.get(0).getLatitude(), EPSILON);\n    assertEquals(2.0, coordinates.get(1).getLongitude(), EPSILON);\n    assertEquals(3.0, coordinates.get(1).getLatitude(), EPSILON);\n    assertNull(coordinates.get(2));\n\n    List<GeoCoordinate> bcoordinates = jedis.geopos(bfoo, bA, bB, bNotexist);\n    assertEquals(3, bcoordinates.size());\n    assertEquals(3.0, bcoordinates.get(0).getLongitude(), EPSILON);\n    assertEquals(4.0, bcoordinates.get(0).getLatitude(), EPSILON);\n    assertEquals(2.0, bcoordinates.get(1).getLongitude(), EPSILON);\n    assertEquals(3.0, bcoordinates.get(1).getLatitude(), EPSILON);\n    assertNull(bcoordinates.get(2));\n  }\n\n  @Test\n  public void georadius() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily\", coordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortDescending());\n    assertEquals(2, members.size());\n    assertEquals(\"Catania\", members.get(1).getMemberByString());\n    assertEquals(\"Palermo\", members.get(0).getMemberByString());\n\n    // sort, count 1\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1).withCoord().withDist().withHash());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n    assertEquals(3479447370796909L, response.getRawScore());\n\n    // sort, count 1, with hash\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1).withHash());\n    assertEquals(1, members.size());\n    response = members.get(0);\n    assertEquals(3479447370796909L, response.getRawScore());\n\n    // sort, count 1, any\n    members = jedis.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortDescending().count(1, true));\n    assertEquals(1, members.size());\n    response = members.get(0);\n    assertTrue(coordinateMap.containsKey(response.getMemberByString()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusStore() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily\", coordinateMap);\n\n    long size = jedis.georadiusStore(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam(),\n      GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"Palermo\");\n    expected.add(\"Catania\");\n    assertEquals(expected, jedis.zrange(\"SicilyStore\", 0, -1));\n  }\n\n  @Test\n  public void georadiusReadonly() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily\", coordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertEquals(\"Catania\", members.get(0).getMemberByString());\n    assertEquals(\"Palermo\", members.get(1).getMemberByString());\n\n    // sort, count 1\n    members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void georadiusBinary() {\n    // prepare datas\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(bfoo, bcoordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bB, members.get(0).getMember());\n    assertArrayEquals(bA, members.get(1).getMember());\n\n    // sort, count 1\n    members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadius(bfoo, 15, 37, 200, GeoUnit.KM, GeoRadiusParam.geoRadiusParam()\n        .sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusStoreBinary() {\n    // prepare datas\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(bfoo, bcoordinateMap);\n\n    long size = jedis.georadiusStore(bfoo, 15, 37, 200, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam(),\n      GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n    assertEquals(2, size);\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    AssertUtil.assertByteArrayListEquals(bexpected, jedis.zrange(\"SicilyStore\".getBytes(), 0, -1));\n  }\n\n  @Test\n  public void georadiusReadonlyBinary() {\n    // prepare datas\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(bfoo, bcoordinateMap);\n\n    List<GeoRadiusResponse> members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    // sort\n    members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bB, members.get(0).getMember());\n    assertArrayEquals(bA, members.get(1).getMember());\n\n    // sort, count 1\n    members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n    assertEquals(1, members.size());\n\n    // sort, count 1, withdist, withcoord\n    members = jedis.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n    GeoRadiusResponse response = members.get(0);\n    assertEquals(56.4413, response.getDistance(), EPSILON);\n    assertEquals(15.087269, response.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.502669, response.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void georadiusByMember() {\n    jedis.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMember(\"Sicily\", \"Agrigento\", 100,\n      GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMember(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM, GeoRadiusParam\n        .geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertEquals(\"Agrigento\", members.get(0).getMemberByString());\n    assertEquals(\"Palermo\", members.get(1).getMemberByString());\n\n    members = jedis.georadiusByMember(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM, GeoRadiusParam\n        .geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertEquals(\"Agrigento\", member.getMemberByString());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusByMemberStore() {\n    jedis.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    long size = jedis.georadiusByMemberStore(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam(),\n      GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"Agrigento\");\n    expected.add(\"Palermo\");\n    assertEquals(expected, jedis.zrange(\"SicilyStore\", 0, -1));\n  }\n\n  @Test\n  public void georadiusByMemberReadonly() {\n    jedis.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100,\n      GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertEquals(\"Agrigento\", members.get(0).getMemberByString());\n    assertEquals(\"Palermo\", members.get(1).getMemberByString());\n\n    members = jedis.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n      GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertEquals(\"Agrigento\", member.getMemberByString());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void georadiusByMemberBinary() {\n    jedis.geoadd(bfoo, 13.583333, 37.316667, bA);\n    jedis.geoadd(bfoo, 13.361389, 38.115556, bB);\n    jedis.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMember(bfoo, bA, 100, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMember(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bA, members.get(0).getMember());\n    assertArrayEquals(bB, members.get(1).getMember());\n\n    members = jedis.georadiusByMember(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertArrayEquals(bA, member.getMember());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusByMemberStoreBinary() {\n    jedis.geoadd(bfoo, 13.583333, 37.316667, bA);\n    jedis.geoadd(bfoo, 13.361389, 38.115556, bB);\n    jedis.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    assertEquals(2, jedis.georadiusByMemberStore(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\")));\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    AssertUtil.assertByteArrayListEquals(bexpected, jedis.zrange(\"SicilyStore\".getBytes(), 0, -1));\n  }\n\n  @Test\n  public void georadiusByMemberReadonlyBinary() {\n    jedis.geoadd(bfoo, 13.583333, 37.316667, bA);\n    jedis.geoadd(bfoo, 13.361389, 38.115556, bB);\n    jedis.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    List<GeoRadiusResponse> members = jedis.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM);\n    assertEquals(2, members.size());\n\n    members = jedis.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n    assertEquals(2, members.size());\n    assertArrayEquals(bA, members.get(0).getMember());\n    assertArrayEquals(bB, members.get(1).getMember());\n\n    members = jedis.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n    assertEquals(1, members.size());\n\n    GeoRadiusResponse member = members.get(0);\n    assertArrayEquals(bA, member.getMember());\n    assertEquals(0, member.getDistance(), EPSILON);\n    assertEquals(13.583333, member.getCoordinate().getLongitude(), EPSILON);\n    assertEquals(37.316667, member.getCoordinate().getLatitude(), EPSILON);\n  }\n\n  @Test\n  public void geosearch() {\n    jedis.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    jedis.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n    jedis.geoadd(\"barcelona\", 2.583333d, 41.316667d, \"place3\");\n\n    // FROMLONLAT and BYRADIUS\n    List<GeoRadiusResponse> members = jedis.geosearch(\"barcelona\",\n            new GeoCoordinate(2.191d,41.433d), 1000, GeoUnit.M);\n    assertEquals(1, members.size());\n    assertEquals(\"place1\", members.get(0).getMemberByString());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().byRadius(3000, GeoUnit.M)\n            .fromLonLat(2.191d,41.433d).desc());\n    assertEquals(2, members.size());\n    assertEquals(\"place2\", members.get(0).getMemberByString());\n\n    // FROMMEMBER and BYRADIUS\n    members = jedis.geosearch(\"barcelona\",\"place3\", 100, GeoUnit.KM);\n    assertEquals(3, members.size());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"place1\")\n            .byRadius(100, GeoUnit.KM).withDist().withCoord().withHash().count(2));\n\n    assertEquals(2, members.size());\n    assertEquals(\"place1\", members.get(0).getMemberByString());\n    GeoRadiusResponse res2 = members.get(1);\n    assertEquals(\"place2\", res2.getMemberByString());\n    assertEquals(3.0674157, res2.getDistance(), 5);\n    assertEquals(new GeoCoordinate(2.187376320362091, 41.40634178640635), res2.getCoordinate());\n\n    // FROMMEMBER and BYBOX\n    members = jedis.geosearch(\"barcelona\",\"place3\", 100, 100, GeoUnit.KM);\n    assertEquals(3, members.size());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"place3\")\n            .byBox(100, 100, GeoUnit.KM).asc().count(1, true));\n    assertEquals(1, members.size());\n\n    // FROMLONLAT and BYBOX\n    members = jedis.geosearch(\"barcelona\", new GeoCoordinate(2.191, 41.433),\n            1, 1, GeoUnit.KM);\n    assertEquals(1, members.size());\n\n    // using Params\n    members = jedis.geosearch(\"barcelona\", new GeoSearchParam().byBox(1,1, GeoUnit.KM)\n            .fromLonLat(2.191, 41.433).withDist().withCoord());\n    assertEquals(1, members.size());\n    assertEquals(\"place1\", members.get(0).getMemberByString());\n    assertEquals(0.0881, members.get(0).getDistance(), 10);\n    assertThat(members.get(0).getCoordinate(),\n        GeoCoordinateMatcher.atCoordinates(2.19093829393386841, 41.43379028184083523));\n  }\n\n  @Test\n  public void geosearchNegative() {\n    // combine byradius and bybox\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam()\n          .byRadius(3000, GeoUnit.M)\n          .byBox(300, 300, GeoUnit.M));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n\n    // without byradius and without bybox\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"foobar\"));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n\n    // combine frommember and fromlonlat\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam()\n          .fromMember(\"foobar\")\n          .fromLonLat(10,10));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n\n    // without frommember and without fromlonlat\n    try {\n      jedis.geosearch(\"barcelona\", new GeoSearchParam().byRadius(10, GeoUnit.MI));\n      fail();\n    } catch (IllegalArgumentException ignored) { }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void geosearchstore() {\n    jedis.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    jedis.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n    jedis.geoadd(\"barcelona\", 2.583333d, 41.316667d, \"place3\");\n\n    // FROMLONLAT and BYRADIUS\n    long members = jedis.geosearchStore(\"tel-aviv\", \"barcelona\", new GeoCoordinate(2.191d,41.433d),\n            1000, GeoUnit.M);\n    assertEquals(1, members);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"place1\");\n    assertEquals(expected, jedis.zrange(\"tel-aviv\", 0, -1));\n\n    members = jedis.geosearchStore(\"tel-aviv\",\"barcelona\", new GeoSearchParam()\n            .byRadius(3000, GeoUnit.M)\n            .fromLonLat(new GeoCoordinate(2.191d,41.433d)));\n    assertEquals(2, members);\n    assertEquals(2, members);\n\n    // FROMMEMBER and BYRADIUS\n    members = jedis.geosearchStore(\"tel-aviv\", \"barcelona\",\"place3\", 100, GeoUnit.KM);\n    assertEquals(3, members);\n\n    // FROMMEMBER and BYBOX\n    members = jedis.geosearchStore(\"tel-aviv\",\"barcelona\",\"place3\", 100, 100, GeoUnit.KM);\n    assertEquals(3, members);\n\n    // FROMLONLAT and BYBOX\n    members = jedis.geosearchStore(\"tel-aviv\",\"barcelona\", new GeoCoordinate(2.191, 41.433),\n            1, 1, GeoUnit.KM);\n    assertEquals(1, members);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void geosearchstoreWithdist() {\n    jedis.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    jedis.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n\n    long members = jedis.geosearchStoreStoreDist(\"tel-aviv\",\"barcelona\", new GeoSearchParam().byRadius(3000, GeoUnit.M)\n            .fromLonLat(2.191d,41.433d));\n\n    assertEquals(2, members);\n    assertEquals(88.05060698409301, jedis.zscore(\"tel-aviv\", \"place1\"), 5);\n  }\n\n  private void prepareGeoData() {\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    coordinateMap.put(\"b\", new GeoCoordinate(2, 3));\n    coordinateMap.put(\"c\", new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(3, jedis.geoadd(\"foo\", coordinateMap));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    bcoordinateMap.put(bB, new GeoCoordinate(2, 3));\n    bcoordinateMap.put(bC, new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(3, jedis.geoadd(bfoo, bcoordinateMap));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/HashesCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static java.util.Arrays.asList;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.both;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.HGetExParams;\nimport redis.clients.jedis.params.HSetExParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.JedisByteHashMap;\n\n@Tag(\"integration\")\npublic abstract class HashesCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] bbare = { 0x05, 0x06, 0x07, 0x08, 0x09 };\n  final byte[] bcare = { 0x09, 0x0A, 0x0B, 0x0C, 0x0D };\n  \n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public HashesCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void hset() {\n    assertEquals(1, jedis.hset(\"foo\", \"bar\", \"car\"));\n    assertEquals(0, jedis.hset(\"foo\", \"bar\", \"foo\"));\n\n    // Binary\n    assertEquals(1, jedis.hset(bfoo, bbar, bcar));\n    assertEquals(0, jedis.hset(bfoo, bbar, bfoo));\n  }\n\n  @Test\n  public void hget() {\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertNull(jedis.hget(\"bar\", \"foo\"));\n    assertNull(jedis.hget(\"foo\", \"car\"));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n\n    // Binary\n    jedis.hset(bfoo, bbar, bcar);\n    assertNull(jedis.hget(bbar, bfoo));\n    assertNull(jedis.hget(bfoo, bcar));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n  }\n\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void hgetex() {\n    long seconds = 20;\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(\"car\"), jedis.hgetex(\"foo\", HGetExParams.hGetExParams().ex(seconds), \"bar\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(\"car\"), jedis.hgetex(\"foo\", HGetExParams.hGetExParams().persist(), \"bar\"));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bar\").get(0));\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(\"car\", \"care\"), jedis.hgetex(\"foo\", HGetExParams.hGetExParams().ex(seconds), \"bar\", \"bare\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(\"foo\", \"bare\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    // Binary\n    jedis.hset(bfoo, bbar, bcar);\n    assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    jedis.hset(bfoo, bbar, bcar);\n    assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().persist(), bbar));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0));\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbare, bcare);\n    assertByteArrayListEquals(asList(bcar, bcare), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar, bbare));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void hgetdel() {\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(\"car\"), jedis.hgetdel(\"foo\", \"bar\"));\n    assertEquals(null, jedis.hget(\"foo\", \"bar\"));\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(\"car\", \"care\"), jedis.hgetdel(\"foo\", \"bar\", \"bare\"));\n    assertEquals(null, jedis.hget(\"foo\", \"bar\"));\n    assertEquals(null, jedis.hget(\"foo\", \"bare\"));\n\n    // Binary\n    jedis.hset(bfoo, bbar, bcar);\n    assertByteArrayListEquals(asList(bcar), jedis.hgetdel(bfoo, bbar));\n    assertEquals(null, jedis.hget(bfoo, bbar));\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbare, bcare);\n    assertByteArrayListEquals(asList(bcar, bcare), jedis.hgetdel(bfoo, bbar, bbare));\n    assertEquals(null, jedis.hget(bfoo, bbar));\n    assertEquals(null, jedis.hget(bfoo, bbare));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.9.0\")\n  public void hsetex() {\n    long seconds = 20;\n    jedis.del(\"foo\");\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().ex(seconds).fnx(), \"bar\", \"car\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().keepTtl(), \"bar\", \"car\"));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().fxx(), \"bar\", \"car\"));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bar\").get(0));\n\n    HashMap<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"bare\", \"care\");\n    jedis.del(\"foo\");\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().ex(seconds).fnx(), hash));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(\"foo\", \"bare\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().keepTtl(), hash));\n    assertThat(jedis.httl(\"foo\", \"bar\").get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(\"foo\", \"bare\").get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(\"foo\", HSetExParams.hSetExParams().fxx(), hash));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bar\").get(0));\n    assertEquals(Long.valueOf(-1), jedis.httl(\"foo\", \"bare\").get(0));\n\n    // Binary\n    jedis.del(bfoo);\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bbar, bcar));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bbar, bcar));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bbar, bcar));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0));\n\n    HashMap<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bbare, bcare);\n\n    jedis.del(bfoo);\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bhash));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bhash));\n    assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1));\n    assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1));\n\n    assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bhash));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0));\n    assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbare).get(0));\n  }\n\n  @Test\n  public void hsetnx() {\n    assertEquals(1, jedis.hsetnx(\"foo\", \"bar\", \"car\"));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n\n    assertEquals(0, jedis.hsetnx(\"foo\", \"bar\", \"foo\"));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n\n    assertEquals(1, jedis.hsetnx(\"foo\", \"car\", \"bar\"));\n    assertEquals(\"bar\", jedis.hget(\"foo\", \"car\"));\n\n    // Binary\n    assertEquals(1, jedis.hsetnx(bfoo, bbar, bcar));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n\n    assertEquals(0, jedis.hsetnx(bfoo, bbar, bfoo));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n\n    assertEquals(1, jedis.hsetnx(bfoo, bcar, bbar));\n    assertArrayEquals(bbar, jedis.hget(bfoo, bcar));\n  }\n\n  @Test\n  public void hmset() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    assertEquals(\"OK\", jedis.hmset(\"foo\", hash));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.hget(\"foo\", \"car\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    assertEquals(\"OK\", jedis.hmset(bfoo, bhash));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n    assertArrayEquals(bbar, jedis.hget(bfoo, bcar));\n  }\n\n  @Test\n  public void hsetVariadic() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    assertEquals(2, jedis.hset(\"foo\", hash));\n    assertEquals(\"car\", jedis.hget(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.hget(\"foo\", \"car\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    assertEquals(2, jedis.hset(bfoo, bhash));\n    assertArrayEquals(bcar, jedis.hget(bfoo, bbar));\n    assertArrayEquals(bbar, jedis.hget(bfoo, bcar));\n  }\n\n  @Test\n  public void hmget() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    List<String> values = jedis.hmget(\"foo\", \"bar\", \"car\", \"foo\");\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"car\");\n    expected.add(\"bar\");\n    expected.add(null);\n\n    assertEquals(expected, values);\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    List<byte[]> bvalues = jedis.hmget(bfoo, bbar, bcar, bfoo);\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bcar);\n    bexpected.add(bbar);\n    bexpected.add(null);\n\n    AssertUtil.assertByteArrayListEquals(bexpected, bvalues);\n  }\n\n  @Test\n  public void hincrBy() {\n    assertEquals(1, jedis.hincrBy(\"foo\", \"bar\", 1));\n    assertEquals(0, jedis.hincrBy(\"foo\", \"bar\", -1));\n    assertEquals(-10, jedis.hincrBy(\"foo\", \"bar\", -10));\n\n    // Binary\n    assertEquals(1, jedis.hincrBy(bfoo, bbar, 1));\n    assertEquals(0, jedis.hincrBy(bfoo, bbar, -1));\n    assertEquals(-10, jedis.hincrBy(bfoo, bbar, -10));\n  }\n\n  @Test\n  public void hincrByFloat() {\n    assertEquals(1.5d, jedis.hincrByFloat(\"foo\", \"bar\", 1.5d), 0);\n    assertEquals(0d, jedis.hincrByFloat(\"foo\", \"bar\", -1.5d), 0);\n    assertEquals(-10.7d, jedis.hincrByFloat(\"foo\", \"bar\", -10.7d), 0);\n\n    // Binary\n    assertEquals(1.5d, jedis.hincrByFloat(bfoo, bbar, 1.5d), 0d);\n    assertEquals(0d, jedis.hincrByFloat(bfoo, bbar, -1.5d), 0d);\n    assertEquals(-10.7d, jedis.hincrByFloat(bfoo, bbar, -10.7d), 0d);\n  }\n\n  @Test\n  public void hexists() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertFalse(jedis.hexists(\"bar\", \"foo\"));\n    assertFalse(jedis.hexists(\"foo\", \"foo\"));\n    assertTrue(jedis.hexists(\"foo\", \"bar\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertFalse(jedis.hexists(bbar, bfoo));\n    assertFalse(jedis.hexists(bfoo, bfoo));\n    assertTrue(jedis.hexists(bfoo, bbar));\n  }\n\n  @Test\n  public void hdel() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertEquals(0, jedis.hdel(\"bar\", \"foo\"));\n    assertEquals(0, jedis.hdel(\"foo\", \"foo\"));\n    assertEquals(1, jedis.hdel(\"foo\", \"bar\"));\n    assertNull(jedis.hget(\"foo\", \"bar\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertEquals(0, jedis.hdel(bbar, bfoo));\n    assertEquals(0, jedis.hdel(bfoo, bfoo));\n    assertEquals(1, jedis.hdel(bfoo, bbar));\n    assertNull(jedis.hget(bfoo, bbar));\n  }\n\n  @Test\n  public void hlen() {\n    Map<String, String> hash = new HashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    assertEquals(0, jedis.hlen(\"bar\"));\n    assertEquals(2, jedis.hlen(\"foo\"));\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    assertEquals(0, jedis.hlen(bbar));\n    assertEquals(2, jedis.hlen(bfoo));\n  }\n\n  @Test\n  public void hkeys() {\n    Map<String, String> hash = new LinkedHashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    Set<String> keys = jedis.hkeys(\"foo\");\n    Set<String> expected = new LinkedHashSet<String>();\n    expected.add(\"bar\");\n    expected.add(\"car\");\n    assertEquals(expected, keys);\n\n    // Binary\n    Map<byte[], byte[]> bhash = new LinkedHashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    Set<byte[]> bkeys = jedis.hkeys(bfoo);\n    Set<byte[]> bexpected = new LinkedHashSet<byte[]>();\n    bexpected.add(bbar);\n    bexpected.add(bcar);\n    AssertUtil.assertByteArraySetEquals(bexpected, bkeys);\n  }\n\n  @Test\n  public void hvals() {\n    Map<String, String> hash = new LinkedHashMap<String, String>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", hash);\n\n    List<String> vals = jedis.hvals(\"foo\");\n    assertEquals(2, vals.size());\n    AssertUtil.assertCollectionContains(vals, \"bar\");\n    AssertUtil.assertCollectionContains(vals, \"car\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new LinkedHashMap<byte[], byte[]>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    jedis.hmset(bfoo, bhash);\n\n    List<byte[]> bvals = jedis.hvals(bfoo);\n\n    assertEquals(2, bvals.size());\n    AssertUtil.assertByteArrayCollectionContains(bvals, bbar);\n    AssertUtil.assertByteArrayCollectionContains(bvals, bcar);\n  }\n\n  @Test\n  public void hgetAll() {\n    Map<String, String> h = new HashMap<String, String>();\n    h.put(\"bar\", \"car\");\n    h.put(\"car\", \"bar\");\n    jedis.hmset(\"foo\", h);\n\n    Map<String, String> hash = jedis.hgetAll(\"foo\");\n    assertEquals(2, hash.size());\n    assertEquals(\"car\", hash.get(\"bar\"));\n    assertEquals(\"bar\", hash.get(\"car\"));\n\n    // Binary\n    Map<byte[], byte[]> bh = new HashMap<byte[], byte[]>();\n    bh.put(bbar, bcar);\n    bh.put(bcar, bbar);\n    jedis.hmset(bfoo, bh);\n    Map<byte[], byte[]> bhash = jedis.hgetAll(bfoo);\n\n    assertEquals(2, bhash.size());\n    assertArrayEquals(bcar, bhash.get(bbar));\n    assertArrayEquals(bbar, bhash.get(bcar));\n  }\n\n  @Test\n  public void hscan() {\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(\"a\", \"b\"));\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(\"x\", \"y\"));\n\n    // binary\n    jedis.hset(bfoo, bbar, bcar);\n\n    ScanResult<Map.Entry<byte[], byte[]>> bResult = jedis.hscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(1, bResult.getResult().size());\n\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(bbar));\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(bcar));\n  }\n\n  @Test\n  public void hscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n    jedis.hset(\"foo\", \"aa\", \"xx\");\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(\"a\", \"aa\"));\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(\"x\", \"xx\"));\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<Map.Entry<byte[], byte[]>> bResult = jedis.hscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(4, bResult.getResult().size());\n\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getKey).collect(Collectors.toList()),\n        containsInAnyOrder(bbar, bbar1, bbar2, bbar3));\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getValue).collect(Collectors.toList()),\n        containsInAnyOrder(bcar, bcar, bcar, bcar));\n  }\n\n  @Test\n  public void hscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.hset(\"foo\", \"a\" + i, \"x\" + i);\n    }\n\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getKey).map(s -> s.substring(0, 1)).collect(Collectors.toSet()),\n        containsInAnyOrder(\"a\"));\n    assertThat(\n        result.getResult().stream().map(Map.Entry::getValue).map(s -> s.substring(0, 1)).collect(Collectors.toSet()),\n        containsInAnyOrder(\"x\"));\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<Map.Entry<byte[], byte[]>> bResult = jedis.hscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getKey)\n            .map(a -> Arrays.copyOfRange(a, 0, 4)).map(Arrays::toString).collect(Collectors.toSet()),\n        containsInAnyOrder(Arrays.toString(bbar)));\n    assertThat(\n        bResult.getResult().stream().map(Map.Entry::getValue)\n            .map(a -> Arrays.copyOfRange(a, 0, 4)).map(Arrays::toString).collect(Collectors.toSet()),\n        containsInAnyOrder(Arrays.toString(bcar)));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"NOVALUES flag (since Redis 7.4)\")\n  public void hscanNoValues() {\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n\n    ScanResult<String> result = jedis.hscanNoValues(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(result.getResult(), containsInAnyOrder(\"a\", \"b\"));\n\n    // binary\n    jedis.hset(bfoo, bbar, bcar);\n\n    ScanResult<byte[]> bResult = jedis.hscanNoValues(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(1, bResult.getResult().size());\n\n    assertThat(bResult.getResult(), containsInAnyOrder(bbar));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"NOVALUES flag (since Redis 7.4)\")\n  public void hscanNoValuesMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.hset(\"foo\", \"b\", \"y\");\n    jedis.hset(\"foo\", \"a\", \"x\");\n    jedis.hset(\"foo\", \"aa\", \"xx\");\n    ScanResult<String> result = jedis.hscanNoValues(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertEquals(2, result.getResult().size());\n\n    assertThat(result.getResult(), containsInAnyOrder(\"a\", \"aa\"));\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<byte[]> bResult = jedis.hscanNoValues(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertEquals(4, bResult.getResult().size());\n\n    assertThat(bResult.getResult(), containsInAnyOrder(bbar, bbar1, bbar2, bbar3));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"NOVALUES flag (since Redis 7.4)\")\n  public void hscanNoValuesCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.hset(\"foo\", \"a\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.hscanNoValues(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    assertThat(\n        result.getResult().stream().map(s -> s.substring(0, 1)).collect(Collectors.toSet()),\n        containsInAnyOrder(\"a\"));\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.hset(bfoo, bbar, bcar);\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    jedis.hset(bfoo, bbar3, bcar);\n\n    ScanResult<byte[]> bResult = jedis.hscanNoValues(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n\n    assertThat(\n        bResult.getResult().stream()\n            .map(a -> Arrays.copyOfRange(a, 0, 4)).map(Arrays::toString).collect(Collectors.toSet()),\n        containsInAnyOrder(Arrays.toString(bbar)));\n  }\n\n  @Test\n  public void testHstrLen_EmptyHash() {\n    Long response = jedis.hstrlen(\"myhash\", \"k1\");\n    assertEquals(0l, response.longValue());\n  }\n\n  @Test\n  public void testHstrLen() {\n    Map<String, String> values = new HashMap<>();\n    values.put(\"key\", \"value\");\n    jedis.hmset(\"myhash\", values);\n    Long response = jedis.hstrlen(\"myhash\", \"key\");\n    assertEquals(5l, response.longValue());\n\n  }\n\n  @Test\n  public void testBinaryHstrLen() {\n    Map<byte[], byte[]> values = new HashMap<>();\n    values.put(bbar, bcar);\n    jedis.hmset(bfoo, values);\n    Long response = jedis.hstrlen(bfoo, bbar);\n    assertEquals(4l, response.longValue());\n  }\n\n  @Test\n  public void hrandfield() {\n    assertNull(jedis.hrandfield(\"foo\"));\n    assertEquals(Collections.emptyList(), jedis.hrandfield(\"foo\", 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(\"foo\", 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(\"foo\", -1));\n\n    Map<String, String> hash = new LinkedHashMap<>();\n    hash.put(\"bar\", \"bar\");\n    hash.put(\"car\", \"car\");\n    hash.put(\"bar1\", \"bar1\");\n\n    jedis.hset(\"foo\", hash);\n\n    assertTrue(hash.containsKey(jedis.hrandfield(\"foo\")));\n    assertEquals(2, jedis.hrandfield(\"foo\", 2).size());\n\n    List<Map.Entry<String, String>> actual = jedis.hrandfieldWithValues(\"foo\", 2);\n    assertEquals(2, actual.size());\n    actual.forEach(e -> assertEquals(hash.get(e.getKey()), e.getValue()));\n\n    actual = jedis.hrandfieldWithValues(\"foo\", 5);\n    assertEquals(3, actual.size());\n    actual.forEach(e -> assertEquals(hash.get(e.getKey()), e.getValue()));\n\n    actual = jedis.hrandfieldWithValues(\"foo\", -5);\n    assertEquals(5, actual.size());\n    actual.forEach(e -> assertEquals(hash.get(e.getKey()), e.getValue()));\n\n    // binary\n    assertNull(jedis.hrandfield(bfoo));\n    assertEquals(Collections.emptyList(), jedis.hrandfield(bfoo, 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(bfoo, 1));\n    assertEquals(Collections.emptyList(), jedis.hrandfieldWithValues(bfoo, -1));\n\n    Map<byte[], byte[]> bhash = new JedisByteHashMap();\n    bhash.put(bbar, bbar);\n    bhash.put(bcar, bcar);\n    bhash.put(bbar1, bbar1);\n\n    jedis.hset(bfoo, bhash);\n\n    assertTrue(bhash.containsKey(jedis.hrandfield(bfoo)));\n    assertEquals(2, jedis.hrandfield(bfoo, 2).size());\n\n    List<Map.Entry<byte[], byte[]>> bactual = jedis.hrandfieldWithValues(bfoo, 2);\n    assertEquals(2, bactual.size());\n    bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue()));\n\n    bactual = jedis.hrandfieldWithValues(bfoo, 5);\n    assertEquals(3, bactual.size());\n    bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue()));\n\n    bactual = jedis.hrandfieldWithValues(bfoo, -5);\n    assertEquals(5, bactual.size());\n    bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue()));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAndHttl() {\n    long seconds1 = 20;\n    long seconds2 = 10;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(1L, -2L), jedis.hexpire(\"foo\", seconds1, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(0L, 1L), jedis.hexpire(\"foo\", seconds2, ExpiryOption.NX, \"bar\", \"bared\"));\n\n    assertThat(jedis.httl(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAndHttlBinary() {\n    long seconds1 = 20;\n    long seconds2 = 10;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds1, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(0L, 1L), jedis.hexpire(bfoo, seconds2, ExpiryOption.NX, bbar1, bbar3));\n\n    assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3),\n        contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAndHpttl() {\n    long millis1 = 20_000;\n    long millis2 = 10_000;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(1L, -2L), jedis.hpexpire(\"foo\", millis1, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(1L, 0L), jedis.hpexpire(\"foo\", millis2, ExpiryOption.XX, \"bar\", \"bared\"));\n\n    assertThat(jedis.hpttl(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 1000)), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAndHpttlBinary() {\n    long millis1 = 20_000;\n    long millis2 = 10_000;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    assertEquals(asList(1L, -2L), jedis.hpexpire(bfoo, millis1, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(1L, 0L), jedis.hpexpire(bfoo, millis2, ExpiryOption.XX, bbar1, bbar3));\n\n    assertThat(jedis.hpttl(bfoo, bbar1, bbar2, bbar3),\n        contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 1000)), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAtAndExpireTime() {\n    long currSeconds = System.currentTimeMillis() / 1000;\n    long seconds1 = currSeconds + 20;\n    long seconds2 = currSeconds + 10;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(1L, -2L), jedis.hexpireAt(\"foo\", seconds1, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(1L, 1L), jedis.hexpireAt(\"foo\", seconds2, ExpiryOption.LT, \"bar\", \"bared\"));\n\n    assertThat(jedis.hexpireTime(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hexpireAtAndExpireTimeBinary() {\n    long currSeconds = System.currentTimeMillis() / 1000;\n    long seconds1 = currSeconds + 20;\n    long seconds2 = currSeconds + 10;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    assertEquals(asList(1L, -2L), jedis.hexpireAt(bfoo, seconds1, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(1L, 1L), jedis.hexpireAt(bfoo, seconds2, ExpiryOption.LT, bbar1, bbar3));\n\n    assertThat(jedis.hexpireTime(bfoo, bbar1, bbar2, bbar3),\n        contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L),\n            both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1))));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAtAndPexpireTime() {\n    long currMillis = System.currentTimeMillis();\n    long unixMillis = currMillis + 20_000;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    assertEquals(asList(1L, -2L), jedis.hpexpireAt(\"foo\", unixMillis - 100, \"bar\", \"bared\"));\n\n    jedis.hset(\"foo\", \"bared\", \"cared\");\n    assertEquals(asList(1L, 0L), jedis.hpexpireAt(\"foo\", unixMillis, ExpiryOption.GT, \"bar\", \"bared\"));\n\n    assertThat(jedis.hpexpireTime(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpexpireAtAndPexpireTimeBinary() {\n    long currMillis = System.currentTimeMillis();\n    long unixMillis = currMillis + 20_000;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    assertEquals(asList(1L, -2L), jedis.hpexpireAt(bfoo, unixMillis - 100, bbar1, bbar3));\n\n    jedis.hset(bfoo, bbar3, bcar);\n    assertEquals(asList(1L, 0L), jedis.hpexpireAt(bfoo, unixMillis, ExpiryOption.GT, bbar1, bbar3));\n\n    assertThat(jedis.hpexpireTime(bfoo, bbar1, bbar2, bbar3),\n        contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpersist() {\n    long seconds = 20;\n\n    jedis.hset(\"foo\", \"bar\", \"car\");\n    jedis.hset(\"foo\", \"bare\", \"care\");\n    assertEquals(asList(1L, -2L), jedis.hexpire(\"foo\", seconds, \"bar\", \"bared\"));\n\n    assertEquals(asList(1L, -1L, -2L), jedis.hpersist(\"foo\", \"bar\", \"bare\", \"bared\"));\n\n    assertThat(jedis.httl(\"foo\", \"bar\", \"bare\", \"bared\"),\n        contains(equalTo(-1L), equalTo(-1L), equalTo(-2L)));\n  }\n\n  @Test\n  @SinceRedisVersion(\"7.4.0\")\n  public void hpersistBinary() {\n    long seconds = 20;\n\n    jedis.hset(bfoo, bbar1, bcar);\n    jedis.hset(bfoo, bbar2, bcar);\n    assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds, bbar1, bbar3));\n\n    assertEquals(asList(1L, -1L, -2L), jedis.hpersist(bfoo, bbar1, bbar2, bbar3));\n\n    assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3),\n        contains(equalTo(-1L), equalTo(-1L), equalTo(-2L)));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/HotkeysCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\n@EnabledOnCommand(\"HOTKEYS\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\npublic abstract class HotkeysCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  public HotkeysCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  void setUp() {\n    clearState();\n  }\n\n  @AfterEach\n  void tearDown() {\n    clearState();\n  }\n\n  private void clearState() {\n    if (jedis != null) {\n      jedis.flushAll();\n      jedis.hotkeysStop();\n      jedis.hotkeysReset();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/HyperLogLogCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic abstract class HyperLogLogCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  public HyperLogLogCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void pfadd() {\n    long status = jedis.pfadd(\"foo\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"foo\", \"a\");\n    assertEquals(0, status);\n  }\n\n  @Test\n  public void pfaddBinary() {\n    byte[] bFoo = SafeEncoder.encode(\"foo\");\n    byte[] bBar = SafeEncoder.encode(\"bar\");\n    byte[] bBar2 = SafeEncoder.encode(\"bar2\");\n\n    long status = jedis.pfadd(bFoo, bBar, bBar2);\n    assertEquals(1, status);\n\n    status = jedis.pfadd(bFoo, bBar, bBar2);\n    assertEquals(0, status);\n  }\n\n  @Test\n  public void pfcount() {\n    long status = jedis.pfadd(\"hll\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"hll\", \"zap\", \"zap\", \"zap\");\n    assertEquals(0, status);\n\n    status = jedis.pfadd(\"hll\", \"foo\", \"bar\");\n    assertEquals(0, status);\n\n    status = jedis.pfcount(\"hll\");\n    assertEquals(3, status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pfcounts() {\n    long status = jedis.pfadd(\"hll_1\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n    status = jedis.pfadd(\"hll_2\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"hll_3\", \"foo\", \"bar\", \"baz\");\n    assertEquals(1, status);\n    status = jedis.pfcount(\"hll_1\");\n    assertEquals(3, status);\n    status = jedis.pfcount(\"hll_2\");\n    assertEquals(3, status);\n    status = jedis.pfcount(\"hll_3\");\n    assertEquals(3, status);\n\n    status = jedis.pfcount(\"hll_1\", \"hll_2\");\n    assertEquals(3, status);\n\n    status = jedis.pfcount(\"hll_1\", \"hll_2\", \"hll_3\");\n    assertEquals(4, status);\n\n  }\n\n  @Test\n  public void pfcountBinary() {\n    byte[] bHll = SafeEncoder.encode(\"hll\");\n    byte[] bFoo = SafeEncoder.encode(\"foo\");\n    byte[] bBar = SafeEncoder.encode(\"bar\");\n    byte[] bZap = SafeEncoder.encode(\"zap\");\n\n    long status = jedis.pfadd(bHll, bFoo, bBar, bZap);\n    assertEquals(1, status);\n\n    status = jedis.pfadd(bHll, bZap, bZap, bZap);\n    assertEquals(0, status);\n\n    status = jedis.pfadd(bHll, bFoo, bBar);\n    assertEquals(0, status);\n\n    status = jedis.pfcount(bHll);\n    assertEquals(3, status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pfmerge() {\n    long status = jedis.pfadd(\"hll1\", \"foo\", \"bar\", \"zap\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"hll2\", \"a\", \"b\", \"c\", \"foo\");\n    assertEquals(1, status);\n\n    String mergeStatus = jedis.pfmerge(\"hll3\", \"hll1\", \"hll2\");\n    assertEquals(\"OK\", mergeStatus);\n\n    status = jedis.pfcount(\"hll3\");\n    assertEquals(6, status);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void pfmergeBinary() {\n    byte[] bHll1 = SafeEncoder.encode(\"hll1\");\n    byte[] bHll2 = SafeEncoder.encode(\"hll2\");\n    byte[] bHll3 = SafeEncoder.encode(\"hll3\");\n    byte[] bFoo = SafeEncoder.encode(\"foo\");\n    byte[] bBar = SafeEncoder.encode(\"bar\");\n    byte[] bZap = SafeEncoder.encode(\"zap\");\n    byte[] bA = SafeEncoder.encode(\"a\");\n    byte[] bB = SafeEncoder.encode(\"b\");\n    byte[] bC = SafeEncoder.encode(\"c\");\n\n    long status = jedis.pfadd(bHll1, bFoo, bBar, bZap, bA);\n    assertEquals(1, status);\n\n    status = jedis.pfadd(bHll2, bA, bB, bC, bFoo);\n    assertEquals(1, status);\n\n    String mergeStatus = jedis.pfmerge(bHll3, bHll1, bHll2);\n    assertEquals(\"OK\", mergeStatus);\n\n    status = jedis.pfcount(bHll3);\n    assertEquals(6, status);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/ListCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Timeout;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\npublic abstract class ListCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  private final Logger logger = LoggerFactory.getLogger(getClass());\n\n  protected final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  protected final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x05 };\n  protected final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  protected final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  protected final byte[] bA = { 0x0A };\n  protected final byte[] bB = { 0x0B };\n  protected final byte[] bC = { 0x0C };\n  protected final byte[] b1 = { 0x01 };\n  protected final byte[] b2 = { 0x02 };\n  protected final byte[] b3 = { 0x03 };\n  protected final byte[] bhello = { 0x04, 0x02 };\n  protected final byte[] bx = { 0x02, 0x04 };\n  protected final byte[] bdst = { 0x11, 0x12, 0x13, 0x14 };\n\n  public ListCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void rpush() {\n    assertEquals(1, jedis.rpush(\"foo\", \"bar\"));\n    assertEquals(2, jedis.rpush(\"foo\", \"foo\"));\n    assertEquals(4, jedis.rpush(\"foo\", \"bar\", \"foo\"));\n\n    // Binary\n    assertEquals(1, jedis.rpush(bfoo, bbar));\n    assertEquals(2, jedis.rpush(bfoo, bfoo));\n    assertEquals(4, jedis.rpush(bfoo, bbar, bfoo));\n  }\n\n  @Test\n  public void lpush() {\n    assertEquals(1, jedis.lpush(\"foo\", \"bar\"));\n    assertEquals(2, jedis.lpush(\"foo\", \"foo\"));\n    assertEquals(4, jedis.lpush(\"foo\", \"bar\", \"foo\"));\n\n    // Binary\n    assertEquals(1, jedis.lpush(bfoo, bbar));\n    assertEquals(2, jedis.lpush(bfoo, bfoo));\n    assertEquals(4, jedis.lpush(bfoo, bbar, bfoo));\n  }\n\n  @Test\n  public void llen() {\n    assertEquals(0, jedis.llen(\"foo\"));\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo\", \"car\");\n    assertEquals(2, jedis.llen(\"foo\"));\n\n    // Binary\n    assertEquals(0, jedis.llen(bfoo));\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo, bcar);\n    assertEquals(2, jedis.llen(bfoo));\n\n  }\n\n  @Test\n  public void llenNotOnList() {\n    try {\n      jedis.set(\"foo\", \"bar\");\n      jedis.llen(\"foo\");\n      fail(\"JedisDataException expected\");\n    } catch (final JedisDataException e) {\n    }\n\n    // Binary\n    try {\n      jedis.set(bfoo, bbar);\n      jedis.llen(bfoo);\n      fail(\"JedisDataException expected\");\n    } catch (final JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void lrange() {\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    List<String> range = jedis.lrange(\"foo\", 0, 2);\n    assertEquals(expected, range);\n\n    range = jedis.lrange(\"foo\", 0, 20);\n    assertEquals(expected, range);\n\n    expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    range = jedis.lrange(\"foo\", 1, 2);\n    assertEquals(expected, range);\n\n    range = jedis.lrange(\"foo\", 2, 1);\n    assertEquals(Collections.<String> emptyList(), range);\n\n    // Binary\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    bexpected.add(bC);\n\n    List<byte[]> brange = jedis.lrange(bfoo, 0, 2);\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.lrange(bfoo, 0, 20);\n    assertByteArrayListEquals(bexpected, brange);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bB);\n    bexpected.add(bC);\n\n    brange = jedis.lrange(bfoo, 1, 2);\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.lrange(bfoo, 2, 1);\n    assertByteArrayListEquals(Collections.<byte[]> emptyList(), brange);\n  }\n\n  @Test\n  public void ltrim() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n    String status = jedis.ltrim(\"foo\", 0, 1);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"3\");\n    expected.add(\"2\");\n\n    assertEquals(\"OK\", status);\n    assertEquals(2, jedis.llen(\"foo\"));\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n    String bstatus = jedis.ltrim(bfoo, 0, 1);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b3);\n    bexpected.add(b2);\n\n    assertEquals(\"OK\", bstatus);\n    assertEquals(2, jedis.llen(bfoo));\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void lset() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"3\");\n    expected.add(\"bar\");\n    expected.add(\"1\");\n\n    String status = jedis.lset(\"foo\", 1, \"bar\");\n\n    assertEquals(\"OK\", status);\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(b3);\n    bexpected.add(bbar);\n    bexpected.add(b1);\n\n    String bstatus = jedis.lset(bfoo, 1, bbar);\n\n    assertEquals(\"OK\", bstatus);\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void lindex() {\n    jedis.lpush(\"foo\", \"1\");\n    jedis.lpush(\"foo\", \"2\");\n    jedis.lpush(\"foo\", \"3\");\n\n    assertEquals(\"3\", jedis.lindex(\"foo\", 0));\n    assertNull(jedis.lindex(\"foo\", 100));\n\n    // Binary\n    jedis.lpush(bfoo, b1);\n    jedis.lpush(bfoo, b2);\n    jedis.lpush(bfoo, b3);\n\n    assertArrayEquals(b3, jedis.lindex(bfoo, 0));\n    assertNull(jedis.lindex(bfoo, 100));\n  }\n\n  @Test\n  public void lrem() {\n    jedis.lpush(\"foo\", \"hello\");\n    jedis.lpush(\"foo\", \"hello\");\n    jedis.lpush(\"foo\", \"x\");\n    jedis.lpush(\"foo\", \"hello\");\n    jedis.lpush(\"foo\", \"c\");\n    jedis.lpush(\"foo\", \"b\");\n    jedis.lpush(\"foo\", \"a\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n    expected.add(\"hello\");\n    expected.add(\"x\");\n\n    assertEquals(2, jedis.lrem(\"foo\", -2, \"hello\"));\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 1000));\n    assertEquals(0, jedis.lrem(\"bar\", 100, \"foo\"));\n\n    // Binary\n    jedis.lpush(bfoo, bhello);\n    jedis.lpush(bfoo, bhello);\n    jedis.lpush(bfoo, bx);\n    jedis.lpush(bfoo, bhello);\n    jedis.lpush(bfoo, bC);\n    jedis.lpush(bfoo, bB);\n    jedis.lpush(bfoo, bA);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n    bexpected.add(bC);\n    bexpected.add(bhello);\n    bexpected.add(bx);\n\n    assertEquals(2, jedis.lrem(bfoo, -2, bhello));\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 1000));\n    assertEquals(0, jedis.lrem(bbar, 100, bfoo));\n\n  }\n\n  @Test\n  public void lpop() {\n\n    assertNull(jedis.lpop(\"foo\"));\n    assertNull(jedis.lpop(\"foo\", 0));\n\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    assertEquals(\"a\", jedis.lpop(\"foo\"));\n    assertEquals(Arrays.asList(\"b\", \"c\"), jedis.lpop(\"foo\", 10));\n\n    assertNull(jedis.lpop(\"foo\"));\n    assertNull(jedis.lpop(\"foo\", 1));\n\n    // Binary\n\n    assertNull(jedis.lpop(bfoo));\n    assertNull(jedis.lpop(bfoo, 0));\n\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    assertArrayEquals(bA, jedis.lpop(bfoo));\n    assertByteArrayListEquals(Arrays.asList(bB, bC), jedis.lpop(bfoo, 10));\n\n    assertNull(jedis.lpop(bfoo));\n    assertNull(jedis.lpop(bfoo, 1));\n\n  }\n\n  @Test\n  public void rpop() {\n\n    assertNull(jedis.rpop(\"foo\"));\n    assertNull(jedis.rpop(\"foo\", 0));\n\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    assertEquals(\"c\", jedis.rpop(\"foo\"));\n    assertEquals(Arrays.asList(\"b\", \"a\"), jedis.rpop(\"foo\", 10));\n\n    assertNull(jedis.rpop(\"foo\"));\n    assertNull(jedis.rpop(\"foo\", 1));\n\n    // Binary\n\n    assertNull(jedis.rpop(bfoo));\n    assertNull(jedis.rpop(bfoo, 0));\n\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    assertArrayEquals(bC, jedis.rpop(bfoo));\n    assertByteArrayListEquals(Arrays.asList(bB, bA), jedis.rpop(bfoo, 10));\n\n    assertNull(jedis.rpop(bfoo));\n    assertNull(jedis.rpop(bfoo, 1));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void rpoplpush() {\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    jedis.rpush(\"dst\", \"foo\");\n    jedis.rpush(\"dst\", \"bar\");\n\n    String element = jedis.rpoplpush(\"foo\", \"dst\");\n\n    assertEquals(\"c\", element);\n\n    List<String> srcExpected = new ArrayList<String>();\n    srcExpected.add(\"a\");\n    srcExpected.add(\"b\");\n\n    List<String> dstExpected = new ArrayList<String>();\n    dstExpected.add(\"c\");\n    dstExpected.add(\"foo\");\n    dstExpected.add(\"bar\");\n\n    assertEquals(srcExpected, jedis.lrange(\"foo\", 0, 1000));\n    assertEquals(dstExpected, jedis.lrange(\"dst\", 0, 1000));\n\n    // Binary\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    jedis.rpush(bdst, bfoo);\n    jedis.rpush(bdst, bbar);\n\n    byte[] belement = jedis.rpoplpush(bfoo, bdst);\n\n    assertArrayEquals(bC, belement);\n\n    List<byte[]> bsrcExpected = new ArrayList<byte[]>();\n    bsrcExpected.add(bA);\n    bsrcExpected.add(bB);\n\n    List<byte[]> bdstExpected = new ArrayList<byte[]>();\n    bdstExpected.add(bC);\n    bdstExpected.add(bfoo);\n    bdstExpected.add(bbar);\n\n    assertByteArrayListEquals(bsrcExpected, jedis.lrange(bfoo, 0, 1000));\n    assertByteArrayListEquals(bdstExpected, jedis.lrange(bdst, 0, 1000));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blpop() throws InterruptedException {\n    List<String> result = jedis.blpop(1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.blpop(1, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo\", result.get(0));\n    assertEquals(\"bar\", result.get(1));\n\n    // Multi keys\n    result = jedis.blpop(1, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.blpop(1, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo1\", result.get(0));\n    assertEquals(\"bar1\", result.get(1));\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    List<byte[]> bresult = jedis.blpop(1, bfoo);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n\n    // Binary Multi keys\n    bresult = jedis.blpop(1, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.blpop(1, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blpopDouble() throws InterruptedException {\n    KeyValue<String, String> result = jedis.blpop(0.1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.blpop(3.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n\n    // Multi keys\n    result = jedis.blpop(0.18, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.blpop(1d, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo1\", result.getKey());\n    assertEquals(\"bar1\", result.getValue());\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    KeyValue<byte[], byte[]> bresult = jedis.blpop(3.12, bfoo);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n\n    // Binary Multi keys\n    bresult = jedis.blpop(0.11, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.blpop(1d, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n  }\n\n  @Test\n  @Timeout(5)\n  public void blpopDoubleWithSleep() {\n    KeyValue<String, String> result = jedis.blpop(0.04, \"foo\");\n    assertNull(result);\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(30);\n      } catch(InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      jedis.lpush(\"foo\", \"bar\");\n    }).start();\n    result = jedis.blpop(1.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpop() throws InterruptedException {\n    List<String> result = jedis.brpop(1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.brpop(1, \"foo\");\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo\", result.get(0));\n    assertEquals(\"bar\", result.get(1));\n\n    // Multi keys\n    result = jedis.brpop(1, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.brpop(1, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo1\", result.get(0));\n    assertEquals(\"bar1\", result.get(1));\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    List<byte[]> bresult = jedis.brpop(1, bfoo);\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n\n    // Binary Multi keys\n    bresult = jedis.brpop(1, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.brpop(1, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpopDouble() throws InterruptedException {\n    KeyValue<String, String> result = jedis.brpop(0.1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.brpop(3.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n\n    // Multi keys\n    result = jedis.brpop(0.18, \"foo\", \"foo1\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    jedis.lpush(\"foo1\", \"bar1\");\n    result = jedis.brpop(1d, \"foo1\", \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo1\", result.getKey());\n    assertEquals(\"bar1\", result.getValue());\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    KeyValue<byte[], byte[]> bresult = jedis.brpop(3.12, bfoo);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n\n    // Binary Multi keys\n    bresult = jedis.brpop(0.11, bfoo, bfoo1);\n    assertNull(bresult);\n\n    jedis.lpush(bfoo, bbar);\n    jedis.lpush(bfoo1, bcar);\n    bresult = jedis.brpop(1d, bfoo, bfoo1);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n  }\n\n  @Test\n  @Timeout(5)\n  public void brpopDoubleWithSleep() {\n    KeyValue<String, String> result = jedis.brpop(0.04, \"foo\");\n    assertNull(result);\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(30);\n      } catch(InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      jedis.lpush(\"foo\", \"bar\");\n    }).start();\n    result = jedis.brpop(1.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n  }\n\n  @Test\n  public void lpushx() {\n    assertEquals(0, jedis.lpushx(\"foo\", \"bar\"));\n\n    jedis.lpush(\"foo\", \"a\");\n    assertEquals(2, jedis.lpushx(\"foo\", \"b\"));\n\n    // Binary\n    assertEquals(0, jedis.lpushx(bfoo, bbar));\n\n    jedis.lpush(bfoo, bA);\n    assertEquals(2, jedis.lpushx(bfoo, bB));\n  }\n\n  @Test\n  public void rpushx() {\n    assertEquals(0, jedis.rpushx(\"foo\", \"bar\"));\n\n    jedis.lpush(\"foo\", \"a\");\n    assertEquals(2, jedis.rpushx(\"foo\", \"b\"));\n\n    // Binary\n    assertEquals(0, jedis.rpushx(bfoo, bbar));\n\n    jedis.lpush(bfoo, bA);\n    assertEquals(2, jedis.rpushx(bfoo, bB));\n  }\n\n  @Test\n  public void linsert() {\n    assertEquals(0, jedis.linsert(\"foo\", ListPosition.BEFORE, \"bar\", \"car\"));\n\n    jedis.lpush(\"foo\", \"a\");\n    assertEquals(2, jedis.linsert(\"foo\", ListPosition.AFTER, \"a\", \"b\"));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.lrange(\"foo\", 0, 100));\n\n    assertEquals(-1, jedis.linsert(\"foo\", ListPosition.BEFORE, \"bar\", \"car\"));\n\n    // Binary\n    assertEquals(0, jedis.linsert(bfoo, ListPosition.BEFORE, bbar, bcar));\n\n    jedis.lpush(bfoo, bA);\n    assertEquals(2, jedis.linsert(bfoo, ListPosition.AFTER, bA, bB));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bA);\n    bexpected.add(bB);\n\n    assertByteArrayListEquals(bexpected, jedis.lrange(bfoo, 0, 100));\n\n    assertEquals(-1, jedis.linsert(bfoo, ListPosition.BEFORE, bbar, bcar));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpoplpush() {\n\n    new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          Thread.sleep(100);\n        } catch (InterruptedException e) {\n          logger.error(\"\", e);\n        }\n        jedis.lpush(\"foo\", \"a\");\n      }\n    }).start();\n\n    String element = jedis.brpoplpush(\"foo\", \"bar\", 0);\n\n    assertEquals(\"a\", element);\n    assertEquals(1, jedis.llen(\"bar\"));\n    assertEquals(\"a\", jedis.lrange(\"bar\", 0, -1).get(0));\n\n    // Binary\n\n    new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          Thread.sleep(100);\n        } catch (InterruptedException e) {\n          logger.error(\"\", e);\n        }\n        jedis.lpush(bfoo, bA);\n      }\n    }).start();\n\n    byte[] belement = jedis.brpoplpush(bfoo, bbar, 0);\n\n    assertArrayEquals(bA, belement);\n    assertEquals(1, jedis.llen(\"bar\"));\n    assertArrayEquals(bA, jedis.lrange(bbar, 0, -1).get(0));\n  }\n\n  @Test\n  public void lpos() {\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"c\");\n\n    Long pos = jedis.lpos(\"foo\", \"b\");\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"d\");\n    assertNull(pos);\n\n    jedis.rpush(\"foo\", \"a\");\n    jedis.rpush(\"foo\", \"b\");\n    jedis.rpush(\"foo\", \"b\");\n\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams());\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(3));\n    assertEquals(5, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2));\n    assertEquals(4, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-5));\n    assertNull(pos);\n\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(1).maxlen(2));\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2).maxlen(2));\n    assertNull(pos);\n    pos = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2).maxlen(2));\n    assertEquals(4, pos.intValue());\n\n    List<Long> expected = new ArrayList<Long>();\n    expected.add(1L);\n    expected.add(4L);\n    expected.add(5L);\n    List<Long> posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams(), 2);\n    assertEquals(expected.subList(0, 2), posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams(), 0);\n    assertEquals(expected, posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2), 0);\n    assertEquals(expected.subList(1, 3), posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2).maxlen(5), 0);\n    assertEquals(expected.subList(1, 2), posList);\n\n    Collections.reverse(expected);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2), 0);\n    assertEquals(expected.subList(1, 3), posList);\n    posList = jedis.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-1).maxlen(5), 2);\n    assertEquals(expected.subList(0, 2), posList);\n\n    // Binary\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bC);\n\n    pos = jedis.lpos(bfoo, bB);\n    assertEquals(1, pos.intValue());\n    pos = jedis.lpos(bfoo, b3);\n    assertNull(pos);\n\n    jedis.rpush(bfoo, bA);\n    jedis.rpush(bfoo, bB);\n    jedis.rpush(bfoo, bA);\n\n    pos = jedis.lpos(bfoo, bB, LPosParams.lPosParams().rank(2));\n    assertEquals(4, pos.intValue());\n    pos = jedis.lpos(bfoo, bB, LPosParams.lPosParams().rank(-2).maxlen(5));\n    assertEquals(1, pos.intValue());\n\n    expected.clear();\n    expected.add(0L);\n    expected.add(3L);\n    expected.add(5L);\n\n    posList = jedis.lpos(bfoo, bA, LPosParams.lPosParams().maxlen(6), 0);\n    assertEquals(expected, posList);\n    posList = jedis.lpos(bfoo, bA, LPosParams.lPosParams().maxlen(6).rank(2), 1);\n    assertEquals(expected.subList(1, 2), posList);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lmove() {\n    jedis.rpush(\"foo\", \"bar1\", \"bar2\", \"bar3\");\n    assertEquals(\"bar3\", jedis.lmove(\"foo\", \"bar\", ListDirection.RIGHT, ListDirection.LEFT));\n    assertEquals(Collections.singletonList(\"bar3\"), jedis.lrange(\"bar\", 0, -1));\n    assertEquals(Arrays.asList(\"bar1\", \"bar2\"), jedis.lrange(\"foo\", 0, -1));\n\n    // Binary\n    jedis.rpush(bfoo, b1, b2, b3);\n    assertArrayEquals(b3, jedis.lmove(bfoo, bbar, ListDirection.RIGHT, ListDirection.LEFT));\n    assertByteArrayListEquals(Collections.singletonList(b3), jedis.lrange(bbar, 0, -1));\n    assertByteArrayListEquals(Arrays.asList(b1, b2), jedis.lrange(bfoo, 0, -1));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blmove() {\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      jedis.rpush(\"foo\", \"bar1\", \"bar2\", \"bar3\");\n    }).start();\n\n    assertEquals(\"bar3\", jedis.blmove(\"foo\", \"bar\", ListDirection.RIGHT, ListDirection.LEFT, 0));\n    assertEquals(Collections.singletonList(\"bar3\"), jedis.lrange(\"bar\", 0, -1));\n    assertEquals(Arrays.asList(\"bar1\", \"bar2\"), jedis.lrange(\"foo\", 0, -1));\n\n    // Binary\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      jedis.rpush(bfoo, b1, b2, b3);\n    }).start();\n    assertArrayEquals(b3, jedis.blmove(bfoo, bbar, ListDirection.RIGHT, ListDirection.LEFT, 0));\n    assertByteArrayListEquals(Collections.singletonList(b3), jedis.lrange(bbar, 0, -1));\n    assertByteArrayListEquals(Arrays.asList(b1, b2), jedis.lrange(bfoo, 0, -1));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lmpop() {\n    String mylist1 = \"mylist1\";\n    String mylist2 = \"mylist2\";\n\n    // add elements to list\n    jedis.lpush(mylist1, \"one\", \"two\", \"three\", \"four\", \"five\");\n    jedis.lpush(mylist2, \"one\", \"two\", \"three\", \"four\", \"five\");\n\n    KeyValue<String, List<String>> elements = jedis.lmpop(ListDirection.LEFT, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(1, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.LEFT, 5, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(4, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.RIGHT, 100, mylist1, mylist2);\n    assertEquals(mylist2, elements.getKey());\n    assertEquals(5, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.RIGHT, mylist1, mylist2);\n    assertNull(elements);\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blmpopSimple() {\n    String mylist1 = \"mylist1\";\n    String mylist2 = \"mylist2\";\n\n    // add elements to list\n    jedis.lpush(mylist1, \"one\", \"two\", \"three\", \"four\", \"five\");\n    jedis.lpush(mylist2, \"one\", \"two\", \"three\", \"four\", \"five\");\n\n    KeyValue<String, List<String>> elements = jedis.blmpop(1L, ListDirection.LEFT, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(1, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.LEFT, 5, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(4, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.RIGHT, 100, mylist1, mylist2);\n    assertEquals(mylist2, elements.getKey());\n    assertEquals(5, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.RIGHT, mylist1, mylist2);\n    assertNull(elements);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/SetCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayCollectionContainsAll;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArraySetEquals;\nimport static redis.clients.jedis.util.AssertUtil.assertCollectionContainsAll;\nimport static redis.clients.jedis.util.ByteArrayUtil.byteArrayCollectionRemoveAll;\n\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\npublic abstract class SetCommandsTestBase extends UnifiedJedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bd = { 0x0D };\n  final byte[] bx = { 0x42 };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public SetCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void sadd() {\n    long status = jedis.sadd(\"foo\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.sadd(\"foo\", \"a\");\n    assertEquals(0, status);\n\n    long bstatus = jedis.sadd(bfoo, ba);\n    assertEquals(1, bstatus);\n\n    bstatus = jedis.sadd(bfoo, ba);\n    assertEquals(0, bstatus);\n\n  }\n\n  @Test\n  public void smembers() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n\n    Set<String> members = jedis.smembers(\"foo\");\n\n    assertEquals(expected, members);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n\n    Set<byte[]> bmembers = jedis.smembers(bfoo);\n\n    assertByteArraySetEquals(bexpected, bmembers);\n  }\n\n  @Test\n  public void srem() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    long status = jedis.srem(\"foo\", \"a\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"b\");\n\n    assertEquals(1, status);\n    assertEquals(expected, jedis.smembers(\"foo\"));\n\n    status = jedis.srem(\"foo\", \"bar\");\n\n    assertEquals(0, status);\n\n    // Binary\n\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    long bstatus = jedis.srem(bfoo, ba);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n\n    assertEquals(1, bstatus);\n    assertByteArraySetEquals(bexpected, jedis.smembers(bfoo));\n\n    bstatus = jedis.srem(bfoo, bbar);\n\n    assertEquals(0, bstatus);\n\n  }\n\n  @Test\n  public void spop() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    String member = jedis.spop(\"foo\");\n\n    assertTrue(\"a\".equals(member) || \"b\".equals(member));\n    assertEquals(1, jedis.smembers(\"foo\").size());\n\n    member = jedis.spop(\"bar\");\n    assertNull(member);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    byte[] bmember = jedis.spop(bfoo);\n\n    assertTrue(Arrays.equals(ba, bmember) || Arrays.equals(bb, bmember));\n    assertEquals(1, jedis.smembers(bfoo).size());\n\n    bmember = jedis.spop(bbar);\n    assertNull(bmember);\n\n  }\n\n  @Test\n  public void spopWithCount() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n    jedis.sadd(\"foo\", \"c\");\n\n    Set<String> superSet = new HashSet<String>();\n    superSet.add(\"c\");\n    superSet.add(\"b\");\n    superSet.add(\"a\");\n\n    Set<String> members = jedis.spop(\"foo\", 2);\n\n    assertEquals(2, members.size());\n    assertCollectionContainsAll(superSet, members);\n    superSet.removeAll(members);\n\n    members = jedis.spop(\"foo\", 2);\n    assertEquals(1, members.size());\n    assertEquals(superSet, members);\n\n    assertTrue(jedis.spop(\"foo\", 2).isEmpty());\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n    jedis.sadd(bfoo, bc);\n\n    Set<byte[]> bsuperSet = new HashSet<byte[]>();\n    bsuperSet.add(bc);\n    bsuperSet.add(bb);\n    bsuperSet.add(ba);\n\n    Set<byte[]> bmembers = jedis.spop(bfoo, 2);\n\n    assertEquals(2, bmembers.size());\n    assertByteArrayCollectionContainsAll(bsuperSet, bmembers);\n    byteArrayCollectionRemoveAll(bsuperSet, bmembers);\n\n    bmembers = jedis.spop(bfoo, 2);\n    assertEquals(1, bmembers.size());\n    assertByteArraySetEquals(bsuperSet, bmembers);\n\n    assertTrue(jedis.spop(bfoo, 2).isEmpty());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void smove() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"c\");\n\n    long status = jedis.smove(\"foo\", \"bar\", \"a\");\n\n    Set<String> expectedSrc = new HashSet<String>();\n    expectedSrc.add(\"b\");\n\n    Set<String> expectedDst = new HashSet<String>();\n    expectedDst.add(\"c\");\n    expectedDst.add(\"a\");\n\n    assertEquals(status, 1);\n    assertEquals(expectedSrc, jedis.smembers(\"foo\"));\n    assertEquals(expectedDst, jedis.smembers(\"bar\"));\n\n    status = jedis.smove(\"foo\", \"bar\", \"a\");\n\n    assertEquals(status, 0);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bc);\n\n    long bstatus = jedis.smove(bfoo, bbar, ba);\n\n    Set<byte[]> bexpectedSrc = new HashSet<byte[]>();\n    bexpectedSrc.add(bb);\n\n    Set<byte[]> bexpectedDst = new HashSet<byte[]>();\n    bexpectedDst.add(bc);\n    bexpectedDst.add(ba);\n\n    assertEquals(bstatus, 1);\n    assertByteArraySetEquals(bexpectedSrc, jedis.smembers(bfoo));\n    assertByteArraySetEquals(bexpectedDst, jedis.smembers(bbar));\n\n    bstatus = jedis.smove(bfoo, bbar, ba);\n    assertEquals(bstatus, 0);\n\n  }\n\n  @Test\n  public void scard() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    long card = jedis.scard(\"foo\");\n\n    assertEquals(2, card);\n\n    card = jedis.scard(\"bar\");\n    assertEquals(0, card);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    long bcard = jedis.scard(bfoo);\n\n    assertEquals(2, bcard);\n\n    bcard = jedis.scard(bbar);\n    assertEquals(0, bcard);\n\n  }\n\n  @Test\n  public void sismember() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    assertTrue(jedis.sismember(\"foo\", \"a\"));\n\n    assertFalse(jedis.sismember(\"foo\", \"c\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    assertTrue(jedis.sismember(bfoo, ba));\n\n    assertFalse(jedis.sismember(bfoo, bc));\n\n  }\n\n  @Test\n  public void smismember() {\n    jedis.sadd(\"foo\", \"a\", \"b\");\n\n    assertEquals(Arrays.asList(true, false), jedis.smismember(\"foo\", \"a\", \"c\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba, bb);\n\n    assertEquals(Arrays.asList(true, false), jedis.smismember(bfoo, ba, bc));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sinter() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"b\");\n\n    Set<String> intersection = jedis.sinter(\"foo\", \"bar\");\n    assertEquals(expected, intersection);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n\n    Set<byte[]> bintersection = jedis.sinter(bfoo, bbar);\n    assertByteArraySetEquals(bexpected, bintersection);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sinterstore() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"b\");\n\n    long status = jedis.sinterstore(\"car\", \"foo\", \"bar\");\n    assertEquals(1, status);\n\n    assertEquals(expected, jedis.smembers(\"car\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n\n    long bstatus = jedis.sinterstore(bcar, bfoo, bbar);\n    assertEquals(1, bstatus);\n\n    assertByteArraySetEquals(bexpected, jedis.smembers(bcar));\n\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sintercard() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"a\");\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    long card = jedis.sintercard(\"foo\", \"bar\");\n    assertEquals(2, card);\n    long limitedCard = jedis.sintercard(1, \"foo\", \"bar\");\n    assertEquals(1, limitedCard);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, ba);\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    long bcard = jedis.sintercard(bfoo, bbar);\n    assertEquals(2, bcard);\n    long blimitedCard = jedis.sintercard(1, bfoo, bbar);\n    assertEquals(1, blimitedCard);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sunion() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    Set<String> union = jedis.sunion(\"foo\", \"bar\");\n    assertEquals(expected, union);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    Set<byte[]> bunion = jedis.sunion(bfoo, bbar);\n    assertByteArraySetEquals(bexpected, bunion);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sunionstore() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    jedis.sadd(\"bar\", \"b\");\n    jedis.sadd(\"bar\", \"c\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    long status = jedis.sunionstore(\"car\", \"foo\", \"bar\");\n    assertEquals(3, status);\n\n    assertEquals(expected, jedis.smembers(\"car\"));\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bbar, bb);\n    jedis.sadd(bbar, bc);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    long bstatus = jedis.sunionstore(bcar, bfoo, bbar);\n    assertEquals(3, bstatus);\n\n    assertByteArraySetEquals(bexpected, jedis.smembers(bcar));\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sdiff() {\n    jedis.sadd(\"foo\", \"x\");\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n    jedis.sadd(\"foo\", \"c\");\n\n    jedis.sadd(\"bar\", \"c\");\n\n    jedis.sadd(\"car\", \"a\");\n    jedis.sadd(\"car\", \"d\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"x\");\n    expected.add(\"b\");\n\n    Set<String> diff = jedis.sdiff(\"foo\", \"bar\", \"car\");\n    assertEquals(expected, diff);\n\n    // Binary\n    jedis.sadd(bfoo, bx);\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n    jedis.sadd(bfoo, bc);\n\n    jedis.sadd(bbar, bc);\n\n    jedis.sadd(bcar, ba);\n    jedis.sadd(bcar, bd);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(bx);\n\n    Set<byte[]> bdiff = jedis.sdiff(bfoo, bbar, bcar);\n    assertByteArraySetEquals(bexpected, bdiff);\n\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sdiffstore() {\n    jedis.sadd(\"foo\", \"x\");\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n    jedis.sadd(\"foo\", \"c\");\n\n    jedis.sadd(\"bar\", \"c\");\n\n    jedis.sadd(\"car\", \"a\");\n    jedis.sadd(\"car\", \"d\");\n\n    Set<String> expected = new HashSet<String>();\n    expected.add(\"x\");\n    expected.add(\"b\");\n\n    long status = jedis.sdiffstore(\"tar\", \"foo\", \"bar\", \"car\");\n    assertEquals(2, status);\n    assertEquals(expected, jedis.smembers(\"tar\"));\n\n    // Binary\n    jedis.sadd(bfoo, bx);\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n    jedis.sadd(bfoo, bc);\n\n    jedis.sadd(bbar, bc);\n\n    jedis.sadd(bcar, ba);\n    jedis.sadd(bcar, bd);\n\n    Set<byte[]> bexpected = new HashSet<byte[]>();\n    bexpected.add(bx);\n    bexpected.add(bb);\n\n    long bstatus = jedis.sdiffstore(\"tar\".getBytes(), bfoo, bbar, bcar);\n    assertEquals(2, bstatus);\n    assertByteArraySetEquals(bexpected, jedis.smembers(\"tar\".getBytes()));\n\n  }\n\n  @Test\n  public void srandmember() {\n    jedis.sadd(\"foo\", \"a\");\n    jedis.sadd(\"foo\", \"b\");\n\n    String member = jedis.srandmember(\"foo\");\n\n    assertTrue(\"a\".equals(member) || \"b\".equals(member));\n    assertEquals(2, jedis.smembers(\"foo\").size());\n\n    List<String> members = jedis.srandmember(\"foo\", 2);\n    members.sort(Comparator.naturalOrder());\n    assertEquals( Arrays.asList(\"a\", \"b\"), members);\n\n    member = jedis.srandmember(\"bar\");\n    assertNull(member);\n\n    members = jedis.srandmember(\"bar\", 2);\n    assertEquals(0, members.size());\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    byte[] bmember = jedis.srandmember(bfoo);\n\n    assertTrue(Arrays.equals(ba, bmember) || Arrays.equals(bb, bmember));\n    assertEquals(2, jedis.smembers(bfoo).size());\n\n    List<byte[]> bmembers = jedis.srandmember(bfoo, 2);\n    assertEquals(2, bmembers.size());\n\n    bmember = jedis.srandmember(bbar);\n    assertNull(bmember);\n\n    members = jedis.srandmember(\"bbar\", 2);\n    assertEquals(0, members.size());\n  }\n\n  @Test\n  public void sscan() {\n    jedis.sadd(\"foo\", \"a\", \"b\");\n\n    ScanResult<String> result = jedis.sscan(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    jedis.sadd(bfoo, ba, bb);\n\n    ScanResult<byte[]> bResult = jedis.sscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void sscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.sadd(\"foo\", \"b\", \"a\", \"aa\");\n    ScanResult<String> result = jedis.sscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.sadd(bfoo, bbar1, bbar2, bbar3);\n    ScanResult<byte[]> bResult = jedis.sscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void sscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    jedis.sadd(\"foo\", \"a1\", \"a2\", \"a3\", \"a4\", \"a5\");\n\n    ScanResult<String> result = jedis.sscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.sadd(bfoo, bbar1, bbar2, bbar3);\n    ScanResult<byte[]> bResult = jedis.sscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/SortedSetCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.*;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\npublic abstract class SortedSetCommandsTestBase extends UnifiedJedisCommandsTestBase {\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bInclusiveB = { 0x5B, 0x0B };\n  final byte[] bExclusiveC = { 0x28, 0x0C };\n  final byte[] bLexMinusInf = { 0x2D };\n  final byte[] bLexPlusInf = { 0x2B };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public SortedSetCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void zadd() {\n    assertEquals(1, jedis.zadd(\"foo\", 1d, \"a\"));\n\n    assertEquals(1, jedis.zadd(\"foo\", 10d, \"b\"));\n\n    assertEquals(1, jedis.zadd(\"foo\", 0.1d, \"c\"));\n\n    assertEquals(0, jedis.zadd(\"foo\", 2d, \"a\"));\n\n    // Binary\n    assertEquals(1, jedis.zadd(bfoo, 1d, ba));\n\n    assertEquals(1, jedis.zadd(bfoo, 10d, bb));\n\n    assertEquals(1, jedis.zadd(bfoo, 0.1d, bc));\n\n    assertEquals(0, jedis.zadd(bfoo, 2d, ba));\n  }\n\n  @Test\n  public void zaddWithParams() {\n    jedis.del(\"foo\");\n\n    // xx: never add new member\n    assertEquals(0L, jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().xx()));\n\n    jedis.zadd(\"foo\", 1d, \"a\");\n    // nx: never update current member\n    assertEquals(0L, jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(1d), jedis.zscore(\"foo\", \"a\"));\n\n    Map<String, Double> scoreMembers = new HashMap<String, Double>();\n    scoreMembers.put(\"a\", 2d);\n    scoreMembers.put(\"b\", 1d);\n    // ch: return count of members not only added, but also updated\n    assertEquals(2L, jedis.zadd(\"foo\", scoreMembers, ZAddParams.zAddParams().ch()));\n\n    // lt: only update existing elements if the new score is less than the current score.\n    jedis.zadd(\"foo\", 3d, \"a\", ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"a\"));\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(\"foo\", \"a\"));\n\n    // gt: only update existing elements if the new score is greater than the current score.\n    jedis.zadd(\"foo\", 0d, \"b\", ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(\"foo\", \"b\"));\n    jedis.zadd(\"foo\", 2d, \"b\", ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"b\"));\n\n    // incr: don't update already existing elements.\n    assertNull(jedis.zaddIncr(\"foo\", 1d, \"b\", ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"b\"));\n    // incr: update elements that already exist.\n    assertEquals(Double.valueOf(3d), jedis.zaddIncr(\"foo\", 1d,\"b\", ZAddParams.zAddParams().xx()));\n    assertEquals(Double.valueOf(3d), jedis.zscore(\"foo\", \"b\"));\n\n    // binary\n    jedis.del(bfoo);\n\n    // xx: never add new member\n    assertEquals(0L, jedis.zadd(bfoo, 1d, ba, ZAddParams.zAddParams().xx()));\n\n    jedis.zadd(bfoo, 1d, ba);\n    // nx: never update current member\n    assertEquals(0L, jedis.zadd(bfoo, 2d, ba, ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(1d), jedis.zscore(bfoo, ba));\n\n    Map<byte[], Double> binaryScoreMembers = new HashMap<byte[], Double>();\n    binaryScoreMembers.put(ba, 2d);\n    binaryScoreMembers.put(bb, 1d);\n    // ch: return count of members not only added, but also updated\n    assertEquals(2L, jedis.zadd(bfoo, binaryScoreMembers, ZAddParams.zAddParams().ch()));\n\n    // lt: only update existing elements if the new score is less than the current score.\n    jedis.zadd(bfoo, 3d, ba, ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, ba));\n    jedis.zadd(bfoo, 1d, ba, ZAddParams.zAddParams().lt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(bfoo, ba));\n\n    // gt: only update existing elements if the new score is greater than the current score.\n    jedis.zadd(bfoo, 0d, bb, ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(1d), jedis.zscore(bfoo, bb));\n    jedis.zadd(bfoo, 2d, bb, ZAddParams.zAddParams().gt());\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, bb));\n\n    // incr: don't update already existing elements.\n    assertNull(jedis.zaddIncr(bfoo, 1d, bb, ZAddParams.zAddParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, bb));\n    // incr: update elements that already exist.\n    assertEquals(Double.valueOf(3d), jedis.zaddIncr(bfoo, 1d, bb, ZAddParams.zAddParams().xx()));\n    assertEquals(Double.valueOf(3d), jedis.zscore(bfoo, bb));\n  }\n\n  @Test\n  public void zrange() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"a\");\n\n    List<String> range = jedis.zrange(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(\"b\");\n    range = jedis.zrange(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    List<byte[]> brange = jedis.zrange(bfoo, 0, 1);\n    assertByteArrayListEquals(bexpected, brange);\n\n    bexpected.add(bb);\n    brange = jedis.zrange(bfoo, 0, 100);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrangeByLex() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"bb\");\n    jedis.zadd(\"foo\", 1, \"d\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"bb\");\n    expected.add(\"c\");\n\n    // exclusive aa ~ inclusive c\n    assertEquals(expected, jedis.zrangeByLex(\"foo\", \"(aa\", \"[c\"));\n\n    expected.clear();\n    expected.add(\"bb\");\n    expected.add(\"c\");\n\n    // with LIMIT\n    assertEquals(expected, jedis.zrangeByLex(\"foo\", \"-\", \"+\", 1, 2));\n  }\n\n  @Test\n  public void zrangeByLexBinary() {\n    // binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    List<byte[]> bExpected = new ArrayList<byte[]>();\n    bExpected.add(bb);\n\n    assertByteArrayListEquals(bExpected, jedis.zrangeByLex(bfoo, bInclusiveB, bExclusiveC));\n\n    bExpected.clear();\n    bExpected.add(ba);\n    bExpected.add(bb);\n\n    // with LIMIT\n    assertByteArrayListEquals(bExpected, jedis.zrangeByLex(bfoo, bLexMinusInf, bLexPlusInf, 0, 2));\n  }\n\n  @Test\n  public void zrevrangeByLex() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"bb\");\n    jedis.zadd(\"foo\", 1, \"d\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"bb\");\n\n    // exclusive aa ~ inclusive c\n    assertEquals(expected, jedis.zrevrangeByLex(\"foo\", \"[c\", \"(aa\"));\n\n    expected.clear();\n    expected.add(\"c\");\n    expected.add(\"bb\");\n\n    // with LIMIT\n    assertEquals(expected, jedis.zrevrangeByLex(\"foo\", \"+\", \"-\", 1, 2));\n  }\n\n  @Test\n  public void zrevrangeByLexBinary() {\n    // binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    List<byte[]> bExpected = new ArrayList<byte[]>();\n    bExpected.add(bb);\n\n    assertByteArrayListEquals(bExpected, jedis.zrevrangeByLex(bfoo, bExclusiveC, bInclusiveB));\n\n    bExpected.clear();\n    bExpected.add(bc);\n    bExpected.add(bb);\n\n    // with LIMIT\n    assertByteArrayListEquals(bExpected, jedis.zrevrangeByLex(bfoo, bLexPlusInf, bLexMinusInf, 0, 2));\n  }\n\n  @Test\n  public void zrevrange() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"a\");\n\n    List<String> range = jedis.zrevrange(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(\"c\");\n    range = jedis.zrevrange(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n\n    List<byte[]> brange = jedis.zrevrange(bfoo, 0, 1);\n    assertByteArrayListEquals(bexpected, brange);\n\n    bexpected.add(bc);\n    brange = jedis.zrevrange(bfoo, 0, 100);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrangeParams() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"bb\");\n    jedis.zadd(\"foo\", 1, \"d\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"bb\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", ZRangeParams.zrangeByLexParams(\"[c\", \"(aa\").rev()));\n    assertNotNull(jedis.zrangeWithScores(\"foo\", ZRangeParams.zrangeByScoreParams(0, 1)));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    List<byte[]> bExpected = new ArrayList<byte[]>();\n    bExpected.add(bb);\n\n    assertByteArrayListEquals(bExpected, jedis.zrange(bfoo, ZRangeParams.zrangeByLexParams(bExclusiveC, bInclusiveB).rev()));\n    assertNotNull(jedis.zrangeWithScores(bfoo, ZRangeParams.zrangeByScoreParams(0, 1).limit(0, 3)));\n  }\n\n  @Test\n  public void zrangeParamsLongMinMax() {\n\n    long min = 0;\n    long max = 1;\n\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"a\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", ZRangeParams.zrangeParams(min, max).rev()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zrangestore() {\n    jedis.zadd(\"foo\", 1, \"aa\");\n    jedis.zadd(\"foo\", 2, \"c\");\n    jedis.zadd(\"foo\", 3, \"bb\");\n\n    long stored = jedis.zrangestore(\"bar\", \"foo\", ZRangeParams.zrangeByScoreParams(1, 2));\n    assertEquals(2, stored);\n\n    List<String> range = jedis.zrange(\"bar\", 0, -1);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"aa\");\n    expected.add(\"c\");\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    long bstored = jedis.zrangestore(bbar, bfoo, ZRangeParams.zrangeParams(0, 1).rev());\n    assertEquals(2, bstored);\n\n    List<byte[]> brange = jedis.zrevrange(bbar, 0, 1);\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrem() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    assertEquals(1, jedis.zrem(\"foo\", \"a\"));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    assertEquals(0, jedis.zrem(\"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    assertEquals(1, jedis.zrem(bfoo, ba));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n\n    assertEquals(0, jedis.zrem(bfoo, bbar));\n  }\n\n  @Test\n  public void zincrby() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    assertEquals(3d, jedis.zincrby(\"foo\", 2d, \"a\"), 0);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n    expected.add(\"a\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    assertEquals(3d, jedis.zincrby(bfoo, 2d, ba), 0);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void zincrbyWithParams() {\n    jedis.del(\"foo\");\n\n    // xx: never add new member\n    assertNull(jedis.zincrby(\"foo\", 2d, \"a\", ZIncrByParams.zIncrByParams().xx()));\n\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    // nx: never update current member\n    assertNull(jedis.zincrby(\"foo\", 1d, \"a\", ZIncrByParams.zIncrByParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(\"foo\", \"a\"));\n\n    // Binary\n\n    jedis.del(bfoo);\n\n    // xx: never add new member\n    assertNull(jedis.zincrby(bfoo, 2d, ba, ZIncrByParams.zIncrByParams().xx()));\n\n    jedis.zadd(bfoo, 2d, ba);\n\n    // nx: never update current member\n    assertNull(jedis.zincrby(bfoo, 1d, ba, ZIncrByParams.zIncrByParams().nx()));\n    assertEquals(Double.valueOf(2d), jedis.zscore(bfoo, ba));\n  }\n\n  @Test\n  public void zrank() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    long rank = jedis.zrank(\"foo\", \"a\");\n    assertEquals(0, rank);\n\n    rank = jedis.zrank(\"foo\", \"b\");\n    assertEquals(1, rank);\n\n    assertNull(jedis.zrank(\"car\", \"b\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    long brank = jedis.zrank(bfoo, ba);\n    assertEquals(0, brank);\n\n    brank = jedis.zrank(bfoo, bb);\n    assertEquals(1, brank);\n\n    assertNull(jedis.zrank(bcar, bb));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.2.0\")\n  public void zrankWithScore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    KeyValue<Long, Double> keyValue = jedis.zrankWithScore(\"foo\", \"a\");\n    assertEquals(Long.valueOf(0), keyValue.getKey());\n    assertEquals(Double.valueOf(1d), keyValue.getValue());\n\n    keyValue = jedis.zrankWithScore(\"foo\", \"b\");\n    assertEquals(Long.valueOf(1), keyValue.getKey());\n    assertEquals(Double.valueOf(2d), keyValue.getValue());\n\n    assertNull(jedis.zrankWithScore(\"car\", \"b\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    keyValue = jedis.zrankWithScore(bfoo, ba);\n    assertEquals(Long.valueOf(0), keyValue.getKey());\n    assertEquals(Double.valueOf(1d), keyValue.getValue());\n\n    keyValue = jedis.zrankWithScore(bfoo, bb);\n    assertEquals(Long.valueOf(1), keyValue.getKey());\n    assertEquals(Double.valueOf(2d), keyValue.getValue());\n\n    assertNull(jedis.zrankWithScore(bcar, bb));\n  }\n\n  @Test\n  public void zrevrank() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    long rank = jedis.zrevrank(\"foo\", \"a\");\n    assertEquals(1, rank);\n\n    rank = jedis.zrevrank(\"foo\", \"b\");\n    assertEquals(0, rank);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 2d, bb);\n\n    long brank = jedis.zrevrank(bfoo, ba);\n    assertEquals(1, brank);\n\n    brank = jedis.zrevrank(bfoo, bb);\n    assertEquals(0, brank);\n  }\n\n  @Test\n  public void zrangeWithScores() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"a\", 2d));\n\n    List<Tuple> range = jedis.zrangeWithScores(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(new Tuple(\"b\", 10d));\n    range = jedis.zrangeWithScores(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    List<Tuple> brange = jedis.zrangeWithScores(bfoo, 0, 1);\n    assertEquals(bexpected, brange);\n\n    bexpected.add(new Tuple(bb, 10d));\n    brange = jedis.zrangeWithScores(bfoo, 0, 100);\n    assertEquals(bexpected, brange);\n\n  }\n\n  @Test\n  public void zrevrangeWithScores() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"b\", 10d));\n    expected.add(new Tuple(\"a\", 2d));\n\n    List<Tuple> range = jedis.zrevrangeWithScores(\"foo\", 0, 1);\n    assertEquals(expected, range);\n\n    expected.add(new Tuple(\"c\", 0.1d));\n    range = jedis.zrevrangeWithScores(\"foo\", 0, 100);\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bb, 10d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    List<Tuple> brange = jedis.zrevrangeWithScores(bfoo, 0, 1);\n    assertEquals(bexpected, brange);\n\n    bexpected.add(new Tuple(bc, 0.1d));\n    brange = jedis.zrevrangeWithScores(bfoo, 0, 100);\n    assertEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zcard() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(3, jedis.zcard(\"foo\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(3, jedis.zcard(bfoo));\n  }\n\n  @Test\n  public void zscore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals((Double) 10d, jedis.zscore(\"foo\", \"b\"));\n\n    assertEquals((Double) 0.1d, jedis.zscore(\"foo\", \"c\"));\n\n    assertNull(jedis.zscore(\"foo\", \"s\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals((Double) 10d, jedis.zscore(bfoo, bb));\n\n    assertEquals((Double) 0.1d, jedis.zscore(bfoo, bc));\n\n    assertNull(jedis.zscore(bfoo, SafeEncoder.encode(\"s\")));\n\n  }\n\n  @Test\n  public void zmscore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(Arrays.asList(10d, 0.1d, null), jedis.zmscore(\"foo\", \"b\", \"c\", \"s\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(Arrays.asList(10d, 0.1d, null),\n      jedis.zmscore(bfoo, bb, bc, SafeEncoder.encode(\"s\")));\n  }\n\n  @Test\n  public void zpopmax() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"d\");\n\n    Tuple actual = jedis.zpopmax(\"foo\");\n    Tuple expected = new Tuple(\"b\", 10d);\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\");\n    expected = new Tuple(\"d\", 2d);\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\");\n    expected = new Tuple(\"a\", 1d);\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\");\n    expected = new Tuple(\"c\", 0.1d);\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(\"foo\");\n    assertNull(actual);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    // First\n    actual = jedis.zpopmax(bfoo);\n    expected = new Tuple(bb, 10d);\n    assertEquals(expected, actual);\n\n    // Second\n    actual = jedis.zpopmax(bfoo);\n    expected = new Tuple(ba, 2d);\n    assertEquals(expected, actual);\n\n    // Third\n    actual = jedis.zpopmax(bfoo);\n    expected = new Tuple(bc, 0.1d);\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(bfoo);\n    assertNull(actual);\n  }\n\n  @Test\n  public void zpopmaxWithCount() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"d\");\n    jedis.zadd(\"foo\", 0.03, \"e\");\n\n    List<Tuple> actual = jedis.zpopmax(\"foo\", 2);\n    assertEquals(2, actual.size());\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"b\", 10d));\n    expected.add(new Tuple(\"d\", 2d));\n    assertEquals(expected, actual);\n\n    actual = jedis.zpopmax(\"foo\", 3);\n    assertEquals(3, actual.size());\n\n    expected.clear();\n    expected.add(new Tuple(\"a\", 1d));\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"e\", 0.03d));\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(\"foo\", 1);\n    expected.clear();\n    assertEquals(expected, actual);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    // First\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    expected.add(new Tuple(bb, 10d));\n    assertEquals(expected, actual);\n\n    // Second\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    expected.add(new Tuple(ba, 2d));\n    assertEquals(expected, actual);\n\n    // Last 2 (just 1, because 1 was overwritten)\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    expected.add(new Tuple(bc, 0.1d));\n    assertEquals(expected, actual);\n\n    // Empty\n    actual = jedis.zpopmax(bfoo, 1);\n    expected.clear();\n    assertEquals(expected, actual);\n  }\n\n  @Test\n  public void zpopmin() {\n\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    List<Tuple> range = jedis.zpopmin(\"foo\", 2);\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"a\", 1d));\n\n    assertEquals(expected, range);\n\n    assertEquals(new Tuple(\"b\", 10d), jedis.zpopmin(\"foo\"));\n\n    // Binary\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> brange = jedis.zpopmin(bfoo, 2);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n    assertEquals(new Tuple(bb, 10d), jedis.zpopmin(bfoo));\n  }\n\n  @Test\n  public void zcount() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(2, jedis.zcount(\"foo\", 0.01d, 2.1d));\n\n    assertEquals(3, jedis.zcount(\"foo\", \"(0.01\", \"+inf\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(2, jedis.zcount(bfoo, 0.01d, 2.1d));\n\n    assertEquals(3, jedis.zcount(bfoo, SafeEncoder.encode(\"(0.01\"), SafeEncoder.encode(\"+inf\")));\n  }\n\n  @Test\n  public void zlexcount() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 1, \"b\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"aa\");\n\n    assertEquals(2, jedis.zlexcount(\"foo\", \"[aa\", \"(c\"));\n\n    assertEquals(4, jedis.zlexcount(\"foo\", \"-\", \"+\"));\n\n    assertEquals(3, jedis.zlexcount(\"foo\", \"-\", \"(c\"));\n\n    assertEquals(3, jedis.zlexcount(\"foo\", \"[aa\", \"+\"));\n  }\n\n  @Test\n  public void zlexcountBinary() {\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    assertEquals(1, jedis.zlexcount(bfoo, bInclusiveB, bExclusiveC));\n\n    assertEquals(3, jedis.zlexcount(bfoo, bLexMinusInf, bLexPlusInf));\n  }\n\n  @Test\n  public void zrangebyscore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<String> range = jedis.zrangeByScore(\"foo\", 0d, 2d);\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"a\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScore(\"foo\", 0d, 2d, 0, 1);\n\n    expected = new ArrayList<String>();\n    expected.add(\"c\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScore(\"foo\", 0d, 2d, 1, 1);\n    List<String> range2 = jedis.zrangeByScore(\"foo\", \"-inf\", \"(2\");\n    assertEquals(expected, range2);\n\n    expected = new ArrayList<String>();\n    expected.add(\"a\");\n\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> brange = jedis.zrangeByScore(bfoo, 0d, 2d);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScore(bfoo, 0d, 2d, 0, 1);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScore(bfoo, 0d, 2d, 1, 1);\n    List<byte[]> brange2 = jedis.zrangeByScore(bfoo, SafeEncoder.encode(\"-inf\"),\n      SafeEncoder.encode(\"(2\"));\n    assertByteArrayListEquals(bexpected, brange2);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n  }\n\n  @Test\n  public void zrevrangebyscore() {\n    jedis.zadd(\"foo\", 1.0d, \"a\");\n    jedis.zadd(\"foo\", 2.0d, \"b\");\n    jedis.zadd(\"foo\", 3.0d, \"c\");\n    jedis.zadd(\"foo\", 4.0d, \"d\");\n    jedis.zadd(\"foo\", 5.0d, \"e\");\n\n    List<String> range = jedis.zrevrangeByScore(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"c\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    expected = new ArrayList<String>();\n    expected.add(\"c\");\n    expected.add(\"b\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", 4d, 2d);\n    expected = new ArrayList<String>();\n    expected.add(\"d\");\n    expected.add(\"c\");\n    expected.add(\"b\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", \"4\", \"2\", 0, 2);\n    expected = new ArrayList<String>();\n    expected.add(\"d\");\n    expected.add(\"c\");\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScore(\"foo\", \"+inf\", \"(4\");\n    expected = new ArrayList<String>();\n    expected.add(\"e\");\n\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<byte[]> brange = jedis.zrevrangeByScore(bfoo, 2d, 0d);\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    brange = jedis.zrevrangeByScore(bfoo, 2d, 0d, 0, 1);\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n\n    assertByteArrayListEquals(bexpected, brange);\n\n    List<byte[]> brange2 = jedis.zrevrangeByScore(bfoo, SafeEncoder.encode(\"+inf\"),\n      SafeEncoder.encode(\"(2\"));\n\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, brange2);\n\n    brange = jedis.zrevrangeByScore(bfoo, 2d, 0d, 1, 1);\n    bexpected = new ArrayList<byte[]>();\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zrangebyscoreWithScores() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    List<Tuple> range = jedis.zrangeByScoreWithScores(\"foo\", 0d, 2d);\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n    expected.add(new Tuple(\"a\", 2d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScoreWithScores(\"foo\", 0d, 2d, 0, 1);\n\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 0.1d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrangeByScoreWithScores(\"foo\", 0d, 2d, 1, 1);\n\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", 2d));\n\n    assertEquals(expected, range);\n\n    // Binary\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> brange = jedis.zrangeByScoreWithScores(bfoo, 0d, 2d);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScoreWithScores(bfoo, 0d, 2d, 0, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrangeByScoreWithScores(bfoo, 0d, 2d, 1, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n  }\n\n  @Test\n  public void zrevrangebyscoreWithScores() {\n    jedis.zadd(\"foo\", 1.0d, \"a\");\n    jedis.zadd(\"foo\", 2.0d, \"b\");\n    jedis.zadd(\"foo\", 3.0d, \"c\");\n    jedis.zadd(\"foo\", 4.0d, \"d\");\n    jedis.zadd(\"foo\", 5.0d, \"e\");\n\n    List<Tuple> range = jedis.zrevrangeByScoreWithScores(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 3.0d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScoreWithScores(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"c\", 3.0d));\n    expected.add(new Tuple(\"b\", 2.0d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScoreWithScores(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"b\", 2.0d));\n\n    assertEquals(expected, range);\n\n    range = jedis.zrevrangeByScoreWithScores(\"foo\", 4d, 2d);\n    expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"d\", 4.0d));\n    expected.add(new Tuple(\"c\", 3.0d));\n    expected.add(new Tuple(\"b\", 2.0d));\n\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    List<Tuple> brange = jedis.zrevrangeByScoreWithScores(bfoo, 2d, 0d);\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, 2d));\n    bexpected.add(new Tuple(bc, 0.1d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrevrangeByScoreWithScores(bfoo, 2d, 0d, 0, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, 2d));\n\n    assertEquals(bexpected, brange);\n\n    brange = jedis.zrevrangeByScoreWithScores(bfoo, 2d, 0d, 1, 1);\n\n    bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(bc, 0.1d));\n\n    assertEquals(bexpected, brange);\n  }\n\n  @Test\n  public void zremrangeByRank() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(1, jedis.zremrangeByRank(\"foo\", 0, 0));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(1, jedis.zremrangeByRank(bfoo, 0, 0));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n\n  }\n\n  @Test\n  public void zremrangeByScore() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 10d, \"b\");\n    jedis.zadd(\"foo\", 0.1d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"a\");\n\n    assertEquals(2, jedis.zremrangeByScore(\"foo\", 0, 2));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"b\");\n\n    assertEquals(expected, jedis.zrange(\"foo\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    assertEquals(2, jedis.zremrangeByScore(bfoo, 0, 2));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(bb);\n\n    assertByteArrayListEquals(bexpected, jedis.zrange(bfoo, 0, 100));\n  }\n\n  @Test\n  public void zremrangeByScoreExclusive() {\n    jedis.zadd(\"foo\", 1d, \"a\");\n    jedis.zadd(\"foo\", 0d, \"c\");\n    jedis.zadd(\"foo\", 2d, \"b\");\n\n    assertEquals(1, jedis.zremrangeByScore(\"foo\", \"(0\", \"(2\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 0d, bc);\n    jedis.zadd(bfoo, 2d, bb);\n\n    assertEquals(1, jedis.zremrangeByScore(bfoo, \"(0\".getBytes(), \"(2\".getBytes()));\n  }\n\n  @Test\n  public void zremrangeByLex() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 1, \"b\");\n    jedis.zadd(\"foo\", 1, \"c\");\n    jedis.zadd(\"foo\", 1, \"aa\");\n\n    assertEquals(2, jedis.zremrangeByLex(\"foo\", \"[aa\", \"(c\"));\n\n    List<String> expected = new ArrayList<String>();\n    expected.add(\"a\");\n    expected.add(\"c\");\n\n    assertEquals(expected, jedis.zrangeByLex(\"foo\", \"-\", \"+\"));\n  }\n\n  @Test\n  public void zremrangeByLexBinary() {\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bc);\n    jedis.zadd(bfoo, 1, bb);\n\n    assertEquals(1, jedis.zremrangeByLex(bfoo, bInclusiveB, bExclusiveC));\n\n    List<byte[]> bexpected = new ArrayList<byte[]>();\n    bexpected.add(ba);\n    bexpected.add(bc);\n\n    assertByteArrayListEquals(bexpected, jedis.zrangeByLex(bfoo, bLexMinusInf, bLexPlusInf));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunion() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    List<String> expected = new ArrayList<>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    assertEquals(expected, jedis.zunion(params, \"foo\", \"bar\"));\n\n    List<Tuple> expectedTuple = new ArrayList<>();\n    expectedTuple.add(new Tuple(\"a\", new Double(7)));\n    expectedTuple.add(new Tuple(\"b\", new Double(9)));\n    assertEquals(expectedTuple, jedis.zunionWithScores(params, \"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(ba);\n    bexpected.add(bb);\n    AssertUtil.assertByteArrayListEquals(bexpected, jedis.zunion(params, bfoo, bbar));\n\n    List<Tuple> bexpectedTuple = new ArrayList<>();\n    bexpectedTuple.add(new Tuple(ba, new Double(7)));\n    bexpectedTuple.add(new Tuple(bb, new Double(9)));\n    assertEquals(bexpectedTuple, jedis.zunionWithScores(params, bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunionstore() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 2, \"b\");\n\n    assertEquals(2, jedis.zunionstore(\"dst\", \"foo\", \"bar\"));\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", new Double(3)));\n    expected.add(new Tuple(\"b\", new Double(4)));\n    assertEquals(expected, jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    assertEquals(2, jedis.zunionstore(SafeEncoder.encode(\"dst\"), bfoo, bbar));\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, new Double(3)));\n    bexpected.add(new Tuple(bb, new Double(4)));\n    assertEquals(bexpected, jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunionstoreParams() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(2, jedis.zunionstore(\"dst\", params, \"foo\", \"bar\"));\n\n    List<Tuple> expected = new ArrayList<Tuple>();\n    expected.add(new Tuple(\"a\", new Double(7)));\n    expected.add(new Tuple(\"b\", new Double(9)));\n    assertEquals(expected, jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(2, jedis.zunionstore(SafeEncoder.encode(\"dst\"), bparams, bfoo, bbar));\n\n    List<Tuple> bexpected = new ArrayList<Tuple>();\n    bexpected.add(new Tuple(ba, new Double(7)));\n    bexpected.add(new Tuple(bb, new Double(9)));\n    assertEquals(bexpected, jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zinter() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n    assertEquals(singletonList(\"a\"), jedis.zinter(params, \"foo\", \"bar\"));\n\n    assertEquals(singletonList(new Tuple(\"a\", new Double(7))),\n      jedis.zinterWithScores(params, \"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n    AssertUtil.assertByteArrayListEquals(singletonList(ba), jedis.zinter(params, bfoo, bbar));\n\n    assertEquals(singletonList(new Tuple(ba, new Double(7))),\n      jedis.zinterWithScores(bparams, bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zinterstore() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n\n    assertEquals(1, jedis.zinterstore(\"dst\", \"foo\", \"bar\"));\n\n    assertEquals(singletonList(new Tuple(\"a\", new Double(3))),\n        jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n\n    assertEquals(1, jedis.zinterstore(SafeEncoder.encode(\"dst\"), bfoo, bbar));\n\n    assertEquals(singletonList(new Tuple(ba, new Double(3))),\n        jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zintertoreParams() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(1, jedis.zinterstore(\"dst\", params, \"foo\", \"bar\"));\n\n    assertEquals(singletonList(new Tuple(\"a\", new Double(7))),\n        jedis.zrangeWithScores(\"dst\", 0, 100));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(1, jedis.zinterstore(SafeEncoder.encode(\"dst\"), bparams, bfoo, bbar));\n\n    assertEquals(singletonList(new Tuple(ba, new Double(7))),\n        jedis.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zintercard() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"bar\", 2, \"a\");\n    jedis.zadd(\"bar\", 1, \"b\");\n\n    assertEquals(2, jedis.zintercard(\"foo\", \"bar\"));\n    assertEquals(1, jedis.zintercard(1, \"foo\", \"bar\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bbar, 2, ba);\n    jedis.zadd(bbar, 2, bb);\n\n    assertEquals(2, jedis.zintercard(bfoo, bbar));\n    assertEquals(1, jedis.zintercard(1, bfoo, bbar));\n  }\n\n  @Test\n  public void zscan() {\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 2, \"b\");\n\n    ScanResult<Tuple> result = jedis.zscan(\"foo\", SCAN_POINTER_START);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 1, bb);\n\n    ScanResult<Tuple> bResult = jedis.zscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void zscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    jedis.zadd(\"foo\", 2, \"b\");\n    jedis.zadd(\"foo\", 1, \"a\");\n    jedis.zadd(\"foo\", 11, \"aa\");\n    ScanResult<Tuple> result = jedis.zscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    jedis.zadd(bfoo, 2, bbar1);\n    jedis.zadd(bfoo, 1, bbar2);\n    jedis.zadd(bfoo, 11, bbar3);\n    ScanResult<Tuple> bResult = jedis.zscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertArrayEquals(SCAN_POINTER_START_BINARY, bResult.getCursorAsBytes());\n    assertFalse(bResult.getResult().isEmpty());\n\n  }\n\n  @Test\n  public void zscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    jedis.zadd(\"foo\", 1, \"a1\");\n    jedis.zadd(\"foo\", 2, \"a2\");\n    jedis.zadd(\"foo\", 3, \"a3\");\n    jedis.zadd(\"foo\", 4, \"a4\");\n    jedis.zadd(\"foo\", 5, \"a5\");\n\n    ScanResult<Tuple> result = jedis.zscan(\"foo\", SCAN_POINTER_START, params);\n\n    assertFalse(result.getResult().isEmpty());\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    jedis.zadd(bfoo, 2, bbar1);\n    jedis.zadd(bfoo, 1, bbar2);\n    jedis.zadd(bfoo, 11, bbar3);\n\n    ScanResult<Tuple> bResult = jedis.zscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    assertFalse(bResult.getResult().isEmpty());\n  }\n\n  @Test\n  public void infinity() {\n    jedis.zadd(\"key\", Double.POSITIVE_INFINITY, \"pos\");\n    assertEquals(Double.POSITIVE_INFINITY, jedis.zscore(\"key\", \"pos\"), 0d);\n    jedis.zadd(\"key\", Double.NEGATIVE_INFINITY, \"neg\");\n    assertEquals(Double.NEGATIVE_INFINITY, jedis.zscore(\"key\", \"neg\"), 0d);\n    jedis.zadd(\"key\", 0d, \"zero\");\n\n    List<Tuple> set = jedis.zrangeWithScores(\"key\", 0, -1);\n    Iterator<Tuple> itr = set.iterator();\n    assertEquals(Double.NEGATIVE_INFINITY, itr.next().getScore(), 0d);\n    assertEquals(0d, itr.next().getScore(), 0d);\n    assertEquals(Double.POSITIVE_INFINITY, itr.next().getScore(), 0d);\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bzpopmax() {\n    assertNull(jedis.bzpopmax(1, \"foo\", \"bar\"));\n\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"bar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    assertEquals(new KeyValue<>(\"foo\", new Tuple(\"b\", 10d)), jedis.bzpopmax(0, \"foo\", \"bar\"));\n\n    // Binary\n    assertNull(jedis.bzpopmax(1, bfoo, bbar));\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bbar, 0.1d, bc);\n    KeyValue<byte[], Tuple> actual = jedis.bzpopmax(0, bfoo, bbar);\n    assertArrayEquals(bfoo, actual.getKey());\n    assertEquals(new Tuple(bb, 10d), actual.getValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bzpopmin() {\n    assertNull(jedis.bzpopmin(1, \"bar\", \"foo\"));\n\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"bar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    assertEquals(new KeyValue<>(\"bar\", new Tuple(\"c\", 0.1)), jedis.bzpopmin(0, \"bar\", \"foo\"));\n\n    // Binary\n    assertNull(jedis.bzpopmin(1, bbar, bfoo));\n\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bbar, 0.1d, bc);\n    KeyValue<byte[], Tuple> actual = jedis.bzpopmin(0, bbar, bfoo);\n    assertArrayEquals(bbar, (byte[]) actual.getKey());\n    assertEquals(new Tuple(bc, 0.1), actual.getValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zdiff() {\n    jedis.zadd(\"foo\", 1.0, \"a\");\n    jedis.zadd(\"foo\", 2.0, \"b\");\n    jedis.zadd(\"bar\", 1.0, \"a\");\n\n    assertEquals(0, jedis.zdiff(\"bar1\", \"bar2\").size());\n    assertEquals(singletonList(\"b\"), jedis.zdiff(\"foo\", \"bar\"));\n    assertEquals(singletonList(new Tuple(\"b\", 2.0d)), jedis.zdiffWithScores(\"foo\", \"bar\"));\n\n    // binary\n\n    jedis.zadd(bfoo, 1.0, ba);\n    jedis.zadd(bfoo, 2.0, bb);\n    jedis.zadd(bbar, 1.0, ba);\n\n    assertEquals(0, jedis.zdiff(bbar1, bbar2).size());\n    List<byte[]> bactual = jedis.zdiff(bfoo, bbar);\n    assertEquals(1, bactual.size());\n    assertArrayEquals(bb, bactual.iterator().next());\n    assertEquals(singletonList(new Tuple(bb, 2.0d)), jedis.zdiffWithScores(bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zdiffstore() {\n    jedis.zadd(\"foo\", 1.0, \"a\");\n    jedis.zadd(\"foo\", 2.0, \"b\");\n    jedis.zadd(\"bar\", 1.0, \"a\");\n\n    assertEquals(0, jedis.zdiffstore(\"bar3\", \"bar1\", \"bar2\"));\n    assertEquals(1, jedis.zdiffstore(\"bar3\", \"foo\", \"bar\"));\n    assertEquals(singletonList(\"b\"), jedis.zrange(\"bar3\", 0, -1));\n\n    // binary\n\n    jedis.zadd(bfoo, 1.0, ba);\n    jedis.zadd(bfoo, 2.0, bb);\n    jedis.zadd(bbar, 1.0, ba);\n\n    assertEquals(0, jedis.zdiffstore(bbar3, bbar1, bbar2));\n    assertEquals(1, jedis.zdiffstore(bbar3, bfoo, bbar));\n    List<byte[]> bactual = jedis.zrange(bbar3, 0, -1);\n    assertArrayEquals(bb, bactual.iterator().next());\n  }\n\n  @Test\n  public void zrandmember() {\n    assertNull(jedis.zrandmember(\"foo\"));\n    assertEquals(Collections.emptyList(), jedis.zrandmember(\"foo\", 1));\n    assertEquals(Collections.emptyList(), jedis.zrandmemberWithScores(\"foo\", 1));\n\n    Map<String, Double> hash = new HashMap<>();\n    hash.put(\"bar1\", 1d);\n    hash.put(\"bar2\", 10d);\n    hash.put(\"bar3\", 0.1d);\n    jedis.zadd(\"foo\", hash);\n\n    AssertUtil.assertCollectionContains(hash.keySet(), jedis.zrandmember(\"foo\"));\n    assertEquals(2, jedis.zrandmember(\"foo\", 2).size());\n\n    List<Tuple> actual = jedis.zrandmemberWithScores(\"foo\", 2);\n    assertEquals(2, actual.size());\n    actual.forEach(t -> assertEquals(hash.get(t.getElement()), t.getScore(), 0d));\n\n    // Binary\n    assertNull(jedis.zrandmember(bfoo));\n    assertEquals(Collections.emptyList(), jedis.zrandmember(bfoo, 1));\n    assertEquals(Collections.emptyList(), jedis.zrandmemberWithScores(bfoo, 1));\n\n    Map<byte[], Double> bhash = new HashMap<>();\n    bhash.put(bbar1, 1d);\n    bhash.put(bbar2, 10d);\n    bhash.put(bbar3, 0.1d);\n    jedis.zadd(bfoo, bhash);\n\n    AssertUtil.assertByteArrayCollectionContains(bhash.keySet(), jedis.zrandmember(bfoo));\n    assertEquals(2, jedis.zrandmember(bfoo, 2).size());\n\n    List<Tuple> bactual = jedis.zrandmemberWithScores(bfoo, 2);\n    assertEquals(2, bactual.size());\n    bactual.forEach(t -> assertEquals(getScoreFromByteMap(bhash, t.getBinaryElement()), t.getScore(), 0d));\n  }\n\n  private Double getScoreFromByteMap(Map<byte[], Double> bhash, byte[] key) {\n    for (Map.Entry<byte[], Double> en : bhash.entrySet()) {\n      if (Arrays.equals(en.getKey(), key)) {\n        return en.getValue();\n      }\n    }\n    return null;\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void zmpop() {\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    KeyValue<String, List<Tuple>> single = jedis.zmpop(SortedSetOption.MAX, \"foo\");\n    KeyValue<String, List<Tuple>> range = jedis.zmpop(SortedSetOption.MIN, 2, \"foo\");\n\n    assertEquals(new Tuple(\"b\", 10d), single.getValue().get(0));\n    assertEquals(2, range.getValue().size());\n    assertNull(jedis.zmpop(SortedSetOption.MAX, \"foo\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void bzmpopSimple() {\n    jedis.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    KeyValue<String, List<Tuple>> single = jedis.bzmpop(1L, SortedSetOption.MAX, \"foo\");\n    KeyValue<String, List<Tuple>> range = jedis.bzmpop(1L, SortedSetOption.MIN, 2, \"foo\");\n\n    assertEquals(new Tuple(\"b\", 10d), single.getValue().get(0));\n    assertEquals(2, range.getValue().size());\n    assertNull(jedis.bzmpop(1L, SortedSetOption.MAX, \"foo\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XCfgSetParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.params.XTrimParams;\nimport redis.clients.jedis.resps.StreamEntryBinary;\nimport redis.clients.jedis.resps.StreamEntryDeletionResult;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static io.redis.test.utils.RedisVersion.V8_4_0_STRING;\nimport static java.util.Collections.singletonMap;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY;\nimport static redis.clients.jedis.util.ByteArrayMapMatcher.contentEquals;\nimport static redis.clients.jedis.util.StreamEntryBinaryListMatcher.equalsStreamEntries;\n\n@Tag(\"integration\")\npublic abstract class StreamsBinaryCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  protected static final byte[] STREAM_KEY_1 = \"{binary-stream}-1\".getBytes();\n  protected static final byte[] STREAM_KEY_2 = \"{binary-stream}-2\".getBytes();\n  protected static final byte[] GROUP_NAME = \"group-1\".getBytes();\n  protected static final byte[] CONSUMER_NAME = \"consumer-1\".getBytes();\n\n  protected static final byte[] FIELD_KEY_1 = \"binary-field-1\".getBytes();\n  // Test with invalid UTF-8 characters\n  protected static final byte[] BINARY_VALUE_1 = new byte[] { 0x00, 0x01, 0x02, 0x03, (byte) 0xFF };\n\n  protected static final byte[] FIELD_KEY_2 = \"binary-field-1\".getBytes();\n  protected static final byte[] BINARY_VALUE_2 = \"binary-value-2\".getBytes();\n  protected static final Map<byte[], byte[]> HASH_1 = singletonMap(FIELD_KEY_1, BINARY_VALUE_1);\n  protected static final Map<byte[], byte[]> HASH_2 = singletonMap(FIELD_KEY_2, BINARY_VALUE_2);\n\n  protected static final List<StreamEntryBinary> stream1Entries = new ArrayList<>();\n  protected static final List<StreamEntryBinary> stream2Entries = new ArrayList<>();\n\n  static {\n    stream1Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-1\"), HASH_1));\n    stream1Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-3\"), HASH_2));\n\n    stream2Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-2\"), HASH_1));\n  }\n\n  public StreamsBinaryCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  /**\n   * Creates a map of stream keys to StreamEntryID objects.\n   * @param streamOffsets Array of stream key and offset pairs\n   * @return Map of stream keys to StreamEntryID objects\n   */\n  public static Map<byte[], StreamEntryID> offsets(Object... streamOffsets) {\n    if (streamOffsets.length % 2 != 0) {\n      throw new IllegalArgumentException(\"Stream offsets must be provided as key-value pairs\");\n    }\n\n    Map<byte[], StreamEntryID> result = new HashMap<>();\n    for (int i = 0; i < streamOffsets.length; i += 2) {\n      byte[] key = (byte[]) streamOffsets[i];\n      Object value = streamOffsets[i + 1];\n\n      StreamEntryID id;\n      if (value instanceof String) {\n        id = new StreamEntryID((String) value);\n      } else if (value instanceof StreamEntryID) {\n        id = (StreamEntryID) value;\n      } else {\n        throw new IllegalArgumentException(\"Offset must be a String or StreamEntryID\");\n      }\n\n      result.put(key, id);\n    }\n\n    return result;\n  }\n\n  @BeforeEach\n  public void setUpTestStream() {\n    setUpTestStream(StreamEntryID.XGROUP_LAST_ENTRY.toString().getBytes());\n  }\n\n  private void setUpTestStream(byte[] startId) {\n    jedis.del(STREAM_KEY_1);\n    jedis.del(STREAM_KEY_2);\n    try {\n      jedis.xgroupCreate(STREAM_KEY_1, GROUP_NAME, startId, true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n    try {\n      jedis.xgroupCreate(STREAM_KEY_2, GROUP_NAME, startId, true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n  }\n\n  @Test\n  public void xreadBinaryNoEntries() {\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadBinary(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertNull(actualEntries);\n  }\n\n  @Test\n  public void xreadBinary() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadBinary(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadBinaryCount() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadBinary(\n        XReadParams.xReadParams().count(1), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries.subList(0, 1)));\n  }\n\n  @Test\n  public void xreadBinaryAsMapNoEntries() {\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertNull(actualEntries);\n  }\n\n  @Test\n  public void xreadBinaryAsMap() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadBinaryAsMapCount() {\n\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams().count(1), offsets(STREAM_KEY_1, \"0-0\"));\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries.subList(0, 1)));\n  }\n\n  @Test\n  public void xreadBinaryAsMapWithMultipleStreams() {\n\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n    stream2Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_2, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\", STREAM_KEY_2, \"0-0\"));\n\n    assertThat(actualEntries.entrySet(), hasSize(2));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n    assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries));\n  }\n\n  @Test\n  public void xreadGroupBinary() {\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY));\n\n    // verify the result contains entries from one stream\n    // and is under the expected stream key\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadGroupBinaryAsMap() {\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadGroupBinaryAsMap(GROUP_NAME,\n        CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY));\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadGroupBinaryAsMapMultipleStreams() {\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n    stream2Entries.forEach(\n        entry -> jedis.xadd(STREAM_KEY_2, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Map<byte[], List<StreamEntryBinary>> actualEntries = jedis.xreadGroupBinaryAsMap(GROUP_NAME,\n        CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY, STREAM_KEY_2,\n            XREADGROUP_UNDELIVERED_ENTRY));\n\n    assertThat(actualEntries.entrySet(), hasSize(2));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n    assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries));\n  }\n\n  // ========== XACKDEL Command Tests ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdel() {\n    setUpTestStream();\n\n    // Add a message to the stream\n    byte[] messageId = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    assertNotNull(messageId);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Read the message with consumer group to add it to PEL\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(1, messages.get(0).getValue().size());\n    byte[] readMessageId = messages.get(0).getValue().get(0).getID().toString().getBytes();\n\n    // Test XACKDEL - should acknowledge and delete the message\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify message is deleted from stream\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdelWithTrimMode() {\n    setUpTestStream();\n\n    // Add multiple messages\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Read the messages with consumer group\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Test XACKDEL with KEEP_REFERENCES mode\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamDeletionPolicy.KEEP_REFERENCES, readId1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify one message is deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdelUnreadMessages() {\n    setUpTestStream();\n\n    // Add test entries but don't read them\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n\n    // Test XACKDEL on unread messages - should return NOT_FOUND for PEL\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1);\n\n    assertThat(results, hasSize(1));\n    // Should return NOT_FOUND because message was never read by the consumer group\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0));\n\n    // Stream should still contain the message\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXackdelMultipleMessages() {\n    setUpTestStream();\n\n    // Add multiple messages\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"3-0\"), HASH_1);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Read the messages with consumer group\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(3, messages.get(0).getValue().size());\n\n    // Test XACKDEL with multiple IDs\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(1));\n\n    // Verify two messages are deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  // ========== XDELEX Command Tests ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelex() {\n    setUpTestStream();\n\n    // Add test entries\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES)\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify entry is deleted from stream\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexWithTrimMode() {\n    setUpTestStream();\n\n    // Add test entries\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n\n    // Test XDELEX with DELETE_REFERENCES mode\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.DELETE_REFERENCES, id1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify entry is deleted from stream\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexMultipleEntries() {\n    setUpTestStream();\n\n    // Add test entries\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    byte[] id3 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"3-0\"), HASH_1);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDELEX with multiple IDs\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1, id3);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(1));\n\n    // Verify two entries are deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexNonExistentEntries() {\n    setUpTestStream();\n\n    // Add one entry\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDELEX with mix of existing and non-existent IDs\n    byte[] nonExistentId = \"999-0\".getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Existing entry\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(1)); // Non-existent entry\n\n    // Verify existing entry is deleted\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexWithConsumerGroups() {\n    setUpTestStream();\n\n    // Add test entries\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"1-0\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Read messages with consumer group to add them to PEL\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Acknowledge only the first message\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes();\n    jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1);\n\n    // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged\n    assertEquals(StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED, results.get(1)); // id2 not acknowledged\n\n    // Verify only acknowledged entry was deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXdelexEmptyStream() {\n    setUpTestStream();\n\n    // Test XDELEX on empty stream\n    byte[] nonExistentId = \"1-0\".getBytes();\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, nonExistentId);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0));\n  }\n\n  // ========== XTRIM Command Tests with trimmingMode ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXtrimWithKeepReferences() {\n    setUpTestStream();\n\n    // Add test entries\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + \"-0\"), HASH_1);\n    }\n    assertEquals(5L, jedis.xlen(STREAM_KEY_1));\n\n    // Read messages with consumer group to create PEL entries\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    // Test XTRIM with KEEP_REFERENCES mode - should preserve PEL references\n    long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(\n        StreamDeletionPolicy.KEEP_REFERENCES));\n    assertEquals(2L, trimmed); // Should trim 2 entries\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testXtrimWithAcknowledged() {\n    setUpTestStream();\n\n    // Add test entries\n    for (int i = 1; i <= 5; i++) {\n      jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + \"-0\"), HASH_1);\n    }\n    assertEquals(5L, jedis.xlen(STREAM_KEY_1));\n\n    // Read messages with consumer group\n    Map<byte[], StreamEntryID> streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> messages = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(3, messages.get(0).getValue().size());\n\n    // Acknowledge only the first 2 messages\n    byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes();\n    byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes();\n    jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2);\n\n    // Test XTRIM with ACKNOWLEDGED mode - should only trim acknowledged entries\n    long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(\n        StreamDeletionPolicy.ACKNOWLEDGED));\n    // The exact behavior depends on implementation, but it should respect acknowledgment status\n    assertTrue(trimmed >= 0);\n    assertTrue(jedis.xlen(STREAM_KEY_1) <= 5); // Should not exceed original length\n  }\n\n  // ========== XREADGROUP CLAIM Tests ==========\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimReturnsMetadataOrdered() throws InterruptedException {\n    setUpTestStream(\"0-0\".getBytes());\n\n    final byte[] CONSUMER_1 = \"consumer-1\".getBytes();\n    final byte[] CONSUMER_2 = \"consumer-2\".getBytes();\n    final long IDLE_TIME_MS = 5;\n\n    // Produce two entries\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(StreamEntryID.NEW_ENTRY), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(StreamEntryID.NEW_ENTRY), HASH_1);\n    Map<byte[], StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n        StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_1, XReadGroupParams.xReadGroupParams().count(10),\n        streams);\n\n    // Ensure idle time so entries are claimable\n    Thread.sleep(IDLE_TIME_MS);\n\n    // Produce fresh entries that are NOT claimed (not pending)\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(StreamEntryID.NEW_ENTRY), HASH_1);\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(StreamEntryID.NEW_ENTRY), HASH_1);\n\n    // Read with consumer-2 using CLAIM\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> consumer2Result = jedis.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_2, XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).count(10),\n        streams);\n\n    assertNotNull(consumer2Result);\n    assertEquals(1, consumer2Result.size());\n\n    List<StreamEntryBinary> entries = consumer2Result.get(0).getValue();\n    assertEquals(4, entries.size());\n\n    long claimedCount = entries.stream().filter(StreamEntryBinary::isClaimed).count();\n    long freshCount = entries.size() - claimedCount;\n\n    assertEquals(2, claimedCount);\n    assertEquals(2, freshCount);\n\n    // Assert order: pending entries are first\n    StreamEntryBinary first = entries.get(0);\n    StreamEntryBinary second = entries.get(1);\n    StreamEntryBinary third = entries.get(2);\n    StreamEntryBinary fourth = entries.get(3);\n\n    // Claimed entries\n    assertTrue(first.isClaimed());\n    assertTrue(second.isClaimed());\n    assertTrue(first.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n    assertTrue(second.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n    assertThat(first.getFields(), contentEquals(HASH_1));\n\n    // Fresh entries\n    assertFalse(third.isClaimed());\n    assertFalse(fourth.isClaimed());\n    assertEquals(Long.valueOf(0), third.getDeliveredCount());\n    assertEquals(Long.valueOf(0), fourth.getDeliveredCount());\n    assertEquals(Long.valueOf(0), third.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(0), fourth.getMillisElapsedFromDelivery());\n    assertThat(fourth.getFields(), contentEquals(HASH_1));\n  }\n\n  @Test\n  public void xreadGroupPreservesFieldOrder() {\n    byte[] streamKey = \"field-order-stream\".getBytes();\n    byte[] groupName = \"field-order-group\".getBytes();\n    byte[] consumerName = \"field-order-consumer\".getBytes();\n\n    // Use LinkedHashMap to ensure insertion order: a, z, m\n    Map<byte[], byte[]> fields = new LinkedHashMap<>();\n    fields.put(\"a\".getBytes(), \"1\".getBytes());\n    fields.put(\"z\".getBytes(), \"4\".getBytes());\n    fields.put(\"m\".getBytes(), \"2\".getBytes());\n\n    jedis.xadd(streamKey, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), fields);\n    jedis.xgroupCreate(streamKey, groupName, \"0-0\".getBytes(), false);\n\n    Map<byte[], StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> result = jedis.xreadGroupBinary(groupName, consumerName,\n        XReadGroupParams.xReadGroupParams().count(1), streamQuery);\n\n    assertEquals(1, result.size());\n    assertEquals(1, result.get(0).getValue().size());\n\n    StreamEntryBinary entry = result.get(0).getValue().get(0);\n    Map<byte[], byte[]> returnedFields = entry.getFields();\n\n    // Verify field order is preserved - this will fail with HashMap, pass with LinkedHashMap\n    byte[][] expectedOrder = {\"a\".getBytes(), \"z\".getBytes(), \"m\".getBytes()};\n    byte[][] actualOrder = returnedFields.keySet().toArray(new byte[0][]);\n\n    assertEquals(expectedOrder.length, actualOrder.length, \"Field count should match\");\n    for (int i = 0; i < expectedOrder.length; i++) {\n      assertArrayEquals(expectedOrder[i], actualOrder[i],\n          String.format(\"Field order mismatch at position %d: expected '%s' but got '%s'. \" +\n              \"Full order: expected [a, z, m], actual %s\",\n              i, new String(expectedOrder[i]), new String(actualOrder[i]),\n              java.util.Arrays.toString(java.util.Arrays.stream(actualOrder).map(String::new).toArray())));\n    }\n  }\n\n  @Test\n  public void xreadBinaryAsMapPreservesStreamOrder() {\n    // Test that xreadBinaryAsMap preserves the order of streams when reading from multiple streams\n    byte[] streamKey1 = \"{stream-order}-test-1\".getBytes();\n    byte[] streamKey2 = \"{stream-order}-test-2\".getBytes();\n    byte[] streamKey3 = \"{stream-order}-test-3\".getBytes();\n\n    // Add entries to streams in specific order\n    Map<byte[], byte[]> fields = new LinkedHashMap<>();\n    fields.put(\"field\".getBytes(), \"value1\".getBytes());\n    jedis.xadd(streamKey1, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), fields);\n\n    fields.put(\"field\".getBytes(), \"value2\".getBytes());\n    jedis.xadd(streamKey2, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), fields);\n\n    fields.put(\"field\".getBytes(), \"value3\".getBytes());\n    jedis.xadd(streamKey3, XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY), fields);\n\n    // Read from multiple streams in specific order\n    Map<byte[], StreamEntryID> streams = new LinkedHashMap<>();\n    streams.put(streamKey1, new StreamEntryID(\"0-0\"));\n    streams.put(streamKey2, new StreamEntryID(\"0-0\"));\n    streams.put(streamKey3, new StreamEntryID(\"0-0\"));\n\n    Map<byte[], List<StreamEntryBinary>> result = jedis.xreadBinaryAsMap(\n        XReadParams.xReadParams().count(10), streams);\n\n    assertNotNull(result);\n    assertEquals(3, result.size());\n\n    // Verify that the order of streams in the result matches the order in the request\n    byte[][] expectedOrder = {streamKey1, streamKey2, streamKey3};\n    byte[][] actualOrder = result.keySet().toArray(new byte[0][]);\n\n    assertEquals(expectedOrder.length, actualOrder.length, \"Stream count should match\");\n    for (int i = 0; i < expectedOrder.length; i++) {\n      assertArrayEquals(expectedOrder[i], actualOrder[i],\n          String.format(\"Stream order mismatch at position %d: expected '%s' but got '%s'\",\n              i, new String(expectedOrder[i]), new String(actualOrder[i])));\n    }\n  }\n\n  // ========== Idempotent Producer Tests ==========\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpAuto() {\n    // Add entry with IDMPAUTO\n    Map<byte[], byte[]> message = new HashMap<>();\n    message.put(\"order\".getBytes(), \"12345\".getBytes());\n    message.put(\"amount\".getBytes(), \"100.00\".getBytes());\n\n    byte[] id1 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()),\n        message);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add same message again with same producer - should return same ID (duplicate detected)\n    byte[] id2 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()),\n        message);\n    assertArrayEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1)); // Stream length unchanged\n\n    // Add same message with different producer - should succeed\n    byte[] id3 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-2\".getBytes()),\n        message);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Add different message with same producer - should succeed\n    Map<byte[], byte[]> message2 = new HashMap<>();\n    message2.put(\"order\".getBytes(), \"67890\".getBytes());\n    message2.put(\"amount\".getBytes(), \"200.00\".getBytes());\n\n    byte[] id4 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()),\n        message2);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmp() {\n    // Add entry with explicit idempotent ID\n    byte[] id1 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\".getBytes(), \"iid-001\".getBytes()), HASH_1);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with same producer and idempotent ID - should return same ID (duplicate detected)\n    byte[] id2 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\".getBytes(), \"iid-001\".getBytes()), HASH_2);\n    assertArrayEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with same producer but different idempotent ID - should succeed\n    byte[] id3 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\".getBytes(), \"iid-002\".getBytes()), HASH_1);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with different producer but same idempotent ID - should succeed\n    byte[] id4 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-2\".getBytes(), \"iid-001\".getBytes()), HASH_1);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXcfgset() {\n    // Add an entry to create the stream\n    jedis.xadd(STREAM_KEY_1, new XAddParams().id(StreamEntryID.NEW_ENTRY), HASH_1);\n\n    // Configure idempotent producer settings\n    byte[] result = jedis.xcfgset(STREAM_KEY_1,\n        XCfgSetParams.xCfgSetParams().idmpDuration(1000).idmpMaxsize(500));\n    assertArrayEquals(\"OK\".getBytes(), result);\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpWithTrimming() {\n    // Add first entry with IDMPAUTO and trimming\n    byte[] id1 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()).maxLen(2), HASH_1);\n    assertNotNull(id1);\n    assertEquals(1, jedis.xlen(STREAM_KEY_1));\n\n    // Add duplicate - should return same ID and not add new entry\n    byte[] id2 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()).maxLen(2), HASH_1);\n    assertArrayEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1, jedis.xlen(STREAM_KEY_1)); // Still 1 entry\n\n    // Add different message - should add new entry\n    byte[] id3 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmpAuto(\"producer-1\".getBytes()).maxLen(2), HASH_2);\n    assertNotNull(id3);\n    assertNotEquals(new String(id1), new String(id3)); // Different IDs\n    assertEquals(2, jedis.xlen(STREAM_KEY_1)); // Now 2 entries\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.args.StreamDeletionPolicy;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.*;\nimport redis.clients.jedis.resps.*;\n\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static io.redis.test.utils.RedisVersion.V8_4_0_STRING;\nimport static java.util.Collections.singletonMap;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Tag(\"integration\")\npublic abstract class StreamsCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  protected static final String STREAM_KEY_1 = \"{stream}-1\";\n  protected static final String STREAM_KEY_2 = \"{stream}-2\";\n  protected static final String GROUP_NAME = \"group-1\";\n  protected static final String CONSUMER_NAME = \"consumer-1\";\n\n  protected static final String FIELD_KEY_1 = \"field-1\";\n  protected static final String VALUE_1 = \"value-1\";\n  protected static final String FIELD_KEY_2 = \"field-2\";\n  protected static final String VALUE_2 = \"value-2\";\n  protected static final Map<String, String> HASH_1 = singletonMap(FIELD_KEY_1, VALUE_1);\n  protected static final Map<String, String> HASH_2 = singletonMap(FIELD_KEY_2, VALUE_2);\n\n  public StreamsCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  /**\n   * Populates a test stream with values using the i-0 format\n   * @param streamKey The stream key to populate\n   * @param count Number of entries to add\n   * @param map Map of field-value pairs for each entry\n   */\n  protected void populateTestStreamWithValues(String streamKey, int count,\n      Map<String, String> map) {\n    for (int i = 1; i <= count; i++) {\n      jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + \"-0\")), map);\n    }\n    assertEquals(count, jedis.xlen(streamKey));\n  }\n\n  @BeforeEach\n  public void setUp() {\n    setUpTestStream();\n  }\n\n  private void setUpTestStream() {\n    setUpTestStream(StreamEntryID.XGROUP_LAST_ENTRY);\n  }\n\n  private void setUpTestStream(StreamEntryID startId) {\n    jedis.del(STREAM_KEY_1);\n    jedis.del(STREAM_KEY_2);\n    try {\n      jedis.xgroupCreate(STREAM_KEY_1, GROUP_NAME, startId, true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n    try {\n      jedis.xgroupCreate(STREAM_KEY_2, GROUP_NAME, startId, true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n  }\n\n  // ========== XADD Command Tests ==========\n\n  @Test\n  public void xaddBasic() {\n    setUpTestStream();\n\n    // Test basic XADD with auto-generated ID\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XADD with multiple fields\n    Map<String, String> multiFieldHash = new HashMap<>();\n    multiFieldHash.put(\"field1\", \"value1\");\n    multiFieldHash.put(\"field2\", \"value2\");\n    multiFieldHash.put(\"field3\", \"value3\");\n\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, multiFieldHash);\n    assertNotNull(id2);\n    assertTrue(id2.compareTo(id1) > 0);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xaddWithSpecificId() {\n    setUpTestStream();\n\n    // Test XADD with specific ID\n    StreamEntryID specificId = new StreamEntryID(\"1000-0\");\n    StreamEntryID resultId = jedis.xadd(STREAM_KEY_1, specificId, HASH_1);\n    assertEquals(specificId, resultId);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XADD with ID that must be greater than previous\n    StreamEntryID nextId = new StreamEntryID(\"1001-0\");\n    StreamEntryID resultId2 = jedis.xadd(STREAM_KEY_1, nextId, HASH_2);\n    assertEquals(nextId, resultId2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xaddWithParams() {\n    setUpTestStream();\n\n    // Test XADD with maxLen parameter\n    populateTestStreamWithValues(STREAM_KEY_1, 5, HASH_1);\n\n    // Add with maxLen=3, should trim to 3 entries\n    StreamEntryID id6 = jedis.xadd(STREAM_KEY_1,\n      XAddParams.xAddParams().id(new StreamEntryID(\"6-0\")).maxLen(3), HASH_2);\n    assertNotNull(id6);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xaddErrorCases() {\n    setUpTestStream();\n\n    // Test XADD with empty hash should fail\n    try {\n      Map<String, String> emptyHash = new HashMap<>();\n      jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, emptyHash);\n      fail(\"Should throw JedisDataException for empty hash\");\n    } catch (JedisDataException expected) {\n      assertTrue(expected.getMessage().contains(\"wrong number of arguments\"));\n    }\n\n    // Test XADD with noMkStream on non-existent stream\n    StreamEntryID result = jedis.xadd(\"non-existent-stream\", XAddParams.xAddParams().noMkStream(),\n      HASH_1);\n    assertNull(result);\n  }\n\n  @ParameterizedTest\n  @CsvSource({ \"KEEP_REFERENCES,3\", \"DELETE_REFERENCES,0\" })\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithTrimmingMode(StreamDeletionPolicy trimMode, int expected) {\n    setUpTestStream();\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries to the stream\n    populateTestStreamWithValues(STREAM_KEY_1, 5, map);\n\n    // Create consumer group and read messages to create PEL entries\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3),\n      streamQuery);\n\n    // Verify PEL has entries\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingBefore.size());\n\n    // Add new entry with maxLen=3 and KEEP_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(STREAM_KEY_1,\n      XAddParams.xAddParams().id(new StreamEntryID(\"6-0\")).maxLen(3).trimmingMode(trimMode), map);\n    assertNotNull(newId);\n\n    // Stream should be trimmed to 3 entries\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(expected, pendingAfter.size());\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithTrimmingModeAcknowledged() {\n    setUpTestStream();\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries to the stream\n    populateTestStreamWithValues(STREAM_KEY_1, 5, map);\n\n    // Create consumer group and read messages\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery);\n\n    // Acknowledge the first 2 messages\n    StreamEntryID id1 = messages.get(0).getValue().get(0).getID();\n    StreamEntryID id2 = messages.get(0).getValue().get(1).getID();\n    jedis.xack(STREAM_KEY_1, GROUP_NAME, id1, id2);\n\n    // Verify PEL state\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(1, pendingBefore.size()); // Only 1 unacknowledged message\n\n    // Add new entry with maxLen=3 and ACKNOWLEDGED mode\n    StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams()\n        .id(new StreamEntryID(\"6-0\")).maxLen(3).trimmingMode(StreamDeletionPolicy.ACKNOWLEDGED),\n      map);\n    assertNotNull(newId);\n\n    // Stream length should respect acknowledgment status\n    long streamLen = jedis.xlen(STREAM_KEY_1);\n    assertEquals(4, streamLen); // Should not trim unacknowledged entries aggressively\n\n    // PEL should still contain unacknowledged entries\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(1, pendingAfter.size()); // Unacknowledged entries should remain\n  }\n\n  // ========== XTRIM Command Tests ==========\n\n  @Test\n  public void xtrimBasic() {\n    setUpTestStream();\n\n    // Add test entries\n    populateTestStreamWithValues(STREAM_KEY_1, 5, HASH_1);\n\n    // Test basic XTRIM with maxLen\n    long trimmed = jedis.xtrim(STREAM_KEY_1, 3, false);\n    assertEquals(2L, trimmed); // Should trim 2 entries\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xtrimWithParams() {\n    setUpTestStream();\n\n    // Add test entries with specific IDs\n    populateTestStreamWithValues(STREAM_KEY_1, 5, HASH_1);\n\n    // Test XTRIM with XTrimParams and exact trimming\n    long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).exactTrimming());\n    assertEquals(2L, trimmed);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XTRIM with minId - use \"4-0\" since we have entries 1-0, 2-0, 3-0, 4-0, 5-0\n    long trimmed2 = jedis.xtrim(STREAM_KEY_1,\n      XTrimParams.xTrimParams().minId(\"4-0\").exactTrimming());\n    assertEquals(1L, trimmed2); // Should trim entries with ID < 4-0 (only 3-0 should be trimmed)\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xtrimApproximate() {\n    setUpTestStream();\n\n    // Add many entries\n    populateTestStreamWithValues(STREAM_KEY_1, 10, HASH_1);\n\n    // Test approximate trimming\n    long trimmed = jedis.xtrim(STREAM_KEY_1, 5, true);\n    assertTrue(trimmed >= 0); // Approximate trimming may trim different amounts\n    assertTrue(jedis.xlen(STREAM_KEY_1) <= 10); // Should not exceed original length\n  }\n\n  @ParameterizedTest\n  @CsvSource({ \"KEEP_REFERENCES,3\", \"DELETE_REFERENCES,1\" })\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithMinIdTrimmingMode(StreamDeletionPolicy trimMode, int expected) {\n    setUpTestStream();\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries with specific IDs\n    populateTestStreamWithValues(STREAM_KEY_1, 5, map);\n\n    // Create consumer group and read messages to create PEL entries\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3),\n      streamQuery);\n\n    // Verify PEL has entries\n    List<StreamPendingEntry> pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(3, pendingBefore.size());\n\n    // Add new entry with minId=\"3-0\" and specified trimming mode\n    StreamEntryID newId = jedis.xadd(STREAM_KEY_1,\n      XAddParams.xAddParams().id(new StreamEntryID(\"6-0\")).minId(\"3-0\").trimmingMode(trimMode),\n      map);\n    assertNotNull(newId);\n\n    // Stream should have entries >= 3-0 plus the new entry\n    long streamLen = jedis.xlen(STREAM_KEY_1);\n    assertTrue(streamLen >= 3);\n\n    // Check PEL entries based on trimming mode\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(expected, pendingAfter.size());\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithApproximateTrimmingAndTrimmingMode() {\n    setUpTestStream();\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries\n    populateTestStreamWithValues(STREAM_KEY_1, 10, map);\n\n    // Create consumer group and read messages\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5),\n      streamQuery);\n\n    // Add new entry with approximate trimming and KEEP_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(STREAM_KEY_1,\n      XAddParams.xAddParams().id(new StreamEntryID(\"11-0\")).maxLen(5).approximateTrimming()\n          .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES),\n      map);\n    assertNotNull(newId);\n\n    // With approximate trimming, the exact length may vary but should be around the target\n    long streamLen = jedis.xlen(STREAM_KEY_1);\n    assertTrue(streamLen >= 5); // Should be approximately 5, but may be more due to approximation\n\n    // PEL should preserve references\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithExactTrimmingAndTrimmingMode() {\n    setUpTestStream();\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries\n    populateTestStreamWithValues(STREAM_KEY_1, 5, map);\n\n    // Create consumer group and read messages\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3),\n      streamQuery);\n\n    // Add new entry with exact trimming and DELETE_REFERENCES mode\n    StreamEntryID newId = jedis.xadd(STREAM_KEY_1,\n      XAddParams.xAddParams().id(new StreamEntryID(\"6-0\")).maxLen(3).exactTrimming()\n          .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES),\n      map);\n    assertNotNull(newId);\n\n    // With exact trimming, stream should be exactly 3 entries\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // PEL references should be cleaned up for trimmed entries\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    // Only entries that still exist in the stream should remain in PEL\n    assertTrue(pendingAfter.size() <= 3);\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xaddWithLimitAndTrimmingMode() {\n    setUpTestStream();\n    Map<String, String> map = singletonMap(\"field\", \"value\");\n\n    // Add initial entries\n    populateTestStreamWithValues(STREAM_KEY_1, 10, map);\n\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5),\n      streamQuery);\n\n    // Add new entry with limit and KEEP_REFERENCES mode (limit requires approximate trimming)\n    StreamEntryID newId = jedis.xadd(STREAM_KEY_1,\n      XAddParams.xAddParams().id(new StreamEntryID(\"11-0\")).maxLen(5).approximateTrimming() // Required\n                                                                                            // for\n                                                                                            // limit\n                                                                                            // to\n                                                                                            // work\n          .limit(2) // Limit the number of entries to examine for trimming\n          .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES),\n      map);\n    assertNotNull(newId);\n\n    // With limit, trimming may be less aggressive\n    long streamLen = jedis.xlen(STREAM_KEY_1);\n    assertTrue(streamLen >= 5); // Should be at least 5, but may be more due to limit\n\n    // PEL should preserve references\n    List<StreamPendingEntry> pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME,\n      XPendingParams.xPendingParams().count(10));\n    assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL\n  }\n\n  // ========== XACK Command Tests ==========\n\n  @Test\n  public void xackBasic() {\n    setUpTestStream();\n\n    // Add a message to the stream\n    StreamEntryID messageId = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n    assertNotNull(messageId);\n\n    // Consumer group already created in setUpTestStream(), just read message\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(1, messages.get(0).getValue().size());\n    StreamEntryID readMessageId = messages.get(0).getValue().get(0).getID();\n\n    // Test XACK\n    long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, readMessageId);\n    assertEquals(1L, acked);\n  }\n\n  @Test\n  public void xackMultipleMessages() {\n    setUpTestStream();\n\n    // Add multiple messages\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n\n    // Consumer group already created in setUpTestStream(), just read messages\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Test XACK with multiple IDs\n    StreamEntryID readId1 = messages.get(0).getValue().get(0).getID();\n    StreamEntryID readId2 = messages.get(0).getValue().get(1).getID();\n    long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2);\n    assertEquals(2L, acked);\n  }\n\n  @Test\n  public void xackNonExistentMessage() {\n    setUpTestStream();\n\n    // Consumer group already created in setUpTestStream()\n    // Test XACK with non-existent message ID\n    StreamEntryID nonExistentId = new StreamEntryID(\"999-0\");\n    long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, nonExistentId);\n    assertEquals(0L, acked); // Should return 0 for non-existent message\n  }\n\n  // ========== XDEL Command Tests ==========\n\n  @Test\n  public void xdelBasic() {\n    setUpTestStream();\n\n    // Add test entries\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDEL with single ID\n    long deleted = jedis.xdel(STREAM_KEY_1, id1);\n    assertEquals(1L, deleted);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xdelMultipleEntries() {\n    setUpTestStream();\n\n    // Add test entries\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"3-0\"), HASH_1);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDEL with multiple IDs\n    long deleted = jedis.xdel(STREAM_KEY_1, id1, id3);\n    assertEquals(2L, deleted);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xdelNonExistentEntries() {\n    setUpTestStream();\n\n    // Add one entry\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDEL with mix of existing and non-existent IDs\n    StreamEntryID nonExistentId = new StreamEntryID(\"999-0\");\n    long deleted = jedis.xdel(STREAM_KEY_1, id1, nonExistentId);\n    assertEquals(1L, deleted); // Should only delete the existing entry\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  public void xdelEmptyStream() {\n    setUpTestStream();\n\n    // Test XDEL on empty stream\n    StreamEntryID nonExistentId = new StreamEntryID(\"1-0\");\n    long deleted = jedis.xdel(STREAM_KEY_1, nonExistentId);\n    assertEquals(0L, deleted);\n  }\n\n  // ========== XACKDEL Command Tests ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xackdelBasic() {\n    setUpTestStream();\n\n    // Add a message to the stream\n    StreamEntryID messageId = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    assertNotNull(messageId);\n\n    // Consumer group already created in setUpTestStream(), read message\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(1, messages.get(0).getValue().size());\n    StreamEntryID readMessageId = messages.get(0).getValue().get(0).getID();\n\n    // Test XACKDEL - should acknowledge and delete the message\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME,\n      readMessageId);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify message is deleted from stream\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xackdelWithTrimMode() {\n    setUpTestStream();\n\n    // Add multiple messages\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n\n    // Consumer group already created, read messages\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Test XACKDEL with KEEP_REFERENCES mode\n    StreamEntryID readId1 = messages.get(0).getValue().get(0).getID();\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME,\n      StreamDeletionPolicy.KEEP_REFERENCES, readId1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify one message is deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xackdelUnreadMessages() {\n    setUpTestStream();\n\n    // Add test entries but don't read them\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n\n    // Test XACKDEL on unread messages - should return NOT_FOUND for PEL\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1);\n\n    assertThat(results, hasSize(1));\n    // Should return NOT_FOUND because message was never read by the consumer group\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0));\n\n    // Stream should still contain the message\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xackdelMultipleMessages() {\n    setUpTestStream();\n\n    // Add multiple messages\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"3-0\"), HASH_1);\n\n    // Read all messages\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(3, messages.get(0).getValue().size());\n\n    // Test XACKDEL with multiple IDs\n    StreamEntryID readId1 = messages.get(0).getValue().get(0).getID();\n    StreamEntryID readId2 = messages.get(0).getValue().get(1).getID();\n    List<StreamEntryDeletionResult> results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1,\n      readId2);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(1));\n\n    // Verify two messages are deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  // ========== XDELEX Command Tests ==========\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexBasic() {\n    setUpTestStream();\n\n    // Add test entries\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES)\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify entry is deleted from stream\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexWithTrimMode() {\n    setUpTestStream();\n\n    // Add test entries\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n\n    // Test XDELEX with DELETE_REFERENCES mode\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1,\n      StreamDeletionPolicy.DELETE_REFERENCES, id1);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n\n    // Verify entry is deleted from stream\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexMultipleEntries() {\n    setUpTestStream();\n\n    // Add test entries\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"3-0\"), HASH_1);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDELEX with multiple IDs\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1, id3);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(1));\n\n    // Verify two entries are deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexNonExistentEntries() {\n    setUpTestStream();\n\n    // Add one entry\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Test XDELEX with mix of existing and non-existent IDs\n    StreamEntryID nonExistentId = new StreamEntryID(\"999-0\");\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Existing entry\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(1)); // Non-existent entry\n\n    // Verify existing entry is deleted\n    assertEquals(0L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexWithConsumerGroups() {\n    setUpTestStream();\n\n    // Add test entries\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), HASH_1);\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), HASH_2);\n\n    // Read messages to add them to PEL\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME,\n      CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams);\n\n    assertEquals(1, messages.size());\n    assertEquals(2, messages.get(0).getValue().size());\n\n    // Acknowledge only the first message\n    StreamEntryID readId1 = messages.get(0).getValue().get(0).getID();\n    StreamEntryID readId2 = messages.get(0).getValue().get(1).getID();\n    jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1);\n\n    // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1,\n      StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2);\n    assertThat(results, hasSize(2));\n    assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged\n    assertEquals(StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED,\n      results.get(1)); // id2 not\n    // acknowledged\n\n    // Verify only acknowledged entry was deleted\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexEmptyStream() {\n    setUpTestStream();\n\n    // Test XDELEX on empty stream\n    StreamEntryID nonExistentId = new StreamEntryID(\"1-0\");\n    List<StreamEntryDeletionResult> results = jedis.xdelex(STREAM_KEY_1, nonExistentId);\n    assertThat(results, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void xdelexNotAcknowledged() {\n    setUpTestStream();\n\n    String groupName = \"test_group\";\n\n    // Add initial entries and create consumer group\n    Map<String, String> entry1 = singletonMap(\"field1\", \"value1\");\n    jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"1-0\"), entry1);\n    jedis.xgroupCreate(STREAM_KEY_1, groupName, new StreamEntryID(\"0-0\"), true);\n\n    // Read one message to create PEL entry\n    String consumerName = \"consumer1\";\n    Map<String, StreamEntryID> streamQuery = singletonMap(STREAM_KEY_1,\n      StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(1),\n      streamQuery);\n\n    // Add a new entry that was never delivered to any consumer\n    Map<String, String> entry2 = singletonMap(\"field4\", \"value4\");\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID(\"2-0\"), entry2);\n\n    // Verify initial state\n    StreamPendingSummary pending = jedis.xpending(STREAM_KEY_1, groupName);\n    assertEquals(1L, pending.getTotal()); // Only id1 is in PEL\n\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY_1);\n    assertEquals(2L, info.getLength()); // Stream has 2 entries\n\n    // Test XDELEX with ACKNOWLEDGED policy on entry that was never delivered\n    // This should return NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED since id2 was never\n    // delivered to any consumer\n    List<StreamEntryDeletionResult> result = jedis.xdelex(STREAM_KEY_1,\n      StreamDeletionPolicy.ACKNOWLEDGED, id2);\n    assertThat(result, hasSize(1));\n    assertEquals(StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED,\n      result.get(0));\n  }\n\n  // ========== XREADGROUP CLAIM Tests ==========\n\n  private static final String CONSUMER_1 = \"consumer-1\";\n  private static final String CONSUMER_2 = \"consumer-2\";\n  private static final long IDLE_TIME_MS = 5;\n\n  Map<String, StreamEntryID> beforeEachClaimTest() throws InterruptedException {\n    setUpTestStream(new StreamEntryID(\"0-0\"));\n\n    // Produce two entries\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n    Map<String, StreamEntryID> streams = singletonMap(STREAM_KEY_1,\n        StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    jedis.xreadGroup(GROUP_NAME, CONSUMER_1, XReadGroupParams.xReadGroupParams().count(10),\n        streams);\n\n    // Ensure idle time so entries are claimable\n    Thread.sleep(IDLE_TIME_MS);\n    return streams;\n  }\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimReturnsMetadataOrdered() throws InterruptedException {\n    Map<String, StreamEntryID> streams = beforeEachClaimTest();\n\n    // Produce fresh entries that are NOT claimed (not pending)\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n\n    // Read with consumer-2 using CLAIM\n    List<Map.Entry<String, List<StreamEntry>>> consumer2Result = jedis.xreadGroup(GROUP_NAME,\n        CONSUMER_2, XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).count(10), streams);\n\n    assertNotNull(consumer2Result);\n    assertEquals(1, consumer2Result.size());\n\n    List<StreamEntry> entries = consumer2Result.get(0).getValue();\n    assertEquals(4, entries.size());\n\n    long claimedCount = entries.stream().filter(StreamEntry::isClaimed).count();\n    long freshCount = entries.size() - claimedCount;\n\n    assertEquals(2, claimedCount);\n    assertEquals(2, freshCount);\n\n    // Assert order: pending entries are first\n    StreamEntry first = entries.get(0);\n    StreamEntry second = entries.get(1);\n    StreamEntry third = entries.get(2);\n    StreamEntry fourth = entries.get(3);\n\n    // Claimed entries\n    assertTrue(first.isClaimed());\n    assertTrue(second.isClaimed());\n    assertTrue(first.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n    assertTrue(second.getMillisElapsedFromDelivery() >= IDLE_TIME_MS);\n    assertEquals(HASH_1, first.getFields());\n\n    // Fresh entries\n    assertFalse(third.isClaimed());\n    assertFalse(fourth.isClaimed());\n    assertEquals(Long.valueOf(0), third.getDeliveredCount());\n    assertEquals(Long.valueOf(0), fourth.getDeliveredCount());\n    assertEquals(Long.valueOf(0), third.getMillisElapsedFromDelivery());\n    assertEquals(Long.valueOf(0), fourth.getMillisElapsedFromDelivery());\n    assertEquals(HASH_1, fourth.getFields());\n  }\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimMovesPendingFromC1ToC2AndRemainsPendingUntilAck()\n      throws InterruptedException {\n    Map<String, StreamEntryID> streams = beforeEachClaimTest();\n\n    // Verify pending belongs to consumer-1\n    StreamPendingSummary before = jedis.xpending(STREAM_KEY_1, GROUP_NAME);\n    assertEquals(2L, before.getTotal());\n    assertEquals(2L, before.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n\n    // Claim with consumer-2\n    List<Map.Entry<String, List<StreamEntry>>> res = jedis.xreadGroup(GROUP_NAME, CONSUMER_2,\n        XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).count(10), streams);\n\n    assertNotNull(res);\n    assertEquals(1, res.size());\n\n    List<StreamEntry> entries = res.get(0).getValue();\n    long claimed = entries.stream().filter(StreamEntry::isClaimed).count();\n    assertEquals(2, claimed);\n\n    // After claim: entries are pending for consumer-2 (moved), not acked yet\n    StreamPendingSummary afterClaim = jedis.xpending(STREAM_KEY_1, GROUP_NAME);\n    assertEquals(2L, afterClaim.getTotal());\n    assertEquals(0L, afterClaim.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n    assertEquals(2L, afterClaim.getConsumerMessageCount().getOrDefault(CONSUMER_2, 0L).longValue());\n\n    // XACK the claimed entries -> PEL should become empty\n    long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, entries.get(0).getID(),\n        entries.get(1).getID());\n    assertEquals(2, acked);\n\n    StreamPendingSummary afterAck = jedis.xpending(STREAM_KEY_1, GROUP_NAME);\n    assertEquals(0L, afterAck.getTotal());\n  }\n\n  @Test\n  @SinceRedisVersion(V8_4_0_STRING)\n  public void xreadgroupClaimWithNoackDoesNotCreatePendingAndRemovesClaimedFromPel()\n      throws InterruptedException {\n    Map<String, StreamEntryID> streams = beforeEachClaimTest();\n\n    // Verify pending belongs to consumer-1\n    StreamPendingSummary before = jedis.xpending(STREAM_KEY_1, GROUP_NAME);\n    assertEquals(2L, before.getTotal());\n    assertEquals(2L, before.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n    assertEquals(0L, before.getConsumerMessageCount().getOrDefault(CONSUMER_2, 0L).longValue());\n\n    // Also produce fresh entries that should not be added to PEL when NOACK is set\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n\n    // Claim with NOACK using consumer-2\n    List<Map.Entry<String, List<StreamEntry>>> res = jedis.xreadGroup(GROUP_NAME, CONSUMER_2,\n        XReadGroupParams.xReadGroupParams().claim(IDLE_TIME_MS).noAck().count(10), streams);\n\n    assertNotNull(res);\n    assertEquals(1, res.size());\n\n    List<StreamEntry> entries = res.get(0).getValue();\n    long claimedCount = entries.stream().filter(StreamEntry::isClaimed).count();\n    long freshCount = entries.size() - claimedCount;\n\n    assertEquals(2, claimedCount);\n    assertEquals(2, freshCount);\n\n    // After NOACK read, previously pending entries remain pending (NOACK does not remove them)\n    StreamPendingSummary afterNoack = jedis.xpending(STREAM_KEY_1, GROUP_NAME);\n    assertEquals(2L, afterNoack.getTotal());\n\n    // Claimed entries remain pending and are now owned by consumer-2 (CLAIM reassigns ownership).\n    // Fresh entries were not added to PEL.\n    assertEquals(0L, afterNoack.getConsumerMessageCount().getOrDefault(CONSUMER_1, 0L).longValue());\n    assertEquals(2L, afterNoack.getConsumerMessageCount().getOrDefault(CONSUMER_2, 0L).longValue());\n  }\n\n  @Test\n  public void xreadGroupPreservesFieldOrder() {\n    String streamKey = \"field-order-stream\";\n    String groupName = \"field-order-group\";\n    String consumerName = \"field-order-consumer\";\n\n    // Use LinkedHashMap to ensure insertion order: a, z, m\n    Map<String, String> fields = new LinkedHashMap<>();\n    fields.put(\"a\", \"1\");\n    fields.put(\"z\", \"4\");\n    fields.put(\"m\", \"2\");\n\n    jedis.xadd(streamKey, StreamEntryID.NEW_ENTRY, fields);\n    jedis.xgroupCreate(streamKey, groupName, new StreamEntryID(\"0-0\"), false);\n\n    Map<String, StreamEntryID> streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    List<Map.Entry<String, List<StreamEntry>>> result = jedis.xreadGroup(groupName, consumerName,\n        XReadGroupParams.xReadGroupParams().count(1), streamQuery);\n\n    assertEquals(1, result.size());\n    assertEquals(1, result.get(0).getValue().size());\n\n    StreamEntry entry = result.get(0).getValue().get(0);\n    Map<String, String> returnedFields = entry.getFields();\n\n    // Verify field order is preserved - this will fail with HashMap, pass with LinkedHashMap\n    String[] expectedOrder = {\"a\", \"z\", \"m\"};\n    String[] actualOrder = returnedFields.keySet().toArray(new String[0]);\n\n    assertEquals(expectedOrder.length, actualOrder.length, \"Field count should match\");\n    for (int i = 0; i < expectedOrder.length; i++) {\n      assertEquals(expectedOrder[i], actualOrder[i],\n          String.format(\"Field order mismatch at position %d: expected '%s' but got '%s'. \" +\n              \"Full order: expected [a, z, m], actual %s\",\n              i, expectedOrder[i], actualOrder[i], java.util.Arrays.toString(actualOrder)));\n    }\n  }\n\n  @Test\n  public void xreadAsMapPreservesStreamOrder() {\n    // Test that xreadAsMap preserves the order of streams when reading from multiple streams\n    String streamKey1 = \"{stream-order}-test-1\";\n    String streamKey2 = \"{stream-order}-test-2\";\n    String streamKey3 = \"{stream-order}-test-3\";\n\n    // Add entries to streams in specific order\n    Map<String, String> fields = new LinkedHashMap<>();\n    fields.put(\"field\", \"value1\");\n    jedis.xadd(streamKey1, StreamEntryID.NEW_ENTRY, fields);\n\n    fields.put(\"field\", \"value2\");\n    jedis.xadd(streamKey2, StreamEntryID.NEW_ENTRY, fields);\n\n    fields.put(\"field\", \"value3\");\n    jedis.xadd(streamKey3, StreamEntryID.NEW_ENTRY, fields);\n\n    // Read from multiple streams in specific order\n    Map<String, StreamEntryID> streams = new LinkedHashMap<>();\n    streams.put(streamKey1, new StreamEntryID(\"0-0\"));\n    streams.put(streamKey2, new StreamEntryID(\"0-0\"));\n    streams.put(streamKey3, new StreamEntryID(\"0-0\"));\n\n    Map<String, List<StreamEntry>> result = jedis.xreadAsMap(\n        XReadParams.xReadParams().count(10), streams);\n\n    assertNotNull(result);\n    assertEquals(3, result.size());\n\n    // Verify that the order of streams in the result matches the order in the request\n    String[] expectedOrder = {streamKey1, streamKey2, streamKey3};\n    String[] actualOrder = result.keySet().toArray(new String[0]);\n\n    assertEquals(expectedOrder.length, actualOrder.length, \"Stream count should match\");\n    for (int i = 0; i < expectedOrder.length; i++) {\n      assertEquals(expectedOrder[i], actualOrder[i],\n          String.format(\"Stream order mismatch at position %d: expected '%s' but got '%s'\",\n              i, expectedOrder[i], actualOrder[i]));\n    }\n  }\n\n  // ========== Idempotent Producer Tests ==========\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpAuto() {\n\n    // Add entry with IDMPAUTO\n    Map<String, String> message = new HashMap<>();\n    message.put(\"order\", \"12345\");\n    message.put(\"amount\", \"100.00\");\n\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\"),\n        message);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add same message again with same producer - should be rejected as duplicate\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\"),\n        message);\n    assertEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1)); // Stream length unchanged\n\n    // Add same message with different producer - should succeed\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-2\"),\n        message);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Add different message with same producer - should succeed\n    Map<String, String> message2 = new HashMap<>();\n    message2.put(\"order\", \"67890\");\n    message2.put(\"amount\", \"200.00\");\n\n    StreamEntryID id4 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmpAuto(\"producer-1\"),\n        message2);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmp() {\n\n    // Add entry with explicit idempotent ID\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), HASH_1);\n    assertNotNull(id1);\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with same producer and idempotent ID - should be rejected\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"),\n        HASH_2); // Different content, but same IDs\n    assertEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with same producer but different idempotent ID - should succeed\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-1\", \"iid-002\"), HASH_1);\n    assertNotNull(id3);\n    assertEquals(2L, jedis.xlen(STREAM_KEY_1));\n\n    // Add with different producer but same idempotent ID - should succeed\n    StreamEntryID id4 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmp(\"producer-2\", \"iid-001\"), HASH_1);\n    assertNotNull(id4);\n    assertEquals(3L, jedis.xlen(STREAM_KEY_1));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXcfgset() {\n    // Configure idempotent producer settings\n    String result = jedis.xcfgset(STREAM_KEY_1,\n        redis.clients.jedis.params.XCfgSetParams.xCfgSetParams().idmpDuration(1000)\n            .idmpMaxsize(500));\n    assertEquals(\"OK\", result);\n\n    // Verify settings via XINFO STREAM\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY_1);\n    assertEquals(Long.valueOf(1000), info.getIdmpDuration());\n    assertEquals(Long.valueOf(500), info.getIdmpMaxsize());\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXinfoStreamIdempotentFields() {\n    // Configure idempotent settings\n    jedis.xcfgset(STREAM_KEY_1,\n        redis.clients.jedis.params.XCfgSetParams.xCfgSetParams().idmpDuration(100)\n            .idmpMaxsize(100));\n\n    // Add some entries with idempotent IDs\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), HASH_1);\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmp(\"producer-1\", \"iid-002\"), HASH_2);\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmp(\"producer-2\", \"iid-001\"), HASH_1);\n\n    // Try to add a duplicate\n    jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().idmp(\"producer-1\", \"iid-001\"), HASH_2);\n\n    // Check XINFO STREAM response\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY_1);\n\n    // Verify idempotent configuration fields\n    assertEquals(Long.valueOf(100), info.getIdmpDuration());\n    assertEquals(Long.valueOf(100), info.getIdmpMaxsize());\n\n    // Verify idempotent statistics fields\n    assertEquals(Long.valueOf(2), info.getPidsTracked()); // 2 producers\n    assertEquals(Long.valueOf(3), info.getIidsTracked()); // 3 unique IDs\n    assertEquals(Long.valueOf(3), info.getIidsAdded()); // 3 entries added\n    assertEquals(Long.valueOf(1), info.getIidsDuplicates()); // 1 duplicate rejected\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXaddIdmpWithTrimming() {\n    // Add first entry with IDMPAUTO and trimming\n    StreamEntryID id1 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmpAuto(\"producer-1\").maxLen(2), HASH_1);\n    assertNotNull(id1);\n\n    // Add duplicate - should return same ID and not add new entry\n    StreamEntryID id2 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmpAuto(\"producer-1\").maxLen(2), HASH_1);\n    assertEquals(id1, id2); // Duplicate returns same ID\n    assertEquals(1, jedis.xlen(STREAM_KEY_1)); // Still 1 entry\n\n    // Add different message - should add new entry and trim\n    StreamEntryID id3 = jedis.xadd(STREAM_KEY_1,\n        XAddParams.xAddParams().idmpAuto(\"producer-1\").maxLen(2), HASH_2);\n    assertNotNull(id3);\n    assertNotEquals(id1, id3); // Different IDs\n    assertEquals(2, jedis.xlen(STREAM_KEY_1)); // Now 2 entries\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void testXcfgsetDefaults() {\n    jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1);\n\n    // Verify default values\n    StreamInfo info = jedis.xinfoStream(STREAM_KEY_1);\n    assertEquals(100L, info.getIdmpDuration());\n    assertEquals(100L, info.getIdmpMaxsize());\n\n    assertEquals(\"OK\", jedis.xcfgset(STREAM_KEY_1,\n        XCfgSetParams.xCfgSetParams().idmpDuration(200).idmpMaxsize(200)));\n\n    StreamInfo infoAfter = jedis.xinfoStream(STREAM_KEY_1);\n    assertEquals(200L, infoAfter.getIdmpDuration());\n    assertEquals(200L, infoAfter.getIdmpMaxsize());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/StringValuesCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.params.SetParams.setParams;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.resps.LCSMatchResult;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@Tag(\"integration\")\npublic abstract class StringValuesCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  public StringValuesCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void setAndGet() {\n    String status = jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    String value = jedis.get(\"foo\");\n    assertEquals(\"bar\", value);\n\n    assertNull(jedis.get(\"bar\"));\n  }\n\n  @Test\n  public void getSet() {\n    String value = jedis.getSet(\"foo\", \"bar\");\n    assertNull(value);\n    value = jedis.get(\"foo\");\n    assertEquals(\"bar\", value);\n  }\n\n  @Test\n  public void setGetWithParams() {\n    jedis.del(\"foo\");\n\n    // no previous, return null\n    assertNull(jedis.setGet(\"foo\", \"bar\", setParams().nx()));\n\n    // key already exists, new value should not be set, previous value should be bbar\n    assertEquals(\"bar\", jedis.setGet(\"foo\", \"foobar\", setParams().nx()));\n\n    assertEquals(\"bar\", jedis.setGet(\"foo\", \"foobar\", setParams().xx()));\n  }\n\n  @Test\n  public void getDel() {\n    String status = jedis.set(\"foo\", \"bar\");\n    assertEquals(\"OK\", status);\n\n    String value = jedis.getDel(\"foo\");\n    assertEquals(\"bar\", value);\n\n    assertNull(jedis.get(\"foo\"));\n  }\n\n  @Test\n  public void getEx() {\n    assertNull(jedis.getEx(\"foo\", GetExParams.getExParams().ex(1)));\n    jedis.set(\"foo\", \"bar\");\n\n    assertEquals(\"bar\", jedis.getEx(\"foo\", GetExParams.getExParams().ex(10)));\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 0 && ttl <= 10);\n\n    assertEquals(\"bar\", jedis.getEx(\"foo\", GetExParams.getExParams().px(20000L)));\n    ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 10 && ttl <= 20);\n\n    assertEquals(\"bar\",\n      jedis.getEx(\"foo\", GetExParams.getExParams().exAt(System.currentTimeMillis() / 1000 + 30)));\n    ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 20 && ttl <= 30);\n\n    assertEquals(\"bar\",\n      jedis.getEx(\"foo\", GetExParams.getExParams().pxAt(System.currentTimeMillis() + 40000L)));\n    ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 30 && ttl <= 40);\n\n    assertEquals(\"bar\", jedis.getEx(\"foo\", GetExParams.getExParams().persist()));\n    assertEquals(-1, jedis.ttl(\"foo\"));\n  }\n\n  @Test\n  public void mget() {\n    List<String> values = jedis.mget(\"foo\", \"bar\");\n    List<String> expected = new ArrayList<String>();\n    expected.add(null);\n    expected.add(null);\n\n    assertEquals(expected, values);\n\n    jedis.set(\"foo\", \"bar\");\n\n    expected = new ArrayList<String>();\n    expected.add(\"bar\");\n    expected.add(null);\n    values = jedis.mget(\"foo\", \"bar\");\n\n    assertEquals(expected, values);\n\n    jedis.set(\"bar\", \"foo\");\n\n    expected = new ArrayList<String>();\n    expected.add(\"bar\");\n    expected.add(\"foo\");\n    values = jedis.mget(\"foo\", \"bar\");\n\n    assertEquals(expected, values);\n  }\n\n  @Test\n  public void setnx() {\n    assertEquals(1, jedis.setnx(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n\n    assertEquals(0, jedis.setnx(\"foo\", \"bar2\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n  }\n\n  @Test\n  public void setex() {\n    String status = jedis.setex(\"foo\", 20, \"bar\");\n    assertEquals(\"OK\", status);\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 0 && ttl <= 20);\n  }\n\n  @Test\n  public void mset() {\n    String status = jedis.mset(\"foo\", \"bar\", \"bar\", \"foo\");\n    assertEquals(\"OK\", status);\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(\"foo\", jedis.get(\"bar\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void msetnx() {\n    assertEquals(1, jedis.msetnx(\"foo\", \"bar\", \"bar\", \"foo\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(\"foo\", jedis.get(\"bar\"));\n\n    assertEquals(0, jedis.msetnx(\"foo\", \"bar1\", \"bar2\", \"foo2\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(\"foo\", jedis.get(\"bar\"));\n  }\n\n  @Test\n  public void incr() {\n    assertEquals(1, jedis.incr(\"foo\"));\n    assertEquals(2, jedis.incr(\"foo\"));\n  }\n\n  @Test\n  public void incrWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.incr(\"foo\"));\n  }\n\n  @Test\n  public void incrBy() {\n    assertEquals(2, jedis.incrBy(\"foo\", 2));\n    assertEquals(5, jedis.incrBy(\"foo\", 3));\n  }\n\n  @Test\n  public void incrByWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.incrBy(\"foo\", 2));\n  }\n\n  @Test\n  public void incrByFloat() {\n    assertEquals(10.5, jedis.incrByFloat(\"foo\", 10.5), 0.0);\n    assertEquals(10.6, jedis.incrByFloat(\"foo\", 0.1), 0.0);\n  }\n\n  @Test\n  public void incrByFloatWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.incrByFloat(\"foo\", 2d));\n  }\n\n  @Test\n  public void decrWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.decr(\"foo\"));\n  }\n\n  @Test\n  public void decr() {\n    assertEquals(-1, jedis.decr(\"foo\"));\n    assertEquals(-2, jedis.decr(\"foo\"));\n  }\n\n  @Test\n  public void decrBy() {\n    assertEquals(-2, jedis.decrBy(\"foo\", 2));\n    assertEquals(-4, jedis.decrBy(\"foo\", 2));\n  }\n\n  @Test\n  public void decrByWrongValue() {\n    jedis.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> jedis.decrBy(\"foo\", 2));\n  }\n\n  @Test\n  public void append() {\n    assertEquals(3, jedis.append(\"foo\", \"bar\"));\n    assertEquals(\"bar\", jedis.get(\"foo\"));\n    assertEquals(6, jedis.append(\"foo\", \"bar\"));\n    assertEquals(\"barbar\", jedis.get(\"foo\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void substr() {\n    jedis.set(\"s\", \"This is a string\");\n    assertEquals(\"This\", jedis.substr(\"s\", 0, 3));\n    assertEquals(\"ing\", jedis.substr(\"s\", -3, -1));\n    assertEquals(\"This is a string\", jedis.substr(\"s\", 0, -1));\n    assertEquals(\" string\", jedis.substr(\"s\", 9, 100000));\n  }\n\n  @Test\n  public void strlen() {\n    String str = \"This is a string\";\n    jedis.set(\"s\", str);\n    assertEquals(str.length(), jedis.strlen(\"s\"));\n  }\n\n  @Test\n  public void incrLargeNumbers() {\n    assertEquals(1, jedis.incr(\"foo\"));\n    assertEquals(1L + Integer.MAX_VALUE, jedis.incrBy(\"foo\", Integer.MAX_VALUE));\n  }\n\n  @Test\n  public void incrReallyLargeNumbers() {\n    jedis.set(\"foo\", Long.toString(Long.MAX_VALUE));\n    assertThrows(JedisDataException.class, () -> jedis.incr(\"foo\")); // Should throw an exception\n  }\n\n  @Test\n  public void psetex() {\n    String status = jedis.psetex(\"foo\", 20000, \"bar\");\n    assertEquals(\"OK\", status);\n    long ttl = jedis.ttl(\"foo\");\n    assertTrue(ttl > 0 && ttl <= 20000);\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lcs() {\n    jedis.mset(\"key1\", \"ohmytext\", \"key2\", \"mynewtext\");\n\n    LCSMatchResult stringMatchResult = jedis.lcs(\"key1\", \"key2\", LCSParams.LCSParams());\n    assertEquals(\"mytext\", stringMatchResult.getMatchString());\n\n    stringMatchResult = jedis.lcs(\"key1\", \"key2\", LCSParams.LCSParams().idx().withMatchLen());\n    assertEquals(stringMatchResult.getLen(), 6);\n    assertEquals(2, stringMatchResult.getMatches().size());\n\n    stringMatchResult = jedis.lcs(\"key1\", \"key2\", LCSParams.LCSParams().idx().minMatchLen(10));\n    assertEquals(0, stringMatchResult.getMatches().size());\n  }\n\n  // MSETEX NX + expiration matrix\n  static Stream<Arguments> msetexNxArgsProvider() {\n    return Stream.of(Arguments.of(\"EX\", new MSetExParams().nx().ex(5)),\n      Arguments.of(\"PX\", new MSetExParams().nx().px(5000)),\n      Arguments.of(\"EXAT\", new MSetExParams().nx().exAt(System.currentTimeMillis() / 1000 + 5)),\n      Arguments.of(\"PXAT\", new MSetExParams().nx().pxAt(System.currentTimeMillis() + 5000)),\n      Arguments.of(\"KEEPTTL\", new MSetExParams().nx().keepTtl()));\n  }\n\n  @ParameterizedTest(name = \"MSETEX NX + {0}\")\n  @MethodSource(\"msetexNxArgsProvider\")\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetexNx_parametrized(String optionLabel, MSetExParams params) {\n    String k1 = \"{t}msetex:unified:k1\";\n    String k2 = \"{t}msetex:unified:k2\";\n\n    boolean result = jedis.msetex(params, k1, \"v1\", k2, \"v2\");\n    assertTrue(result);\n\n    long ttl = jedis.ttl(k1);\n    if (\"KEEPTTL\".equals(optionLabel)) {\n      assertEquals(-1L, ttl);\n    } else {\n      assertTrue(ttl > 0L);\n    }\n  }\n\n  @Test\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetexXxExAt() {\n    String k1 = \"{t}msetex:unified:xx:k1\";\n    String k2 = \"{t}msetex:unified:xx:k2\";\n\n    // First set the keys so they exist (XX requires existing keys)\n    jedis.set(k1, \"initial1\");\n    jedis.set(k2, \"initial2\");\n\n    // Now use MSETEX with XX and EXAT\n    long expiryTimestamp = System.currentTimeMillis() / 1000 + 5;\n    MSetExParams params = new MSetExParams().xx().exAt(expiryTimestamp);\n    boolean result = jedis.msetex(params, k1, \"v1\", k2, \"v2\");\n    assertTrue(result);\n\n    // Verify values were updated\n    assertEquals(\"v1\", jedis.get(k1));\n    assertEquals(\"v2\", jedis.get(k2));\n\n    // Verify TTL is set\n    long ttl = jedis.ttl(k1);\n    assertTrue(ttl > 0L);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/UnifiedJedisCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.CommandsTestsParameters;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@Tag(\"integration\")\npublic abstract class UnifiedJedisCommandsTestBase {\n\n  /**\n   * Input data for parameterized tests. In principle all subclasses of this\n   * class should be parameterized tests, to run with several versions of RESP.\n   *\n   * @see CommandsTestsParameters#respVersions()\n   */\n  protected final RedisProtocol protocol;\n\n  protected UnifiedJedis jedis;\n\n  protected static EndpointConfig endpoint;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> endpoint);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      () -> endpoint);\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n\n  /**\n   * The RESP protocol is to be injected by the subclasses, usually via JUnit\n   * parameterized tests, because most of the subclassed tests are meant to be\n   * executed against multiple RESP versions. For the special cases where a single\n   * RESP version is relevant, we still force the subclass to be explicit and\n   * call this constructor.\n   *\n   * @param protocol The RESP protocol to use during the tests.\n   */\n  public UnifiedJedisCommandsTestBase(RedisProtocol protocol) {\n    this.protocol = protocol;\n  }\n\n  /**\n   * Subclasses provide specific UnifiedJedis setup.\n   */\n  protected abstract UnifiedJedis createTestClient();\n\n  protected void clearData() {\n    if (jedis != null) {\n      jedis.flushAll();\n    }\n  }\n\n  @BeforeEach\n  void setUpBase() {\n    jedis = createTestClient();\n    clearData();\n  }\n\n  @AfterEach\n  void tearDownBase() {\n    if (jedis != null) {\n      jedis.close();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/VectorSetCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified;\n\nimport static java.util.Arrays.asList;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.everyItem;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasItems;\nimport static org.hamcrest.Matchers.is;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.util.VectorTestUtils.floatArrayToFP32Bytes;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\nimport org.junit.jupiter.api.TestInfo;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.VAddParams;\nimport redis.clients.jedis.params.VSimParams;\nimport redis.clients.jedis.resps.RawVector;\nimport redis.clients.jedis.resps.VSimScoreAttribs;\nimport redis.clients.jedis.resps.VectorInfo;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.VectorTestUtils;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.hamcrest.Matchers.*;\n\n@Tag(\"integration\")\n@Tag(\"vector-set\")\npublic abstract class VectorSetCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  public VectorSetCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  /**\n   * Test the basic VADD method with float array. Overload 1: vadd(String key, float[] vector,\n   * String element)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFloatArray(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:F\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Add a new element\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n\n    // Test duplicate addition - should return false\n    result = jedis.vadd(testKey, vector, elementId);\n    assertFalse(result);\n\n    // Cardinality should remain the same\n    assertEquals(1L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VADD method with float array and parameters. Overload 2: vadd(String key, float[] vector,\n   * String element, VAddParams params)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFloatArrayAndParams(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:G\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Create parameters\n    VAddParams params = new VAddParams();\n\n    // Add a new element with parameters\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n  }\n\n  /**\n   * Test VADD method with FP32 byte blob. Overload 3: vaddFP32(String key, byte[] vectorBlob,\n   * String element)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFP32ByteBlob(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:H\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Convert float array to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(vector);\n\n    // Add a new element with FP32 byte blob\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n  }\n\n  /**\n   * Test VADD method with FP32 byte blob and parameters. Overload 4: vaddFP32(String key, byte[]\n   * vectorBlob, String element, VAddParams params)\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithFP32ByteBlobAndParams(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:I\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Convert float array to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(vector);\n\n    // Create parameters\n    VAddParams params = new VAddParams();\n\n    // Add a new element with FP32 byte blob and parameters\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n  }\n\n  /**\n   * Test VADD with quantization parameters. Demonstrates how quantization parameters can be used\n   * with VADD.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithQuantization(TestInfo testInfo) {\n    String baseKey = testInfo.getDisplayName() + \":test:vector:set\";\n    float[] vector = { 1.0f, 2.0f };\n    // Test with basic VADD first to establish a baseline\n    String defaultKey = baseKey + \":default\";\n    jedis.del(defaultKey);\n    boolean result = jedis.vadd(defaultKey, vector, \"point:DEFAULT\");\n    assertTrue(result);\n\n    List<Double> defaultVector = jedis.vemb(defaultKey, \"point:DEFAULT\");\n    assertEquals(2, defaultVector.size());\n    assertEquals(1.0, defaultVector.get(0), 0.01);\n    assertEquals(2.0, defaultVector.get(1), 0.01);\n    assertEquals(1L, jedis.vcard(defaultKey));\n\n    // Test with Q8 quantization parameters\n    String q8Key = baseKey + \":q8\";\n    VAddParams quantParams = new VAddParams().q8();\n    jedis.del(q8Key);\n    result = jedis.vadd(q8Key, vector, \"point:Q8\", quantParams);\n    assertTrue(result);\n\n    List<Double> quantVector = jedis.vemb(q8Key, \"point:Q8\");\n    assertEquals(2, quantVector.size());\n    assertEquals(1.0, quantVector.get(0), 0.01);\n    assertEquals(2.0, quantVector.get(1), 0.01);\n    assertEquals(1L, jedis.vcard(q8Key));\n\n    // Test with NOQUANT quantization parameters\n    String noQuantKey = baseKey + \":noQuant\";\n    VAddParams noQuantParams = new VAddParams().q8();\n    jedis.del(noQuantKey);\n    result = jedis.vadd(noQuantKey, vector, \"point:NOQUANT\", noQuantParams);\n    assertTrue(result);\n\n    List<Double> noQuantVector = jedis.vemb(noQuantKey, \"point:NOQUANT\");\n    assertEquals(2, noQuantVector.size());\n    assertEquals(1.0, noQuantVector.get(0), 0.01);\n    assertEquals(2.0, noQuantVector.get(1), 0.01);\n    assertEquals(1L, jedis.vcard(noQuantKey));\n  }\n\n  /**\n   * Test VADD with dimension reduction using float array. Verifies that high-dimensional vectors\n   * are reduced to target dimensions.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithReduceDimension(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:REDUCED\";\n    // Use a 4-dimensional vector that will be reduced to 2 dimensions\n    float[] highDimVector = { 1.0f, 2.0f, 3.0f, 4.0f };\n    int targetDim = 2;\n\n    // Create parameters for dimension reduction\n    VAddParams params = new VAddParams();\n\n    // Add element with dimension reduction\n    boolean result = jedis.vadd(testKey, highDimVector, elementId, targetDim, params);\n    assertTrue(result);\n\n    // Verify cardinality\n    assertEquals(1L, jedis.vcard(testKey));\n\n    // Verify the vector was reduced to target dimensions\n    assertEquals(targetDim, jedis.vdim(testKey));\n\n    // Retrieve and verify the reduced vector\n    List<Double> reducedVector = jedis.vemb(testKey, elementId);\n    assertEquals(targetDim, reducedVector.size());\n\n    // The values will be different due to random projection, but should exist\n    assertNotNull(reducedVector.get(0));\n    assertNotNull(reducedVector.get(1));\n  }\n\n  /**\n   * Test vaddFP32 with dimension reduction using byte blob. Verifies that FP32 format vectors are\n   * properly reduced.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddFP32WithReduceDimension(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:FP32_REDUCED\";\n    // Use a 4-dimensional vector that will be reduced to 2 dimensions\n    float[] highDimVector = { 1.0f, 2.0f, 3.0f, 4.0f };\n    int targetDim = 2;\n\n    // Convert to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(highDimVector);\n\n    // Create parameters for dimension reduction\n    VAddParams params = new VAddParams();\n\n    // Add element with dimension reduction using FP32 format\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId, targetDim, params);\n    assertTrue(result);\n\n    // Verify cardinality\n    assertEquals(1L, jedis.vcard(testKey));\n\n    // Verify the vector was reduced to target dimensions\n    assertEquals(targetDim, jedis.vdim(testKey));\n\n    // Retrieve and verify the reduced vector\n    List<Double> reducedVector = jedis.vemb(testKey, elementId);\n    assertEquals(targetDim, reducedVector.size());\n\n    // The values will be different due to random projection, but should exist\n    assertNotNull(reducedVector.get(0));\n    assertNotNull(reducedVector.get(1));\n  }\n\n  /**\n   * Test VADD with dimension reduction and additional parameters. Verifies that REDUCE works\n   * alongside other VAddParams.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithReduceDimensionAndParams(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:REDUCED_WITH_PARAMS\";\n    // Use a 6-dimensional vector that will be reduced to 3 dimensions\n    float[] highDimVector = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f };\n    int targetDim = 3;\n\n    // Create parameters with quantization and dimension reduction\n    VAddParams params = new VAddParams().q8().ef(100);\n\n    // Add element with dimension reduction and additional parameters\n    boolean result = jedis.vadd(testKey, highDimVector, elementId, targetDim, params);\n    assertTrue(result);\n\n    // Verify cardinality\n    assertEquals(1L, jedis.vcard(testKey));\n\n    // Verify the vector was reduced to target dimensions\n    assertEquals(targetDim, jedis.vdim(testKey));\n\n    // Retrieve and verify the reduced vector\n    List<Double> reducedVector = jedis.vemb(testKey, elementId);\n    assertEquals(targetDim, reducedVector.size());\n\n    // All dimensions should have values (may be quantized)\n    for (Double value : reducedVector) {\n      assertNotNull(value);\n    }\n  }\n\n  /**\n   * Test VADD with SETATTR parameter. Verifies that attributes can be set when adding elements to\n   * vector sets.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithSetAttr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:WITH_ATTR\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Create simple text attributes for the element\n    String attributes = \"category=test,priority=high,score=95.5\";\n\n    // Create parameters with attributes\n    VAddParams params = new VAddParams().setAttr(attributes);\n\n    // Add element with attributes\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n\n    // Verify the attributes were stored correctly using VGETATTR\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VADD with SETATTR and other parameters combined. Verifies that SETATTR works alongside\n   * quantization and other options.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddWithSetAttrAndQuantization(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:ATTR_QUANT\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Create simple text attributes\n    String attributes = \"type=quantized,method=Q8,timestamp=2024-01-01\";\n\n    // Create parameters with both attributes and quantization\n    VAddParams params = new VAddParams().setAttr(attributes).q8().ef(100);\n\n    // Add element with attributes and quantization\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored (may be quantized)\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.1); // Larger tolerance for quantization\n    assertEquals(2.0, storedVector.get(1), 0.1);\n\n    // Verify the attributes were stored correctly\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VADD with SETATTR using FP32 format. Verifies that attributes work with binary vector\n   * format.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVaddFP32WithSetAttr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:FP32_ATTR\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Convert to FP32 byte blob\n    byte[] vectorBlob = floatArrayToFP32Bytes(vector);\n\n    // Create simple text attributes\n    String attributes = \"format=FP32,source=binary,validated=true\";\n\n    // Create parameters with attributes\n    VAddParams params = new VAddParams().setAttr(attributes);\n\n    // Add element with FP32 format and attributes\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, elementId, params);\n    assertTrue(result);\n\n    // Verify cardinality and dimension\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vdim(testKey));\n\n    // Verify the vector was stored correctly\n    List<Double> storedVector = jedis.vemb(testKey, elementId);\n    assertEquals(2, storedVector.size());\n    assertEquals(1.0, storedVector.get(0), 0.01);\n    assertEquals(2.0, storedVector.get(1), 0.01);\n\n    // Verify the attributes were stored correctly\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VGETATTR command functionality. Verifies that attributes can be retrieved from vector set\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVgetattr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:GETATTR_TEST\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // First add an element without attributes\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // VGETATTR should return null for element without attributes\n    String attrs = jedis.vgetattr(testKey, elementId);\n    assertNull(attrs);\n\n    // Now add an element with attributes\n    String elementWithAttrs = \"point:WITH_ATTRS\";\n    String attributes = \"name=test_point,value=42,active=true\";\n    VAddParams params = new VAddParams().setAttr(attributes);\n    result = jedis.vadd(testKey, vector, elementWithAttrs, params);\n    assertTrue(result);\n\n    // VGETATTR should return the attributes\n    String retrievedAttrs = jedis.vgetattr(testKey, elementWithAttrs);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n\n    // Test VGETATTR with non-existent element\n    String nonExistentAttrs = jedis.vgetattr(testKey, \"non_existent_element\");\n    assertNull(nonExistentAttrs);\n  }\n\n  /**\n   * Test VGETATTR with binary key and element. Verifies that VGETATTR works with byte array keys\n   * and elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVgetattrBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_element_with_attrs\".getBytes();\n    float[] vector = { 1.0f, 2.0f };\n\n    // VGETATTR should return null for element without attributes\n    assertNull(jedis.vgetattr(testKey, elementId));\n\n    // Now add an element with attributes using binary key and element\n    String attributes = \"name=binary_test_point,value=42,active=true\";\n    VAddParams params = new VAddParams().setAttr(attributes);\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // VGETATTR should return the attributes as byte array\n    byte[] retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n\n    // Convert byte array back to string and verify content\n    String retrievedAttrsString = SafeEncoder.encode(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrsString);\n  }\n\n  /**\n   * Test VSETATTR command functionality. Verifies that attributes can be set on vector set\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsetattr(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:SETATTR_TEST\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // First add an element without attributes\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // Set attributes using VSETATTR\n    String attributes = \"name=test_point,value=42,active=true\";\n    boolean setResult = jedis.vsetattr(testKey, elementId, attributes);\n    assertTrue(setResult);\n\n    // Verify attributes were set using VGETATTR\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n\n    // Update attributes with new values\n    String updatedAttributes = \"name=updated_point,value=100,active=false,new_field=added\";\n    setResult = jedis.vsetattr(testKey, elementId, updatedAttributes);\n    assertTrue(setResult);\n\n    // Verify updated attributes\n    retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(updatedAttributes, retrievedAttrs);\n  }\n\n  /**\n   * Test VSETATTR with binary key and element. Verifies that VSETATTR works with byte array keys\n   * and elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsetattrBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_setattr_element\".getBytes();\n    float[] vector = { 1.0f, 2.0f };\n\n    // First add an element without attributes\n    boolean result = jedis.vadd(testKey, vector, elementId);\n    assertTrue(result);\n\n    // Set attributes using binary VSETATTR\n    String attributes = \"name=binary_test_point,value=42,active=true\";\n    byte[] attributesBytes = attributes.getBytes();\n    boolean setResult = jedis.vsetattr(testKey, elementId, attributesBytes);\n    assertTrue(setResult);\n\n    // Verify attributes were set using binary VGETATTR\n    byte[] retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n\n    // Convert back to string and verify\n    String retrievedAttrsString = SafeEncoder.encode(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrsString);\n\n    // Update attributes with new values using binary VSETATTR\n    String updatedAttributes = \"name=updated_binary_point,value=100,active=false,new_field=added\";\n    byte[] updatedAttributesBytes = updatedAttributes.getBytes();\n    setResult = jedis.vsetattr(testKey, elementId, updatedAttributesBytes);\n    assertTrue(setResult);\n\n    // Verify updated attributes using binary VGETATTR\n    retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n\n    String updatedRetrievedString = SafeEncoder.encode(retrievedAttrs);\n    assertEquals(updatedAttributes, updatedRetrievedString);\n  }\n\n  /**\n   * Test VLINKS command functionality. Verifies that vector set links can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinks(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to create a vector set with links\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Get links for element1\n    List<List<String>> links = jedis.vlinks(testKey, \"element1\");\n    assertNotNull(links);\n\n    assertFalse(links.isEmpty());\n    for (List<String> linkList : links) {\n      for (String rawLink : linkList) {\n        assertTrue(rawLink.equals(\"element2\") || rawLink.equals(\"element3\"));\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS command functionality. Verifies that vector set links can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksWithScores(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to create a vector set with links\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Get links for element1\n    List<Map<String, Double>> links = jedis.vlinksWithScores(testKey, \"element1\");\n    assertNotNull(links);\n\n    assertFalse(links.isEmpty());\n    for (Map<String, Double> scores : links) {\n      for (String element : scores.keySet()) {\n        assertTrue(element.equals(\"element2\") || element.equals(\"element3\"));\n        assertTrue(scores.get(element) > 0.0);\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS with binary key and element. Verifies that VLINKS works with byte array keys and\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_element\".getBytes();\n\n    // Add vectors using binary key and elements\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, elementId);\n    jedis.vadd(testKey, vector2, \"element2\".getBytes());\n\n    // Get links using binary VLINKS\n    List<List<byte[]>> binaryLinks = jedis.vlinks(testKey, elementId);\n    assertNotNull(binaryLinks);\n    assertThat(binaryLinks.size(), is(greaterThan(0)));\n\n    // If there are links, verify they are valid strings\n    for (List<byte[]> linkList : binaryLinks) {\n      for (byte[] rawLink : linkList) {\n        String link = SafeEncoder.encode(rawLink);\n        assertThat(link, is(notNullValue()));\n        assertThat(link, not(emptyString()));\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS command functionality. Verifies that vector set links can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksBinaryWithScores(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    // Add some vectors to create a vector set with links\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"element2\".getBytes());\n    jedis.vadd(testKey, vector3, \"element3\".getBytes());\n\n    // Get links for element1\n    List<Map<byte[], Double>> links = jedis.vlinksWithScores(testKey, \"element1\".getBytes());\n    assertNotNull(links);\n\n    assertFalse(links.isEmpty());\n    for (Map<byte[], Double> scores : links) {\n      for (byte[] element : scores.keySet()) {\n        assertTrue(Arrays.equals(element, \"element2\".getBytes())\n            || Arrays.equals(element, \"element3\".getBytes()));\n        assertTrue(scores.get(element) > 0.0);\n      }\n    }\n  }\n\n  /**\n   * Test VLINKS with non-existent element. Verifies that VLINKS handles non-existent elements\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVlinksNonExistent(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:nonexistent\";\n\n    // Add a vector first\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"existing_element\");\n\n    // Try to get links for non-existent element\n    List<List<String>> links = jedis.vlinks(testKey, \"non_existent_element\");\n    // Should return empty list or null for non-existent elements\n    // Exact behavior depends on Redis implementation\n    assertTrue(links == null || links.isEmpty());\n  }\n\n  /**\n   * Test VRANDMEMBER command functionality. Verifies that random vector set members can be\n   * retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmember(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to the set\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Get a single random member\n    String randomMember = jedis.vrandmember(testKey);\n    assertNotNull(randomMember);\n\n    // Should be one of the added elements\n    assertTrue(randomMember.equals(\"element1\") || randomMember.equals(\"element2\")\n        || randomMember.equals(\"element3\"));\n  }\n\n  /**\n   * Test VRANDMEMBER with count parameter. Verifies that multiple random members can be retrieved.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberWithCount(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:count\";\n\n    // Add multiple vectors\n    for (int i = 1; i <= 5; i++) {\n      float[] vector = { (float) i, (float) i };\n      jedis.vadd(testKey, vector, \"element\" + i);\n    }\n\n    // Get 3 random members\n    List<String> randomMembers = jedis.vrandmember(testKey, 3);\n    assertNotNull(randomMembers);\n    assertEquals(3, randomMembers.size());\n\n    // All returned members should be valid element IDs\n    String[] validElements = { \"element1\", \"element2\", \"element3\", \"element4\", \"element5\" };\n    for (String member : randomMembers) {\n      assertTrue(asList(validElements).contains(member));\n    }\n\n    // Test with count larger than set size\n    List<String> allMembers = jedis.vrandmember(testKey, 10);\n    assertNotNull(allMembers);\n    assertTrue(allMembers.size() <= 5); // Should not exceed actual set size\n  }\n\n  /**\n   * Test VRANDMEMBER with binary key. Verifies that VRANDMEMBER works with byte array keys.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    // Add vectors using binary key\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"binary_element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"binary_element2\".getBytes());\n\n    // Get random member using binary key\n    byte[] randomMember = jedis.vrandmember(testKey);\n    assertNotNull(randomMember);\n\n    // Convert to string for comparison\n    String randomMemberStr = SafeEncoder.encode(randomMember);\n    assertTrue(\n      randomMemberStr.equals(\"binary_element1\") || randomMemberStr.equals(\"binary_element2\"));\n\n    // Test with count using binary key\n    List<byte[]> randomMembers = jedis.vrandmember(testKey, 2);\n    assertNotNull(randomMembers);\n    assertTrue(randomMembers.size() <= 2);\n\n    // Verify all returned members are valid\n    for (byte[] member : randomMembers) {\n      assertNotNull(member);\n      String memberStr = SafeEncoder.encode(member);\n      assertTrue(memberStr.equals(\"binary_element1\") || memberStr.equals(\"binary_element2\"));\n    }\n  }\n\n  /**\n   * Test VRANDMEMBER with empty vector set. Verifies that VRANDMEMBER handles empty sets correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberEmptySet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    // Try to get random member from empty/non-existent set\n    String randomMember = jedis.vrandmember(testKey);\n    // Should return null for empty set\n    assertNull(randomMember);\n\n    // Test with count on empty set\n    List<String> randomMembers = jedis.vrandmember(testKey, 5);\n    // Should return empty list for empty set\n    assertTrue(randomMembers.isEmpty());\n  }\n\n  /**\n   * Test VRANDMEMBER with single element. Verifies that VRANDMEMBER works correctly with only one\n   * element.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberSingleElement(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:single\";\n\n    // Add only one vector\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"single_element\");\n\n    // Get random member (should always be the single element)\n    String randomMember = jedis.vrandmember(testKey);\n    assertNotNull(randomMember);\n    assertEquals(\"single_element\", randomMember);\n\n    // Test with count\n    List<String> randomMembers = jedis.vrandmember(testKey, 3);\n    assertNotNull(randomMembers);\n    assertEquals(1, randomMembers.size()); // Should only return the single element\n    assertEquals(\"single_element\", randomMembers.get(0));\n  }\n\n  /**\n   * Test VRANDMEMBER with negative count. Verifies that VRANDMEMBER handles negative count (allows\n   * duplicates).\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrandmemberNegativeCount(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:negative\";\n\n    // Add some vectors\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n\n    // Get random members with negative count (allows duplicates)\n    List<String> randomMembers = jedis.vrandmember(testKey, -5);\n    assertNotNull(randomMembers);\n    assertEquals(5, randomMembers.size()); // Should return exactly 5 elements (with possible\n                                           // duplicates)\n\n    // All returned members should be valid element IDs\n    for (String member : randomMembers) {\n      assertTrue(member.equals(\"element1\") || member.equals(\"element2\"));\n    }\n  }\n\n  /**\n   * Test VREM command functionality. Verifies that vector set elements can be removed correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVrem(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add some vectors to the set\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n\n    // Verify initial cardinality\n    assertEquals(3L, jedis.vcard(testKey));\n\n    // Remove one element\n    boolean removed = jedis.vrem(testKey, \"element2\");\n    assertTrue(removed);\n    assertEquals(2L, jedis.vcard(testKey));\n\n    // Try to remove the same element again (should return false)\n    removed = jedis.vrem(testKey, \"element2\");\n    assertFalse(removed);\n    assertEquals(2L, jedis.vcard(testKey));\n\n    // Remove remaining elements\n    removed = jedis.vrem(testKey, \"element1\");\n    assertTrue(removed);\n    assertEquals(1L, jedis.vcard(testKey));\n\n    removed = jedis.vrem(testKey, \"element3\");\n    assertTrue(removed);\n    assertEquals(0L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VREM with binary key and elements. Verifies that VREM works with byte array keys and\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVremBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    // Add vectors using binary key and elements\n    float[] vector1 = { 1.0f, 0.0f };\n    float[] vector2 = { 0.0f, 1.0f };\n    float[] vector3 = { 1.0f, 1.0f };\n\n    jedis.vadd(testKey, vector1, \"binary_element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"binary_element2\".getBytes());\n    jedis.vadd(testKey, vector3, \"binary_element3\".getBytes());\n\n    // Verify initial cardinality\n    assertEquals(3L, jedis.vcard(testKey));\n\n    // Remove element using binary VREM\n    boolean removed = jedis.vrem(testKey, \"binary_element2\".getBytes());\n    assertTrue(removed);\n    assertEquals(2L, jedis.vcard(testKey));\n\n    // Remove remaining elements using binary VREM\n    boolean removed1 = jedis.vrem(testKey, \"binary_element1\".getBytes());\n    boolean removed3 = jedis.vrem(testKey, \"binary_element3\".getBytes());\n    assertTrue(removed1);\n    assertTrue(removed3);\n    assertEquals(0L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VREM with non-existent elements. Verifies that VREM handles non-existent elements\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVremNonExistent(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:nonexistent\";\n\n    // Add one vector\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"existing_element\");\n\n    // Try to remove non-existent element\n    boolean removed = jedis.vrem(testKey, \"non_existent_element\");\n    assertFalse(removed);\n    assertEquals(1L, jedis.vcard(testKey)); // Cardinality should remain unchanged\n\n    // Try to remove from non-existent vector set\n    String nonExistentKey = testInfo.getDisplayName() + \":non:existent:key\";\n    removed = jedis.vrem(nonExistentKey, \"any_element\");\n    assertFalse(removed);\n  }\n\n  /**\n   * Test VSETATTR with empty attributes (attribute deletion). Verifies that setting empty\n   * attributes removes them.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsetattrDelete(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    String elementId = \"point:DELETE_ATTR\";\n    float[] vector = { 1.0f, 2.0f };\n\n    // Add element with attributes\n    String attributes = \"category=test,priority=high\";\n    VAddParams params = new VAddParams().setAttr(attributes);\n    boolean result = jedis.vadd(testKey, vector, elementId, params);\n    assertTrue(result);\n\n    // Verify attributes exist\n    String retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNotNull(retrievedAttrs);\n    assertEquals(attributes, retrievedAttrs);\n\n    // Delete attributes by setting empty string\n    boolean setResult = jedis.vsetattr(testKey, elementId, \"\");\n    assertTrue(setResult);\n\n    // Verify attributes are deleted (should return null or empty)\n    retrievedAttrs = jedis.vgetattr(testKey, elementId);\n    assertNull(retrievedAttrs);\n  }\n\n  /**\n   * Test VINFO command functionality. Verifies that vector set information can be retrieved.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVinfo(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    float[] vector1 = { 1.0f, 2.0f };\n    float[] vector2 = { 3.0f, 4.0f };\n\n    // Add some elements to the vector set\n    VAddParams params = new VAddParams().setAttr(\"{\\\"type\\\": \\\"fruit\\\", \\\"color\\\": \\\"red\\\"}\");\n    boolean result1 = jedis.vadd(testKey, vector1, \"element1\", params);\n    assertTrue(result1);\n\n    boolean result2 = jedis.vadd(testKey, vector2, \"element2\");\n    assertTrue(result2);\n\n    // Get vector set information\n    VectorInfo info = jedis.vinfo(testKey);\n    assertNotNull(info);\n\n    // Verify basic information is present\n    assertNotNull(info.getVectorInfo());\n    assertFalse(info.getVectorInfo().isEmpty());\n    assertEquals(2, info.getDimensionality());\n    assertEquals(\"int8\", info.getType());\n    assertEquals(2L, info.getSize());\n    assertEquals(16L, info.getMaxNodes());\n    assertThat(info.getMaxNodeUid(), greaterThan(0L));\n    assertThat(info.getVSetUid(), greaterThan(0L));\n    assertEquals(0L, info.getProjectionInputDim());\n    assertEquals(1L, info.getAttributesCount());\n    assertNotNull(info.getMaxLevel());\n  }\n\n  /**\n   * Test VINFO with empty vector set. Verifies behavior when vector set doesn't exist.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVinfoNotExistingSet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    VectorInfo info = jedis.vinfo(testKey);\n    assertNull(info);\n  }\n\n  /**\n   * Test VCARD command functionality. Verifies that vector set cardinality can be retrieved\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVcard(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n    float[] vector1 = { 1.0f, 2.0f };\n    float[] vector2 = { 3.0f, 4.0f };\n\n    // Initially, cardinality should be 0 for non-existent vector set\n    assertEquals(0L, jedis.vcard(testKey));\n\n    // Add first element\n    boolean result1 = jedis.vadd(testKey, vector1, \"element1\");\n    assertTrue(result1);\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(1L, jedis.vcard(testKey.getBytes()));\n\n    // Add second element\n    boolean result2 = jedis.vadd(testKey, vector2, \"element2\");\n    assertTrue(result2);\n    assertEquals(2L, jedis.vcard(testKey));\n    assertEquals(2L, jedis.vcard(testKey.getBytes()));\n\n    // Try to add duplicate element (should not increase cardinality)\n    boolean result3 = jedis.vadd(testKey, vector1, \"element1\");\n    assertFalse(result3); // Should return false for duplicate\n    assertEquals(2L, jedis.vcard(testKey)); // Cardinality should remain 3\n    assertEquals(2L, jedis.vcard(testKey.getBytes()));\n\n    // Remove an element\n    boolean removed = jedis.vrem(testKey, \"element2\");\n    assertTrue(removed);\n    assertEquals(1L, jedis.vcard(testKey));\n    assertEquals(1L, jedis.vcard(testKey.getBytes()));\n\n    // Remove last element\n    removed = jedis.vrem(testKey, \"element1\");\n    assertTrue(removed);\n    assertEquals(0L, jedis.vcard(testKey));\n    assertEquals(0L, jedis.vcard(testKey.getBytes()));\n\n  }\n\n  /**\n   * Test VCARD with non-existent vector set.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVcardNotExistingSet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    // VCARD should return 0 for non-existent vector set\n    assertEquals(0L, jedis.vcard(testKey));\n  }\n\n  /**\n   * Test VDIM command functionality. Verifies that vector set dimension can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdim(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add 2D vector\n    float[] vector2D = { 1.0f, 2.0f };\n    boolean result = jedis.vadd(testKey, vector2D, \"element1\");\n    assertTrue(result);\n    assertEquals(2L, jedis.vdim(testKey));\n    assertEquals(2L, jedis.vdim(testKey.getBytes()));\n\n    // Test different dimensions\n    String testKey3D = testInfo.getDisplayName() + \":test:vector:set:3d\";\n    float[] vector3D = { 1.0f, 2.0f, 3.0f };\n    jedis.vadd(testKey3D, vector3D, \"element3d\");\n    assertEquals(3L, jedis.vdim(testKey3D));\n    assertEquals(3L, jedis.vdim(testKey3D.getBytes()));\n\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdimNotExistingSet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n\n    JedisDataException thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n\n    thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey.getBytes()));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n  }\n\n  // Test VDIM with empty set\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdimWithEmptySet(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:empty:vector:set\";\n    // Add 2D vector\n    float[] vector2D = { 1.0f, 2.0f };\n    assertTrue(jedis.vadd(testKey, vector2D, \"element1\"));\n    assertTrue(jedis.vrem(testKey, \"element1\"));\n    assertEquals(0L, (jedis.vcard(testKey)));\n\n    JedisDataException thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n\n    thrown = assertThrows(JedisDataException.class, () -> jedis.vdim(testKey.getBytes()));\n    assertThat(thrown.getMessage(), is(\"ERR key does not exist\"));\n  }\n\n  /**\n   * Test VDIM with dimension reduction. Verifies that VDIM returns the reduced dimension when\n   * REDUCE is used.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVdimWithDimensionReduction(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:reduced\";\n\n    // Add 4D vector with dimension reduction to 2D\n    float[] vector4D = { 1.0f, 2.0f, 3.0f, 4.0f };\n    VAddParams params = new VAddParams();\n\n    boolean result = jedis.vadd(testKey, vector4D, \"element_reduced\", 2, params);\n    assertTrue(result);\n\n    // VDIM should return the reduced dimension (2), not the original (4)\n    assertEquals(2L, jedis.vdim(testKey));\n    assertEquals(2L, jedis.vdim(testKey.getBytes()));\n  }\n\n  /**\n   * Test VEMB command functionality. Verifies that vector embeddings can be retrieved correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVemb(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    // Add vector to the set\n    float[] originalVector = { 1.0f, 2.0f };\n    VAddParams params = new VAddParams().noQuant();\n    boolean result = jedis.vadd(testKey, originalVector, \"element1\", params);\n    assertTrue(result);\n\n    // Retrieve the vector using VEMB\n    List<Double> retrievedVector = jedis.vemb(testKey, \"element1\");\n    assertNotNull(retrievedVector);\n    assertEquals(2, retrievedVector.size());\n\n    // Verify vector values (with small tolerance for floating point precision)\n    assertEquals(1.0f, retrievedVector.get(0), 0.001);\n    assertEquals(2.0f, retrievedVector.get(1), 0.001);\n  }\n\n  /**\n   * Test VEMB with binary key and element. Verifies that VEMB works with byte array keys and\n   * elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n    byte[] elementId = \"binary_element\".getBytes();\n\n    // Add vector to the set using binary key and element\n    float[] originalVector = { 0.0f, 1.0f };\n    boolean result = jedis.vadd(testKey, originalVector, elementId);\n    assertTrue(result);\n\n    // Retrieve the vector using binary VEMB\n    List<Double> retrievedVector = jedis.vemb(testKey, elementId);\n    assertNotNull(retrievedVector);\n    assertEquals(2, retrievedVector.size());\n\n    // Verify vector values\n    assertEquals(0.0, retrievedVector.get(0), 0.001);\n    assertEquals(1.0, retrievedVector.get(1), 0.001);\n  }\n\n  /**\n   * Test VEMB with RAW option. Verifies that VEMB can return raw vector data when RAW flag is used\n   * with FP32 format.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembRaw(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:raw\";\n\n    // Add vector to the set using FP32 format\n    float[] originalVector = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };\n    byte[] vectorBlob = floatArrayToFP32Bytes(originalVector);\n    VAddParams params = new VAddParams().noQuant();\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, \"raw_element\", params);\n    assertTrue(result);\n\n    // Retrieve the vector using VEMB with RAW option\n    RawVector rawVector = jedis.vembRaw(testKey, \"raw_element\");\n    assertNotNull(rawVector);\n\n    // Verify the raw data length matches the original vector length\n    byte[] rawData = rawVector.getRawData();\n    int expectedLength = originalVector.length * 4; // 4 bytes per float\n    assertEquals(expectedLength, rawData.length);\n\n    // Verify the quantization type is FP32\n    assertEquals(\"f32\", rawVector.getQuantizationType());\n\n    // Verify the norm is present (L2 norm of the vector)\n    assertNotNull(rawVector.getNorm());\n    assertTrue(rawVector.getNorm() > 0);\n\n    // Verify the raw data contains the correct float values by converting back\n    // IEEE 754 32-bit floats are stored in little-endian format\n    List<Float> reconstructedVector = VectorTestUtils.fp32BytesToFloatArray(rawData);\n\n    // Verify the reconstructed vector matches the original\n    assertEquals(originalVector.length, reconstructedVector.size());\n    for (int i = 0; i < originalVector.length; i++) {\n      assertEquals(originalVector[i] / rawVector.getNorm(), reconstructedVector.get(i), 0.001f);\n    }\n  }\n\n  /**\n   * Test VEMB with RAW option. Verifies that VEMB can return raw vector data when RAW flag is used\n   * with FP32 format.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembRawBinary(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:raw\";\n\n    // Add vector to the set using FP32 format\n    float[] originalVector = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };\n    byte[] vectorBlob = floatArrayToFP32Bytes(originalVector);\n    VAddParams params = new VAddParams().noQuant();\n    boolean result = jedis.vaddFP32(testKey, vectorBlob, \"raw_element\", params);\n    assertTrue(result);\n\n    // Retrieve the vector using VEMB with RAW option\n    RawVector rawVector = jedis.vembRaw(testKey.getBytes(), \"raw_element\".getBytes());\n    assertNotNull(rawVector);\n\n    // Verify the raw data length matches the original vector length\n    byte[] rawData = rawVector.getRawData();\n    int expectedLength = originalVector.length * 4; // 4 bytes per float\n    assertEquals(expectedLength, rawData.length);\n\n    // Verify the quantization type is FP32\n    assertEquals(\"f32\", rawVector.getQuantizationType());\n\n    // Verify the norm is present (L2 norm of the vector)\n    assertNotNull(rawVector.getNorm());\n    assertTrue(rawVector.getNorm() > 0);\n\n    // Verify the raw data contains the correct float values by converting back\n    // IEEE 754 32-bit floats are stored in little-endian format\n    List<Float> reconstructedVector = VectorTestUtils.fp32BytesToFloatArray(rawData);\n\n    // Verify the reconstructed vector matches the original\n    assertEquals(originalVector.length, reconstructedVector.size());\n    for (int i = 0; i < originalVector.length; i++) {\n      assertEquals(originalVector[i] / rawVector.getNorm(), reconstructedVector.get(i), 0.001f);\n    }\n  }\n\n  /**\n   * Test VEMB with non-existent element. Verifies that VEMB handles non-existent elements\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVembNonExistent(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:nonexistent\";\n\n    // Add a vector first\n    float[] vector = { 1.0f, 2.0f };\n    jedis.vadd(testKey, vector, \"existing_element\");\n\n    // Try to retrieve non-existent element\n    assertNull(jedis.vemb(testKey, \"non_existent_element\"));\n\n  }\n\n  /**\n   * Test VSIM command functionality. Verifies vector similarity search with vectors and elements.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsim(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set\";\n\n    setupVSimTestSet(testKey);\n\n    // Test vsim with vector\n    List<String> similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f });\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(4));\n    assertThat(similar, hasItems(\"element1\", \"element2\", \"element3\", \"element4\"));\n\n    // Test vsim with element\n    similar = jedis.vsimByElement(testKey, \"element1\");\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(4));\n    assertThat(similar, hasItems(\"element1\", \"element2\", \"element3\", \"element4\"));\n\n    // Test vsim with vector and parameters\n    VSimParams params = new VSimParams().count(2);\n    similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n\n    // Test vsim with element and parameters\n    similar = jedis.vsimByElement(testKey, \"element1\", params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n  }\n\n  private void setupVSimTestSet(String testKey) {\n    // Add test vectors\n    float[] vector1 = { 0.1f, 0.2f, 0.3f };\n    float[] vector2 = { 0.2f, 0.3f, 0.4f };\n    float[] vector3 = { 0.3f, 0.4f, 0.5f };\n    float[] vector4 = { -0.1f, -0.2f, -0.3f };\n\n    jedis.vadd(testKey, vector1, \"element1\");\n    jedis.vadd(testKey, vector2, \"element2\");\n    jedis.vadd(testKey, vector3, \"element3\");\n    jedis.vadd(testKey, vector4, \"element4\");\n  }\n\n  /**\n   * Test VSIM command with scores functionality. Verifies vector similarity search returns scores\n   * correctly.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsimWithScores(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:scores\";\n\n    setupVSimTestSet(testKey);\n\n    // Test vsim with vector and scores\n    VSimParams params = new VSimParams();\n    Map<String, Double> similarWithScores = jedis.vsimWithScores(testKey,\n      new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertThat(similarWithScores.keySet(), hasItems(\"element1\", \"element2\", \"element3\"));\n    assertThat(similarWithScores.values(), everyItem(greaterThanOrEqualTo(0.0)));\n\n    // Test vsim with element and scores\n    similarWithScores = jedis.vsimByElementWithScores(testKey, \"element1\", params);\n    assertThat(similarWithScores.keySet(), hasItems(\"element1\", \"element2\", \"element3\"));\n    assertThat(similarWithScores.get(\"element1\"), closeTo(1, 0.01));\n    assertThat(similarWithScores.get(\"element4\"), closeTo(0, 0.01));\n    assertEquals(1.0, similarWithScores.get(\"element1\"), 0.001);\n\n    // Test with count parameter\n    params = new VSimParams().count(2);\n    similarWithScores = jedis.vsimWithScores(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertThat(similarWithScores.keySet(), hasItems(\"element1\", \"element2\"));\n    assertThat(similarWithScores.values(), everyItem(greaterThan(0.0)));\n\n    // Test with epsilon parameter (distance-based filtering)\n    params = new VSimParams().epsilon(0.2); // Only elements with similarity >= 0.8\n    similarWithScores = jedis.vsimWithScores(testKey, new float[] { -0.1f, -0.2f, -0.3f }, params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores.keySet(), hasItems(\"element4\"));\n    // Verify all returned scores meet the epsilon threshold\n    for (Double score : similarWithScores.values()) {\n      assertTrue(score >= (1.0 - 0.2)); // score >= 0.8\n    }\n  }\n\n  /**\n   * Test VSIM with scores and attributes.\n   */\n  @Test\n  @SinceRedisVersion(\"8.2.0\")\n  public void testVsimWithScoresAndAttribs(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:scores:attribs\";\n\n    // Add test vectors with attributes\n    VAddParams addParams1 = new VAddParams().setAttr(\"category=test,priority=high\");\n    jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, \"element1\", addParams1);\n\n    VAddParams addParams2 = new VAddParams().setAttr(\"category=prod,priority=low\");\n    jedis.vadd(testKey, new float[] { 0.15f, 0.25f, 0.35f }, \"element2\", addParams2);\n\n    // Add element without attributes\n    jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, \"element3\");\n\n    VSimParams params = new VSimParams();\n    Map<String, VSimScoreAttribs> similarWithScoresAndAttribs = jedis\n        .vsimWithScoresAndAttribs(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScoresAndAttribs);\n    assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap())));\n\n    // Verify scores and attributes are present\n    for (Map.Entry<String, VSimScoreAttribs> entry : similarWithScoresAndAttribs.entrySet()) {\n      String element = entry.getKey();\n      VSimScoreAttribs data = entry.getValue();\n\n      assertNotNull(data);\n      assertNotNull(data.getScore());\n      assertThat(data.getScore(), is(both(greaterThanOrEqualTo(0.0)).and(lessThanOrEqualTo(1.0))));\n\n      // Check attributes based on element\n      if (\"element1\".equals(element)) {\n        assertEquals(\"category=test,priority=high\", data.getAttributes());\n      } else if (\"element2\".equals(element)) {\n        assertEquals(\"category=prod,priority=low\", data.getAttributes());\n      } else if (\"element3\".equals(element)) {\n        assertNull(data.getAttributes()); // No attributes set\n      }\n    }\n  }\n\n  /**\n   * Test VSIM by element with scores and attributes.\n   */\n  @Test\n  @SinceRedisVersion(\"8.2.0\")\n  public void testVsimByElementWithScoresAndAttribs(TestInfo testInfo) {\n    String testKey = testInfo.getDisplayName() + \":test:vector:set:element:scores:attribs\";\n\n    // Add test vectors with attributes\n    VAddParams addParams1 = new VAddParams().setAttr(\"type=reference,quality=high\");\n    jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, \"reference\", addParams1);\n\n    VAddParams addParams2 = new VAddParams().setAttr(\"type=similar,quality=medium\");\n    jedis.vadd(testKey, new float[] { 0.12f, 0.22f, 0.32f }, \"similar1\", addParams2);\n\n    VAddParams addParams3 = new VAddParams().setAttr(\"type=different,quality=low\");\n    jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, \"different\", addParams3);\n\n    VSimParams params = new VSimParams();\n    Map<String, VSimScoreAttribs> similarWithScoresAndAttribs = jedis\n        .vsimByElementWithScoresAndAttribs(testKey, \"reference\", params);\n    assertNotNull(similarWithScoresAndAttribs);\n    assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap())));\n\n    // Reference element should have perfect similarity with itself\n    assertTrue(similarWithScoresAndAttribs.containsKey(\"reference\"));\n    VSimScoreAttribs referenceData = similarWithScoresAndAttribs.get(\"reference\");\n    assertThat(referenceData.getScore(), is(closeTo(1.0, 0.001)));\n    assertEquals(\"type=reference,quality=high\", referenceData.getAttributes());\n  }\n\n  /**\n   * Test VSIM command with binary keys and elements. Verifies vector similarity search works with\n   * byte arrays.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsimBinary(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary\").getBytes();\n\n    setupVSimTestSetBinary(testKey);\n\n    // Test vsim with vector (binary)\n    List<byte[]> similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f });\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(3));\n    assertThat(getBinaryElementNames(similar), hasItems(\"element1\", \"element2\", \"element3\"));\n\n    // Test vsim with element (binary)\n    similar = jedis.vsimByElement(testKey, \"element1\".getBytes());\n    assertNotNull(similar);\n    assertThat(similar, is(not(empty())));\n    assertThat(similar.size(), is(3));\n    assertThat(getBinaryElementNames(similar), hasItems(\"element1\", \"element2\", \"element3\"));\n\n    // Test vsim with vector and parameters (binary)\n    VSimParams params = new VSimParams().count(2);\n    similar = jedis.vsim(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n\n    // Test vsim with element and parameters (binary)\n    similar = jedis.vsimByElement(testKey, \"element1\".getBytes(), params);\n    assertNotNull(similar);\n    assertThat(similar.size(), is(2));\n  }\n\n  /**\n   * Test VSIM command with binary keys and scores. Verifies vector similarity search returns scores\n   * with binary data.\n   */\n  @Test\n  @SinceRedisVersion(\"8.0.0\")\n  public void testVsimBinaryWithScores(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary:scores\").getBytes();\n\n    setupVSimTestSetBinary(testKey);\n\n    // Test vsim with vector and scores (binary)\n    VSimParams params = new VSimParams();\n    Map<byte[], Double> similarWithScores = jedis.vsimWithScores(testKey,\n      new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores, is(not(anEmptyMap())));\n\n    // Verify scores are present and valid\n    for (Map.Entry<byte[], Double> entry : similarWithScores.entrySet()) {\n      assertNotNull(entry.getKey());\n      assertNotNull(entry.getValue());\n      assertThat(entry.getValue(), is(greaterThan(0.0)));\n    }\n\n    // Test vsim with element and scores (binary)\n    similarWithScores = jedis.vsimByElementWithScores(testKey, \"element1\".getBytes(), params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores, is(not(anEmptyMap())));\n\n    // Element1 should have perfect similarity with itself\n    Double element1Score = similarWithScores.get(\"element1\".getBytes());\n    assertNotNull(element1Score);\n    assertThat(element1Score, is(closeTo(1.0, 0.001)));\n\n    // Test with count parameter (binary)\n    params = new VSimParams().count(2);\n    similarWithScores = jedis.vsimWithScores(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScores);\n    assertThat(similarWithScores.size(), is(2));\n  }\n\n  /**\n   * Helper method to set up test vector set for binary VSIM tests.\n   */\n  private void setupVSimTestSetBinary(byte[] testKey) {\n    // Add test vectors - same as non-binary version\n    float[] vector1 = { 0.1f, 0.2f, 0.3f };\n    float[] vector2 = { 0.15f, 0.25f, 0.35f };\n    float[] vector3 = { 0.9f, 0.8f, 0.7f };\n\n    jedis.vadd(testKey, vector1, \"element1\".getBytes());\n    jedis.vadd(testKey, vector2, \"element2\".getBytes());\n    jedis.vadd(testKey, vector3, \"element3\".getBytes());\n  }\n\n  /**\n   * Helper method to convert binary element list to string names for assertions.\n   */\n  private List<String> getBinaryElementNames(List<byte[]> binaryElements) {\n    return binaryElements.stream().map(String::new).collect(java.util.stream.Collectors.toList());\n  }\n\n  /**\n   * Test VSIM command with binary keys and scores and attributes. Verifies vector similarity search\n   * returns scores and attributes with binary data.\n   */\n  @Test\n  @SinceRedisVersion(\"8.2.0\")\n  public void testVsimBinaryWithScoresAndAttribs(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:binary:scores:attribs\")\n        .getBytes();\n\n    setupVSimTestSetBinaryWithAttribs(testKey);\n\n    // Test vsim with vector, scores and attributes (binary)\n    VSimParams params = new VSimParams();\n    Map<byte[], VSimScoreAttribs> similarWithScoresAndAttribs = jedis\n        .vsimWithScoresAndAttribs(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params);\n    assertNotNull(similarWithScoresAndAttribs);\n    assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap())));\n\n    // Verify scores and attributes are present and valid\n    for (Map.Entry<byte[], VSimScoreAttribs> entry : similarWithScoresAndAttribs.entrySet()) {\n      byte[] element = entry.getKey();\n      VSimScoreAttribs data = entry.getValue();\n\n      assertNotNull(data);\n      assertNotNull(data.getScore());\n      assertThat(data.getScore(), is(both(greaterThanOrEqualTo(0.0)).and(lessThanOrEqualTo(1.0))));\n\n      // Check attributes based on element\n      String elementName = new String(element);\n      if (\"element1\".equals(elementName)) {\n        assertEquals(\"category=test,priority=high\", data.getAttributes());\n      } else if (\"element2\".equals(elementName)) {\n        assertEquals(\"category=prod,priority=low\", data.getAttributes());\n      } else if (\"element3\".equals(elementName)) {\n        assertNull(data.getAttributes()); // No attributes set\n      }\n    }\n\n  }\n\n  /**\n   * Test VSIM by element command with binary keys and scores and attributes. Verifies element-based\n   * vector similarity search returns scores and attributes with binary data.\n   */\n  @Test\n  @SinceRedisVersion(\"8.2.0\")\n  public void testVsimByElementBinaryWithScoresAndAttribs(TestInfo testInfo) {\n    byte[] testKey = (testInfo.getDisplayName() + \":test:vector:set:element:binary:scores:attribs\")\n        .getBytes();\n\n    // Add test vectors with attributes\n    byte[] referenceElement = \"reference\".getBytes();\n    VAddParams addParams1 = new VAddParams().setAttr(\"type=reference,quality=high\");\n    jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, referenceElement, addParams1);\n\n    byte[] similar1Element = \"similar1\".getBytes();\n    VAddParams addParams2 = new VAddParams().setAttr(\"type=similar,quality=medium\");\n    jedis.vadd(testKey, new float[] { 0.12f, 0.22f, 0.32f }, \"similar1\".getBytes(), addParams2);\n\n    byte[] differentElement = \"different\".getBytes();\n    VAddParams addParams3 = new VAddParams().setAttr(\"type=different,quality=low\");\n    jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, \"different\".getBytes(), addParams3);\n\n    VSimParams params = new VSimParams();\n    Map<byte[], VSimScoreAttribs> similarWithScoresAndAttribs = jedis\n        .vsimByElementWithScoresAndAttribs(testKey, referenceElement, params);\n    assertNotNull(similarWithScoresAndAttribs);\n    assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap())));\n\n    // Reference element should have perfect similarity with itself\n    VSimScoreAttribs referenceData = similarWithScoresAndAttribs.get(referenceElement);\n    assertThat(referenceData.getScore(), is(closeTo(1.0, 0.001)));\n    assertEquals(\"type=reference,quality=high\", referenceData.getAttributes());\n\n  }\n\n  /**\n   * Helper method to set up test vector set for binary VSIM tests with attributes.\n   */\n  private void setupVSimTestSetBinaryWithAttribs(byte[] testKey) {\n    // Add test vectors with attributes - same as non-binary version\n    float[] vector1 = { 0.1f, 0.2f, 0.3f };\n    float[] vector2 = { 0.15f, 0.25f, 0.35f };\n    float[] vector3 = { 0.9f, 0.8f, 0.7f };\n\n    VAddParams addParams1 = new VAddParams().setAttr(\"category=test,priority=high\");\n    jedis.vadd(testKey, vector1, \"element1\".getBytes(), addParams1);\n\n    VAddParams addParams2 = new VAddParams().setAttr(\"category=prod,priority=low\");\n    jedis.vadd(testKey, vector2, \"element2\".getBytes(), addParams2);\n\n    // Element without attributes\n    jedis.vadd(testKey, vector3, \"element3\".getBytes());\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientAllKindOfValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.AllKindOfValuesCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientAllKindOfValuesCommandsTest extends AllKindOfValuesCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientAllKindOfValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientBinaryValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.BinaryValuesCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientBinaryValuesCommandsTest extends BinaryValuesCommandsTestBase {\n\n  public RedisClientBinaryValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientBitCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.commands.unified.BitCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientBitCommandsTest extends BitCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientBitCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientCommandsTestHelper.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport redis.clients.jedis.*;\n\npublic class RedisClientCommandsTestHelper {\n\n  private static EndpointConfig endpoint;\n\n  private static EndpointConfig getEndpointImpl() {\n    if (endpoint == null) {\n      endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n    }\n    return endpoint;\n  }\n\n  /**\n   * Returns the endpoint configuration for standalone0.\n   * This method lazily initializes the endpoint to avoid class loading issues.\n   */\n  public static EndpointConfig getEndpointConfig() {\n    return getEndpointImpl();\n  }\n\n  public static RedisClient getClient(RedisProtocol redisProtocol) {\n    EndpointConfig info = getEndpointImpl();\n    return RedisClient.builder().hostAndPort(info.getHostAndPort()).clientConfig(info.getClientConfigBuilder()\n        .protocol(redisProtocol).build()).build();\n  }\n\n  public static RedisClient getClient(RedisProtocol redisProtocol, EndpointConfig endpoint) {\n\n    return RedisClient.builder().hostAndPort(endpoint.getHostAndPort()).clientConfig(endpoint.getClientConfigBuilder()\n        .protocol(redisProtocol).build()).build();\n  }\n\n  public static void clearData() {\n    EndpointConfig info = getEndpointImpl();\n    try (Jedis node = new Jedis(info.getHostAndPort())) {\n      node.auth(info.getPassword());\n      node.flushAll();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientExtendedVectorSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.ExtendedVectorSetCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientExtendedVectorSetCommandsTest extends ExtendedVectorSetCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientExtendedVectorSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @Override\n  protected void clearData() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientGeoCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.GeoCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientGeoCommandsTest extends GeoCommandsTestBase {\n\n  public RedisClientGeoCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientHashesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.HashesCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientHashesCommandsTest extends HashesCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientHashesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientHotkeysCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.util.RedisVersionUtil.getRedisVersion;\n\nimport java.time.Duration;\n\nimport io.redis.test.utils.RedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.args.HotkeysMetric;\nimport redis.clients.jedis.commands.unified.HotkeysCommandsTestBase;\nimport redis.clients.jedis.params.HotkeysParams;\nimport redis.clients.jedis.resps.HotkeysInfo;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.TestDataUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientHotkeysCommandsTest extends HotkeysCommandsTestBase {\n\n  public RedisClientHotkeysCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @Test\n  public void hotkeysGetBeforeStart() {\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNull(reply);\n  }\n\n  @Test\n  public void hotkeysLifecycle() {\n    String startResult = jedis\n        .hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU));\n    assertEquals(\"OK\", startResult);\n\n    jedis.set(\"key1\", \"value1\");\n    jedis.get(\"key1\");\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertTrue(reply.isTrackingActive());\n\n    String stopResult = jedis.hotkeysStop();\n    assertEquals(\"OK\", stopResult);\n\n    reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertFalse(reply.isTrackingActive());\n    assertTrue(reply.getByCpuTimeUs().containsKey(\"key1\"));\n\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU));\n    jedis.set(\"key2\", \"val2\");\n    reply = jedis.hotkeysGet();\n    assertTrue(reply.isTrackingActive());\n    assertTrue(reply.getByCpuTimeUs().containsKey(\"key2\"));\n    assertFalse(reply.getByCpuTimeUs().containsKey(\"key1\"));\n\n    jedis.hotkeysStop();\n    String resetResult = jedis.hotkeysReset();\n    assertEquals(\"OK\", resetResult);\n\n    reply = jedis.hotkeysGet();\n    assertNull(reply);\n  }\n\n  @Test\n  public void hotkeysBothMetrics() {\n    jedis.hotkeysStart(\n      HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU, HotkeysMetric.NET).sample(1));\n\n    String cpuHot = \"stats:counter\";\n    for (int i = 0; i < 20; i++) {\n      jedis.incr(cpuHot);\n    }\n\n    String netHot = \"blob:data\";\n    jedis.set(netHot, TestDataUtil.generateString(6000));\n    jedis.get(netHot);\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n\n    assertTrue(reply.getByCpuTimeUs().containsKey(cpuHot));\n    assertThat(reply.getByCpuTimeUs().get(cpuHot), greaterThan(0L));\n\n    assertTrue(reply.getByNetBytes().containsKey(netHot));\n    assertThat(reply.getByNetBytes().get(netHot), greaterThan(6000L));\n  }\n\n  @Test\n  public void hotkeysStartOptionsSample() {\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU).sample(5));\n\n    for (int i = 0; i < 20; i++) {\n      jedis.set(\"samplekey\" + i, \"value\" + i);\n    }\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertEquals(5, reply.getSampleRatio());\n    assertThat(reply.getByCpuTimeUs().size(), lessThan(20));\n  }\n\n  @Test\n  public void hotkeysStartOptionsCount() {\n\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU).count(10));\n\n    for (int i = 1; i <= 25; i++) {\n      jedis.set(\"countkey\" + i, \"value\" + i);\n    }\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertThat(reply.getByCpuTimeUs().size(), lessThanOrEqualTo(10));\n  }\n\n  @Test\n  public void hotkeysDurationOption() {\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU).duration(1));\n\n    jedis.set(\"durationkey\", \"testvalue\");\n\n    await().atMost(Duration.ofSeconds(2)).until(() -> {\n      HotkeysInfo info = jedis.hotkeysGet();\n      return info != null && !info.isTrackingActive();\n    });\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n    assertFalse(reply.isTrackingActive());\n    assertThat(reply.getCollectionDurationMs(), greaterThanOrEqualTo(1000L));\n    assertTrue(reply.getByCpuTimeUs().containsKey(\"durationkey\"));\n  }\n\n  @Test\n  public void hotkeysResponseFields() {\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU, HotkeysMetric.NET));\n\n    jedis.set(\"testkey\", \"testvalue\");\n    jedis.get(\"testkey\");\n\n    HotkeysInfo reply = jedis.hotkeysGet();\n    assertNotNull(reply);\n\n    assertTrue(reply.isTrackingActive());\n    assertEquals(1, reply.getSampleRatio());\n    assertNotNull(reply.getSelectedSlots());\n    // In standalone mode, server returns slot ranges (e.g., [[0, 16383]] for all slots)\n    assertThat(reply.getCollectionStartTimeUnixMs(), greaterThan(0L));\n    assertThat(reply.getCollectionDurationMs(), greaterThanOrEqualTo(0L));\n    assertNotNull(reply.getByCpuTimeUs());\n    assertNotNull(reply.getByNetBytes());\n    assertThat(reply.getTotalCpuTimeUserMs(), greaterThanOrEqualTo(0L));\n    assertThat(reply.getTotalCpuTimeSysMs(), greaterThanOrEqualTo(0L));\n    assertThat(reply.getTotalNetBytes(), greaterThanOrEqualTo(0L));\n    assertNull(reply.getSampledCommandSelectedSlotsUs());\n    assertNull(reply.getAllCommandsSelectedSlotsUs());\n    assertThat(reply.getAllCommandsAllSlotsUs(), greaterThanOrEqualTo(0L));\n    assertNull(reply.getNetBytesSampledCommandsSelectedSlots());\n    assertNull(reply.getNetBytesAllCommandsSelectedSlots());\n    assertThat(reply.getNetBytesAllCommandsAllSlots(), greaterThanOrEqualTo(0L));\n  }\n\n  @Test\n  public void infoHotkeysSection() {\n    boolean isRedis8_6_1OrHigher = getRedisVersion(jedis)\n        .isGreaterThanOrEqualTo(RedisVersion.of(\"8.6.1\"));\n\n    String info = jedis.info();\n    // Hotkeys section is displayed in info even when empty starting from Redis 8.6.1+\n    if (isRedis8_6_1OrHigher) {\n      assertTrue(info.contains(\"# Hotkeys\"));\n    } else {\n      assertFalse(info.contains(\"# Hotkeys\"));\n    }\n\n    jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU));\n    info = jedis.info();\n    assertTrue(info.contains(\"# Hotkeys\"));\n    assertTrue(info.contains(\"hotkeys-tracking-active:1\"));\n\n    jedis.hotkeysStop();\n    info = jedis.info();\n    assertTrue(info.contains(\"# Hotkeys\"));\n    assertTrue(info.contains(\"hotkeys-tracking-active:0\"));\n\n    jedis.hotkeysReset();\n    info = jedis.info();\n\n    // Hotkeys section is displayed in info even when empty starting from Redis 8.6.1+\n    if (isRedis8_6_1OrHigher) {\n      assertTrue(info.contains(\"# Hotkeys\"));\n    } else {\n      assertFalse(info.contains(\"# Hotkeys\"));\n    }\n\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientHyperLogLogCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.HyperLogLogCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientHyperLogLogCommandsTest extends HyperLogLogCommandsTestBase {\n\n  public RedisClientHyperLogLogCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientListCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.ListCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientListCommandsTest extends ListCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientListCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientMiscellaneousTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.AbstractPipeline;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.UnifiedJedisCommandsTestBase;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class RedisClientMiscellaneousTest extends UnifiedJedisCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientMiscellaneousTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n  @Test\n  public void pipeline() {\n    final int count = 10;\n    int totalCount = 0;\n    for (int i = 0; i < count; i++) {\n      jedis.set(\"foo\" + i, \"bar\" + i);\n    }\n    totalCount += count;\n    for (int i = 0; i < count; i++) {\n      jedis.rpush(\"foobar\" + i, \"foo\" + i, \"bar\" + i);\n    }\n    totalCount += count;\n\n    List<Response<?>> responses = new ArrayList<>(totalCount);\n    List<Object> expected = new ArrayList<>(totalCount);\n\n    try (AbstractPipeline pipeline = jedis.pipelined()) {\n      for (int i = 0; i < count; i++) {\n        responses.add(pipeline.get(\"foo\" + i));\n        expected.add(\"bar\" + i);\n      }\n      for (int i = 0; i < count; i++) {\n        responses.add(pipeline.lrange(\"foobar\" + i, 0, -1));\n        expected.add(Arrays.asList(\"foo\" + i, \"bar\" + i));\n      }\n      pipeline.sync();\n    }\n\n    for (int i = 0; i < totalCount; i++) {\n      assertEquals(expected.get(i), responses.get(i).get());\n    }\n  }\n\n  @Test\n  public void broadcast() {\n\n    String script_1 = \"return 'jedis'\";\n    String sha1_1 = jedis.scriptLoad(script_1);\n\n    String script_2 = \"return 79\";\n    String sha1_2 = jedis.scriptLoad(script_2);\n\n    assertEquals(Arrays.asList(true, true), jedis.scriptExists(Arrays.asList(sha1_1, sha1_2)));\n\n    jedis.scriptFlush();\n\n    assertEquals(Arrays.asList(false, false), jedis.scriptExists(Arrays.asList(sha1_1, sha1_2)));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void broadcastWithError() {\n    JedisDataException error = assertThrows(JedisDataException.class,\n        () -> jedis.functionDelete(\"xyz\"));\n    assertEquals(\"ERR Library not found\", error.getMessage());\n  }\n\n  @Test\n  public void info() {\n    String info = jedis.info();\n    assertThat(info, notNullValue());\n\n    info = jedis.info(\"server\");\n    assertThat(info, notNullValue());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.SetCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientSetCommandsTest extends SetCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientSortedSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.SortedSetCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientSortedSetCommandsTest extends SortedSetCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientSortedSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientStreamsBinaryCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.StreamsBinaryCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientStreamsBinaryCommandsTest extends StreamsBinaryCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientStreamsBinaryCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientStreamsCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.StreamsCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientStreamsCommandsTest extends StreamsCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientStreamsCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientStringValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.StringValuesCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientStringValuesCommandsTest extends StringValuesCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientStringValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientTransactionIT.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.AbstractTransaction;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.UnifiedJedisCommandsTestBase;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientTransactionIT extends UnifiedJedisCommandsTestBase {\n\n  public RedisClientTransactionIT(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void transaction() {\n    final int count = 10;\n    int totalCount = 0;\n    for (int i = 0; i < count; i++) {\n      jedis.set(\"foo\" + i, \"bar\" + i);\n    }\n    totalCount += count;\n    for (int i = 0; i < count; i++) {\n      jedis.rpush(\"foobar\" + i, \"foo\" + i, \"bar\" + i);\n    }\n    totalCount += count;\n\n    List<Object> responses;\n    List<Object> expected = new ArrayList<>(totalCount);\n\n    try (AbstractTransaction transaction = jedis.multi()) {\n      for (int i = 0; i < count; i++) {\n        transaction.get(\"foo\" + i);\n        expected.add(\"bar\" + i);\n      }\n      for (int i = 0; i < count; i++) {\n        transaction.lrange(\"foobar\" + i, 0, -1);\n        expected.add(Arrays.asList(\"foo\" + i, \"bar\" + i));\n      }\n      responses = transaction.exec();\n    }\n\n    for (int i = 0; i < totalCount; i++) {\n      assertEquals(expected.get(i), responses.get(i));\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void watch() {\n    try (AbstractTransaction tx = jedis.transaction(false)) {\n      assertEquals(\"OK\", tx.watch(\"mykey\", \"somekey\"));\n      tx.multi();\n\n      jedis.set(\"mykey\", \"bar\");\n\n      tx.set(\"mykey\", \"foo\");\n      assertNull(tx.exec());\n\n      assertEquals(\"bar\", jedis.get(\"mykey\"));\n    }\n  }\n\n  /**\n   * Verify manual multi and commands sent before sending multi does not cause out of order\n   * responses\n   */\n  @Test\n  public void transactionManualWithCommandsBeforeMulti() {\n\n    try (AbstractTransaction tx = jedis.transaction(false)) {\n\n      // command before multi\n      Response<String> txSetBeforeMulti = tx.set(\"mykey\", \"before_multi\");\n      Response<String> txGetBeforeMulti = tx.get(\"mykey\");\n      assertEquals(\"OK\", txSetBeforeMulti.get());\n      assertEquals(\"before_multi\", txGetBeforeMulti.get());\n\n      tx.multi();\n      Response<String> txSet = tx.set(\"mykey\", \"foo\");\n      Response<String> txGet = tx.get(\"mykey\");\n      List<Object> txResp = tx.exec();\n\n      assertEquals(\"OK\", txSet.get());\n      assertEquals(\"foo\", txGet.get());\n      assertEquals(2, txResp.size());\n      assertEquals(\"OK\", txResp.get(0));\n      assertEquals(\"foo\", txResp.get(1));\n    }\n  }\n\n  @Test\n  public void publishInTransaction() {\n    try (AbstractTransaction tx = jedis.multi()) {\n      Response<Long> p1 = tx.publish(\"foo\", \"bar\");\n      Response<Long> p2 = tx.publish(\"foo\".getBytes(), \"bar\".getBytes());\n      tx.exec();\n\n      assertEquals(0, p1.get().longValue());\n      assertEquals(0, p2.get().longValue());\n    }\n  }\n\n  @Nested\n  class ResponseHandlingIT {\n\n    @BeforeEach\n    public void setUp() {\n      RedisClientCommandsTestHelper.clearData();\n    }\n\n    @Test\n    public void notInTransactionResponseReturnsExpectedValue() {\n      // Commands executed before multi() should return immediate responses\n      try (AbstractTransaction tx = jedis.transaction(false)) {\n        Response<String> setResponse = tx.set(\"key1\", \"value1\");\n        Response<String> getResponse = tx.get(\"key1\");\n\n        // Responses should be available immediately (not in transaction yet)\n        assertEquals(\"OK\", setResponse.get());\n        assertEquals(\"value1\", getResponse.get());\n      }\n    }\n\n    @Test\n    public void notInTransactionResponsePropagatesException() {\n      // Commands executed before multi() that fail should propagate exceptions\n      try (AbstractTransaction tx = jedis.transaction(false)) {\n        Response<String> setResponse = tx.set(\"key1\", \"not_a_number\");\n        Response<Long> incrResponse = tx.incr(\"key1\");\n\n        // Set should succeed\n        assertEquals(\"OK\", setResponse.get());\n\n        // Incr should fail and propagate exception immediately\n        JedisDataException ex = assertThrows(JedisDataException.class, incrResponse::get);\n        assertEquals(\"ERR value is not an integer or out of range\", ex.getMessage());\n      }\n    }\n\n    @Test\n    public void inTransactionResponseThrowsBeforeExec() {\n      // Calling response.get() before exec() should throw IllegalStateException\n      try (AbstractTransaction tx = jedis.multi()) {\n        Response<String> response = tx.set(\"key1\", \"value1\");\n\n        // Attempting to get response before exec() should throw\n        IllegalStateException ex = assertThrows(IllegalStateException.class, response::get);\n        assertTrue(ex.getMessage()\n            .contains(\"Please close pipeline or multi block before calling this method\"));\n\n        // Now exec the transaction\n        tx.exec();\n\n        // After exec, response should be available\n        assertEquals(\"OK\", response.get());\n      }\n    }\n\n    @Test\n    public void afterExecResponseContainsActualResults() {\n      // After exec(), Response objects should contain actual results from Redis\n      try (AbstractTransaction tx = jedis.multi()) {\n        Response<String> setResponse = tx.set(\"key1\", \"value1\");\n        Response<String> getResponse = tx.get(\"key1\");\n\n        tx.exec();\n\n        // Verify Response objects contain correct values\n        assertEquals(\"OK\", setResponse.get());\n        assertEquals(\"value1\", getResponse.get());\n      }\n    }\n\n    @Test\n    public void execReturnsListWithAllResultsInOrder() {\n      // exec() should return List<Object> with all command results in order\n      try (AbstractTransaction tx = jedis.multi()) {\n        tx.set(\"key1\", \"value1\");\n        tx.get(\"key1\");\n        tx.del(\"key1\");\n\n        List<Object> results = tx.exec();\n\n        // Verify all results are in the correct order\n        assertEquals(3, results.size());\n        assertEquals(\"OK\", results.get(0)); // set key1\n        assertEquals(\"value1\", results.get(1)); // get key1\n        assertEquals(1L, results.get(2)); // del key1\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/RedisClientVectorSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.VectorSetCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisClientVectorSetCommandsTest extends VectorSetCommandsTestBase {\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n\n  public RedisClientVectorSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    RedisClientCommandsTestHelper.clearData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/client/search/FTHybridRedisClientCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.client.search;\n\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.client.RedisClientCommandsTestHelper;\nimport redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class FTHybridRedisClientCommandsTest extends FTHybridCommandsTestBase {\n\n  public FTHybridRedisClientCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return RedisClientCommandsTestHelper.getClient(protocol, endpoint);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterAllKindOfValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.AllKindOfValuesCommandsTestBase;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterAllKindOfValuesCommandsTest extends AllKindOfValuesCommandsTestBase {\n\n  public ClusterAllKindOfValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void existsMany() {\n    String status = jedis.set(\"{foo}1\", \"bar1\");\n    assertEquals(\"OK\", status);\n\n    status = jedis.set(\"{foo}2\", \"bar2\");\n    assertEquals(\"OK\", status);\n\n    assertEquals(2L, jedis.exists(\"{foo}1\", \"{foo}2\"));\n\n    assertEquals(1L, jedis.del(\"{foo}1\"));\n\n    assertEquals(1L, jedis.exists(\"{foo}1\", \"{foo}2\"));\n  }\n\n  @Test\n  @Override\n  public void del() {\n    jedis.set(\"{foo}1\", \"bar1\");\n    jedis.set(\"{foo}2\", \"bar2\");\n    jedis.set(\"{foo}3\", \"bar3\");\n\n    assertEquals(3L, jedis.del(\"{foo}1\", \"{foo}2\", \"{foo}3\"));\n\n    assertFalse(jedis.exists(\"{foo}1\"));\n    assertFalse(jedis.exists(\"{foo}2\"));\n    assertFalse(jedis.exists(\"{foo}3\"));\n\n    jedis.set(\"{foo}1\", \"bar1\");\n\n    assertEquals(1L, jedis.del(\"{foo}1\", \"{foo}2\"));\n\n    assertEquals(0L, jedis.del(\"{foo}1\", \"{foo}2\"));\n  }\n\n  @Test\n  @Override\n  public void unlink() {\n    jedis.set(\"{foo}1\", \"bar1\");\n    jedis.set(\"{foo}2\", \"bar2\");\n    jedis.set(\"{foo}3\", \"bar3\");\n\n    assertEquals(3, jedis.unlink(\"{foo}1\", \"{foo}2\", \"{foo}3\"));\n\n    assertEquals(0, jedis.exists(\"{foo}1\", \"{foo}2\", \"{foo}3\"));\n\n    jedis.set(\"{foo}1\", \"bar1\");\n\n    assertEquals(1, jedis.unlink(\"{foo}1\", \"{foo}2\"));\n\n    assertEquals(0, jedis.unlink(\"{foo}1\", \"{foo}2\"));\n\n    jedis.set(\"{foo}\", \"bar\");\n    assertEquals(1, jedis.unlink(\"{foo}\"));\n    assertFalse(jedis.exists(\"{foo}\"));\n  }\n\n  @Test\n  @Override\n  public void keys() {\n    jedis.set(\"foo\", \"bar\");\n    jedis.set(\"foobar\", \"bar\");\n\n    Set<String> keys = jedis.keys(\"foo*\");\n    Set<String> expected = new HashSet<>();\n    expected.add(\"foo\");\n    expected.add(\"foobar\");\n    assertEquals(expected, keys);\n\n    expected.clear();\n    keys = jedis.keys(\"bar*\");\n\n    assertEquals(expected, keys);\n  }\n\n  @Test\n  @Override\n  public void rename() {\n    jedis.set(\"foo{#}\", \"bar\");\n    String status = jedis.rename(\"foo{#}\", \"bar{#}\");\n    assertEquals(\"OK\", status);\n\n    assertNull(jedis.get(\"foo{#}\"));\n\n    assertEquals(\"bar\", jedis.get(\"bar{#}\"));\n  }\n\n  @Test\n  @Override\n  public void renamenx() {\n    jedis.set(\"foo{&}\", \"bar\");\n    assertEquals(1, jedis.renamenx(\"foo{&}\", \"bar{&}\"));\n\n    jedis.set(\"foo{&}\", \"bar\");\n    assertEquals(0, jedis.renamenx(\"foo{&}\", \"bar{&}\"));\n  }\n\n  @Test\n  @Override\n  public void dbSize() {\n    // In cluster mode, dbSize returns the sum of keys across all nodes\n    assertEquals(0, jedis.dbSize());\n\n    jedis.set(\"foo\", \"bar\");\n    assertEquals(1, jedis.dbSize());\n\n    // Binary\n    jedis.set(bfoo, bbar);\n    assertEquals(2, jedis.dbSize());\n  }\n\n  @Test\n  @Override\n  public void touch() throws Exception {\n    assertEquals(0, jedis.touch(\"{foo}1\", \"{foo}2\", \"{foo}3\"));\n\n    jedis.set(\"{foo}1\", \"bar1\");\n\n    Thread.sleep(1100); // little over 1 sec\n    assertTrue(jedis.objectIdletime(\"{foo}1\") > 0);\n\n    assertEquals(1, jedis.touch(\"{foo}1\"));\n    assertEquals(0L, jedis.objectIdletime(\"{foo}1\").longValue());\n\n    assertEquals(1, jedis.touch(\"{foo}1\", \"{foo}2\", \"{foo}3\"));\n\n    jedis.set(\"{foo}2\", \"bar2\");\n\n    jedis.set(\"{foo}3\", \"bar3\");\n\n    assertEquals(3, jedis.touch(\"{foo}1\", \"{foo}2\", \"{foo}3\"));\n  }\n\n  @Test\n  @Override\n  public void scan() {\n    jedis.set(\"{%}b\", \"b\");\n    jedis.set(\"a{%}\", \"a\");\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, new ScanParams().match(\"*{%}*\"));\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n  }\n\n  @Test\n  @Override\n  public void scanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a{-}*\");\n\n    jedis.set(\"b{-}\", \"b\");\n    jedis.set(\"a{-}\", \"a\");\n    jedis.set(\"a{-}a\", \"aa\");\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, params);\n\n    assertEquals(SCAN_POINTER_START, result.getCursor());\n    assertFalse(result.getResult().isEmpty());\n  }\n\n  @Test\n  @Override\n  public void scanCount() {\n    ScanParams params = new ScanParams();\n    params.match(\"{a}*\");\n    params.count(2);\n\n    for (int i = 0; i < 10; i++) {\n      jedis.set(\"{a}\" + i, \"a\" + i);\n    }\n\n    ScanResult<String> result = jedis.scan(SCAN_POINTER_START, params);\n    assertTrue(result.getResult().size() >= 2);\n  }\n\n  @Test\n  @Override\n  public void scanType() {\n    ScanParams noCount = new ScanParams().match(\"*{+}*\");\n    ScanParams pagingParams = new ScanParams().match(\"*{+}*\").count(4);\n\n    jedis.set(\"{+}a\", \"a\");\n    jedis.hset(\"{+}b\", \"b\", \"b\");\n    jedis.set(\"c{+}\", \"c\");\n    jedis.sadd(\"d{+}\", \"d\");\n    jedis.set(\"e{+}\", \"e\");\n    jedis.zadd(\"{+}f\", 0d, \"f\");\n    jedis.set(\"{+}g\", \"g\");\n\n    // string\n    ScanResult<String> scanResult;\n\n    scanResult = jedis.scan(SCAN_POINTER_START, pagingParams, \"string\");\n    assertFalse(scanResult.isCompleteIteration());\n    int page1Count = scanResult.getResult().size();\n    scanResult = jedis.scan(scanResult.getCursor(), pagingParams, \"string\");\n    assertTrue(scanResult.isCompleteIteration());\n    int page2Count = scanResult.getResult().size();\n    assertEquals(4, page1Count + page2Count);\n\n\n    scanResult = jedis.scan(SCAN_POINTER_START, noCount, \"hash\");\n    assertEquals(Collections.singletonList(\"{+}b\"), scanResult.getResult());\n    scanResult = jedis.scan(SCAN_POINTER_START, noCount, \"set\");\n    assertEquals(Collections.singletonList(\"d{+}\"), scanResult.getResult());\n    scanResult = jedis.scan(SCAN_POINTER_START, noCount, \"zset\");\n    assertEquals(Collections.singletonList(\"{+}f\"), scanResult.getResult());\n  }\n\n  @Test\n  @Override\n  public void scanIsCompleteIteration() {\n    assertThrows( IllegalArgumentException.class, super::scanIsCompleteIteration);\n  }\n\n  @Test\n  @Override\n  public void copy() {\n    assertFalse(jedis.copy(\"unkn{o}wn\", \"f{o}o\", false));\n\n    jedis.set(\"{foo}1\", \"bar\");\n    assertTrue(jedis.copy(\"{foo}1\", \"{foo}2\", false));\n    assertEquals(\"bar\", jedis.get(\"{foo}2\"));\n\n    // replace\n    jedis.set(\"{foo}1\", \"bar1\");\n    assertTrue(jedis.copy(\"{foo}1\", \"{foo}2\", true));\n    assertEquals(\"bar1\", jedis.get(\"{foo}2\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterBinaryValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.BinaryValuesCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterBinaryValuesCommandsTest extends BinaryValuesCommandsTestBase {\n\n  public ClusterBinaryValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Disabled\n  @Override\n  public void mget() {\n  }\n\n  @Disabled\n  @Override\n  public void mset() {\n  }\n\n  @Disabled\n  @Override\n  public void msetnx() {\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterBitCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.commands.unified.BitCommandsTestBase;\nimport redis.clients.jedis.exceptions.JedisDataException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterBitCommandsTest extends BitCommandsTestBase {\n\n  public ClusterBitCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void bitOp() {\n    jedis.set(\"{key}1\", \"\\u0060\");\n    jedis.set(\"{key}2\", \"\\u0044\");\n\n    jedis.bitop(BitOP.AND, \"resultAnd{key}\", \"{key}1\", \"{key}2\");\n    String resultAnd = jedis.get(\"resultAnd{key}\");\n    assertEquals(\"\\u0040\", resultAnd);\n\n    jedis.bitop(BitOP.OR, \"resultOr{key}\", \"{key}1\", \"{key}2\");\n    String resultOr = jedis.get(\"resultOr{key}\");\n    assertEquals(\"\\u0064\", resultOr);\n\n    jedis.bitop(BitOP.XOR, \"resultXor{key}\", \"{key}1\", \"{key}2\");\n    String resultXor = jedis.get(\"resultXor{key}\");\n    assertEquals(\"\\u0024\", resultXor);\n  }\n\n  @Test\n  @Override\n  public void bitOpNot() {\n    jedis.setbit(\"key\", 0, true);\n    jedis.setbit(\"key\", 4, true);\n\n    jedis.bitop(BitOP.NOT, \"resultNot{key}\", \"key\");\n    String resultNot = jedis.get(\"resultNot{key}\");\n    assertEquals(\"\\u0077\", resultNot);\n  }\n\n  @Disabled\n  @Override\n  public void bitOpBinary() {\n  }\n\n  @Test\n  @Override\n  public void bitOpNotMultiSourceShouldFail() {\n    assertThrows(JedisDataException.class,\n        () -> jedis.bitop(BitOP.NOT, \"{!}dest\", \"{!}src1\", \"{!}src2\"));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterCommandsTestHelper.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport java.util.HashSet;\n\nimport redis.clients.jedis.*;\n\npublic class ClusterCommandsTestHelper {\n\n  public static RedisClusterClient getCleanCluster(RedisProtocol protocol) {\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"cluster-stable\");\n\n    RedisClusterClient client = RedisClusterClient.builder()\n        .nodes(new HashSet<>(endpoint.getHostsAndPorts()))\n        .clientConfig(endpoint.getClientConfigBuilder().protocol(protocol).build()).build();\n    client.flushAll();\n    return client;\n  }\n\n  public static RedisClusterClient getCleanCluster(RedisProtocol protocol, EndpointConfig endpoint) {\n\n    RedisClusterClient client = RedisClusterClient.builder()\n        .nodes(new HashSet<>(endpoint.getHostsAndPorts()))\n        .clientConfig(endpoint.getClientConfigBuilder().protocol(protocol).build()).build();\n    client.flushAll();\n    return client;\n  }\n\n  public static void clearClusterData() {\n    getCleanCluster(RedisProtocol.RESP2);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterExtendedVectorSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.ExtendedVectorSetCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterExtendedVectorSetCommandsTest extends ExtendedVectorSetCommandsTestBase {\n\n  public ClusterExtendedVectorSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterFunctionCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.FunctionCommandsTestBase;\nimport redis.clients.jedis.exceptions.JedisBroadcastException;\nimport redis.clients.jedis.exceptions.JedisException;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.everyItem;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ClusterFunctionCommandsTest extends FunctionCommandsTestBase {\n\n  public ClusterFunctionCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  @Override\n  public void testFunctionKill() {\n    JedisException e = assertThrows(JedisException.class, () -> jedis.functionKill());\n    assertThat(e, instanceOf(JedisBroadcastException.class));\n    JedisBroadcastException jbe = (JedisBroadcastException) e;\n    List<String> replies = jbe.getReplies().values().stream().map(e1 -> (Exception) e1)\n        .map(Exception::getMessage).collect(Collectors.toList());\n    assertThat(replies.size(), equalTo(3));\n    assertThat(replies, everyItem(containsString(\"No scripts in execution right now\")));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterGeoCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.commands.unified.GeoCommandsTestBase;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterGeoCommandsTest extends GeoCommandsTestBase {\n\n  public ClusterGeoCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return  ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void georadiusStore() {\n    // prepare datas\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    jedis.geoadd(\"Sicily {ITA}\", coordinateMap);\n\n    long size = jedis.georadiusStore(\"Sicily {ITA}\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"{ITA} SicilyStore\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"Palermo\");\n    expected.add(\"Catania\");\n    assertEquals(expected, jedis.zrange(\"{ITA} SicilyStore\", 0, -1));\n  }\n\n  @Disabled\n  @Override\n  public void georadiusStoreBinary() {\n  }\n\n  @Test\n  @Override\n  public void georadiusByMemberStore() {\n    jedis.geoadd(\"Sicily {ITA}\", 13.583333, 37.316667, \"Agrigento\");\n    jedis.geoadd(\"Sicily {ITA}\", 13.361389, 38.115556, \"Palermo\");\n    jedis.geoadd(\"Sicily {ITA}\", 15.087269, 37.502669, \"Catania\");\n\n    long size = jedis.georadiusByMemberStore(\"Sicily {ITA}\", \"Agrigento\", 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"{ITA} SicilyStore\"));\n    assertEquals(2, size);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"Agrigento\");\n    expected.add(\"Palermo\");\n    assertEquals(expected, jedis.zrange(\"{ITA} SicilyStore\", 0, -1));\n  }\n\n  @Disabled\n  @Override\n  public void georadiusByMemberStoreBinary() {\n  }\n\n  @Disabled\n  @Override\n  public void geosearchstore() {\n  }\n\n  @Disabled\n  @Override\n  public void geosearchstoreWithdist() {\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterHashesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.HashesCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterHashesCommandsTest extends HashesCommandsTestBase {\n\n  public ClusterHashesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterHotkeysCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.args.HotkeysMetric;\nimport redis.clients.jedis.params.HotkeysParams;\n\n/**\n * Tests that HOTKEYS commands are not supported in cluster mode.\n */\n@Tag(\"integration\")\n@EnabledOnCommand(\"HOTKEYS\")\npublic class ClusterHotkeysCommandsTest {\n\n  protected UnifiedJedis jedis;\n  protected RedisProtocol protocol;\n\n  public ClusterHotkeysCommandsTest() {\n    this.protocol = RedisProtocol.RESP3;\n  }\n\n  @BeforeEach\n  public void setUp() {\n    jedis = ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (jedis != null) {\n      jedis.close();\n    }\n  }\n\n  @Test\n  public void hotkeysStartNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class,\n      () -> jedis.hotkeysStart(HotkeysParams.hotkeysParams().metrics(HotkeysMetric.CPU)));\n  }\n\n  @Test\n  public void hotkeysStopNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class, () -> jedis.hotkeysStop());\n  }\n\n  @Test\n  public void hotkeysResetNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class, () -> jedis.hotkeysReset());\n  }\n\n  @Test\n  public void hotkeysGetNotSupportedInCluster() {\n    assertThrows(UnsupportedOperationException.class, () -> jedis.hotkeysGet());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterHyperLogLogCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.HyperLogLogCommandsTestBase;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterHyperLogLogCommandsTest extends HyperLogLogCommandsTestBase {\n\n  public ClusterHyperLogLogCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void pfcounts() {\n    long status = jedis.pfadd(\"{hll}_1\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n    status = jedis.pfadd(\"{hll}_2\", \"foo\", \"bar\", \"zap\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"{hll}_3\", \"foo\", \"bar\", \"baz\");\n    assertEquals(1, status);\n    status = jedis.pfcount(\"{hll}_1\");\n    assertEquals(3, status);\n    status = jedis.pfcount(\"{hll}_2\");\n    assertEquals(3, status);\n    status = jedis.pfcount(\"{hll}_3\");\n    assertEquals(3, status);\n\n    status = jedis.pfcount(\"{hll}_1\", \"{hll}_2\");\n    assertEquals(3, status);\n\n    status = jedis.pfcount(\"{hll}_1\", \"{hll}_2\", \"{hll}_3\");\n    assertEquals(4, status);\n  }\n\n  @Test\n  @Override\n  public void pfmerge() {\n    long status = jedis.pfadd(\"{hll}1\", \"foo\", \"bar\", \"zap\", \"a\");\n    assertEquals(1, status);\n\n    status = jedis.pfadd(\"{hll}2\", \"a\", \"b\", \"c\", \"foo\");\n    assertEquals(1, status);\n\n    String mergeStatus = jedis.pfmerge(\"{hll}3\", \"{hll}1\", \"{hll}2\");\n    assertEquals(\"OK\", mergeStatus);\n\n    status = jedis.pfcount(\"{hll}3\");\n    assertEquals(6, status);\n  }\n\n  @Disabled\n  @Override\n  public void pfmergeBinary() {\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterListCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.commands.unified.ListCommandsTestBase;\nimport redis.clients.jedis.util.KeyValue;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ClusterListCommandsTest extends ListCommandsTestBase {\n\n  private final Logger logger = LoggerFactory.getLogger(getClass());\n\n  public ClusterListCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void rpoplpush() {\n    jedis.rpush(\"foo{|}\", \"a\");\n    jedis.rpush(\"foo{|}\", \"b\");\n    jedis.rpush(\"foo{|}\", \"c\");\n\n    jedis.rpush(\"dst{|}\", \"foo\");\n    jedis.rpush(\"dst{|}\", \"bar\");\n\n    String element = jedis.rpoplpush(\"foo{|}\", \"dst{|}\");\n\n    assertEquals(\"c\", element);\n\n    List<String> srcExpected = new ArrayList<>();\n    srcExpected.add(\"a\");\n    srcExpected.add(\"b\");\n\n    List<String> dstExpected = new ArrayList<>();\n    dstExpected.add(\"c\");\n    dstExpected.add(\"foo\");\n    dstExpected.add(\"bar\");\n\n    assertEquals(srcExpected, jedis.lrange(\"foo{|}\", 0, 1000));\n    assertEquals(dstExpected, jedis.lrange(\"dst{|}\", 0, 1000));\n  }\n\n  @Test\n  @Override\n  public void blpop() throws InterruptedException {\n    List<String> result = jedis.blpop(1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.blpop(1, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo\", result.get(0));\n    assertEquals(\"bar\", result.get(1));\n\n    // Multi keys\n    result = jedis.blpop(1, \"{foo}\", \"{foo}1\");\n    assertNull(result);\n\n    jedis.lpush(\"{foo}\", \"bar\");\n    jedis.lpush(\"{foo}1\", \"bar1\");\n    result = jedis.blpop(1, \"{foo}1\", \"{foo}\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"{foo}1\", result.get(0));\n    assertEquals(\"bar1\", result.get(1));\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    List<byte[]> bresult = jedis.blpop(1, bfoo);\n\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n  }\n\n  @Test\n  @Override\n  public void blpopDouble() throws InterruptedException {\n    KeyValue<String, String> result = jedis.blpop(0.1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.blpop(3.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n\n    // Multi keys\n    result = jedis.blpop(0.18, \"{foo}\", \"{foo}1\");\n    assertNull(result);\n\n    jedis.lpush(\"{foo}\", \"bar\");\n    jedis.lpush(\"{foo}1\", \"bar1\");\n    result = jedis.blpop(1d, \"{foo}1\", \"{foo}\");\n\n    assertNotNull(result);\n    assertEquals(\"{foo}1\", result.getKey());\n    assertEquals(\"bar1\", result.getValue());\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    KeyValue<byte[], byte[]> bresult = jedis.blpop(3.12, bfoo);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n  }\n\n  @Test\n  @Override\n  public void brpop() throws InterruptedException {\n    List<String> result = jedis.brpop(1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.brpop(1, \"foo\");\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"foo\", result.get(0));\n    assertEquals(\"bar\", result.get(1));\n\n    // Multi keys\n    result = jedis.brpop(1, \"{foo}\", \"{foo}1\");\n    assertNull(result);\n\n    jedis.lpush(\"{foo}\", \"bar\");\n    jedis.lpush(\"{foo}1\", \"bar1\");\n    result = jedis.brpop(1, \"{foo}1\", \"{foo}\");\n\n    assertNotNull(result);\n    assertEquals(2, result.size());\n    assertEquals(\"{foo}1\", result.get(0));\n    assertEquals(\"bar1\", result.get(1));\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    List<byte[]> bresult = jedis.brpop(1, bfoo);\n    assertNotNull(bresult);\n    assertEquals(2, bresult.size());\n    assertArrayEquals(bfoo, bresult.get(0));\n    assertArrayEquals(bbar, bresult.get(1));\n  }\n\n  @Test\n  @Override\n  public void brpopDouble() throws InterruptedException {\n    KeyValue<String, String> result = jedis.brpop(0.1, \"foo\");\n    assertNull(result);\n\n    jedis.lpush(\"foo\", \"bar\");\n    result = jedis.brpop(3.2, \"foo\");\n\n    assertNotNull(result);\n    assertEquals(\"foo\", result.getKey());\n    assertEquals(\"bar\", result.getValue());\n\n    // Multi keys\n    result = jedis.brpop(0.18, \"{foo}\", \"{foo}1\");\n    assertNull(result);\n\n    jedis.lpush(\"{foo}\", \"bar\");\n    jedis.lpush(\"{foo}1\", \"bar1\");\n    result = jedis.brpop(1d, \"{foo}1\", \"{foo}\");\n\n    assertNotNull(result);\n    assertEquals(\"{foo}1\", result.getKey());\n    assertEquals(\"bar1\", result.getValue());\n\n    // Binary\n    jedis.lpush(bfoo, bbar);\n    KeyValue<byte[], byte[]> bresult = jedis.brpop(3.12, bfoo);\n\n    assertNotNull(bresult);\n    assertArrayEquals(bfoo, bresult.getKey());\n    assertArrayEquals(bbar, bresult.getValue());\n  }\n\n  @Test\n  @Override\n  public void brpoplpush() {\n\n    new Thread(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          Thread.sleep(100);\n        } catch (InterruptedException e) {\n          logger.error(\"\", e);\n        }\n        jedis.lpush(\"foo{|}\", \"a\");\n      }\n    }).start();\n\n    String element = jedis.brpoplpush(\"foo{|}\", \"bar{|}\", 0);\n\n    assertEquals(\"a\", element);\n    assertEquals(1, jedis.llen(\"bar{|}\"));\n    assertEquals(\"a\", jedis.lrange(\"bar{|}\", 0, -1).get(0));\n  }\n\n  @Test\n  @Override\n  public void lmove() {\n    jedis.rpush(\"{|}foo\", \"bar1\", \"bar2\", \"bar3\");\n    assertEquals(\"bar3\", jedis.lmove(\"{|}foo\", \"{|}bar\", ListDirection.RIGHT, ListDirection.LEFT));\n    assertEquals(Collections.singletonList(\"bar3\"), jedis.lrange(\"{|}bar\", 0, -1));\n    assertEquals(Arrays.asList(\"bar1\", \"bar2\"), jedis.lrange(\"{|}foo\", 0, -1));\n  }\n\n  @Test\n  @Override\n  public void blmove() {\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      jedis.rpush(\"{|}foo\", \"bar1\", \"bar2\", \"bar3\");\n    }).start();\n\n    assertEquals(\"bar3\", jedis.blmove(\"{|}foo\", \"{|}bar\", ListDirection.RIGHT, ListDirection.LEFT, 0));\n    assertEquals(Collections.singletonList(\"bar3\"), jedis.lrange(\"{|}bar\", 0, -1));\n    assertEquals(Arrays.asList(\"bar1\", \"bar2\"), jedis.lrange(\"{|}foo\", 0, -1));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void lmpop() {\n    String mylist1 = \"mylist1{.}\";\n    String mylist2 = \"mylist2{.}\";\n\n    // add elements to list\n    jedis.lpush(mylist1, \"one\", \"two\", \"three\", \"four\", \"five\");\n    jedis.lpush(mylist2, \"one\", \"two\", \"three\", \"four\", \"five\");\n\n    KeyValue<String, List<String>> elements = jedis.lmpop(ListDirection.LEFT, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(1, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.LEFT, 5, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(4, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.RIGHT, 100, mylist1, mylist2);\n    assertEquals(mylist2, elements.getKey());\n    assertEquals(5, elements.getValue().size());\n\n    elements = jedis.lmpop(ListDirection.RIGHT, mylist1, mylist2);\n    assertNull(elements);\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void blmpopSimple() {\n    String mylist1 = \"mylist1{.}\";\n    String mylist2 = \"mylist2{.}\";\n\n    // add elements to list\n    jedis.lpush(mylist1, \"one\", \"two\", \"three\", \"four\", \"five\");\n    jedis.lpush(mylist2, \"one\", \"two\", \"three\", \"four\", \"five\");\n\n    KeyValue<String, List<String>> elements = jedis.blmpop(1L, ListDirection.LEFT, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(1, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.LEFT, 5, mylist1, mylist2);\n    assertEquals(mylist1, elements.getKey());\n    assertEquals(4, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.RIGHT, 100, mylist1, mylist2);\n    assertEquals(mylist2, elements.getKey());\n    assertEquals(5, elements.getValue().size());\n\n    elements = jedis.blmpop(1L, ListDirection.RIGHT, mylist1, mylist2);\n    assertNull(elements);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.SetCommandsTestBase;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ClusterSetCommandsTest extends SetCommandsTestBase {\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bfoo_same_hashslot = { 0x01, 0x02, 0x03, 0x04, 0x03, 0x00, 0x03, 0x1b };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n\n  public ClusterSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void smove() {\n    jedis.sadd(\"{.}foo\", \"a\");\n    jedis.sadd(\"{.}foo\", \"b\");\n\n    jedis.sadd(\"{.}bar\", \"c\");\n\n    long status = jedis.smove(\"{.}foo\", \"{.}bar\", \"a\");\n    assertEquals(status, 1);\n\n    Set<String> expectedSrc = new HashSet<>();\n    expectedSrc.add(\"b\");\n\n    Set<String> expectedDst = new HashSet<>();\n    expectedDst.add(\"c\");\n    expectedDst.add(\"a\");\n\n    assertEquals(expectedSrc, jedis.smembers(\"{.}foo\"));\n    assertEquals(expectedDst, jedis.smembers(\"{.}bar\"));\n\n    status = jedis.smove(\"{.}foo\", \"{.}bar\", \"a\");\n    assertEquals(status, 0);\n  }\n\n  @Test\n  @Override\n  public void sinter() {\n    jedis.sadd(\"foo{.}\", \"a\");\n    jedis.sadd(\"foo{.}\", \"b\");\n\n    jedis.sadd(\"bar{.}\", \"b\");\n    jedis.sadd(\"bar{.}\", \"c\");\n\n    Set<String> expected = new HashSet<>();\n    expected.add(\"b\");\n\n    Set<String> intersection = jedis.sinter(\"foo{.}\", \"bar{.}\");\n    assertEquals(expected, intersection);\n  }\n\n  @Test\n  @Override\n  public void sinterstore() {\n    jedis.sadd(\"foo{.}\", \"a\");\n    jedis.sadd(\"foo{.}\", \"b\");\n\n    jedis.sadd(\"bar{.}\", \"b\");\n    jedis.sadd(\"bar{.}\", \"c\");\n\n    Set<String> expected = new HashSet<>();\n    expected.add(\"b\");\n\n    long status = jedis.sinterstore(\"car{.}\", \"foo{.}\", \"bar{.}\");\n    assertEquals(1, status);\n\n    assertEquals(expected, jedis.smembers(\"car{.}\"));\n  }\n\n  @Test\n  @Override\n  public void sunion() {\n    jedis.sadd(\"{.}foo\", \"a\");\n    jedis.sadd(\"{.}foo\", \"b\");\n\n    jedis.sadd(\"{.}bar\", \"b\");\n    jedis.sadd(\"{.}bar\", \"c\");\n\n    Set<String> expected = new HashSet<>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    Set<String> union = jedis.sunion(\"{.}foo\", \"{.}bar\");\n    assertEquals(expected, union);\n  }\n\n  @Test\n  @Override\n  public void sunionstore() {\n    jedis.sadd(\"{.}foo\", \"a\");\n    jedis.sadd(\"{.}foo\", \"b\");\n\n    jedis.sadd(\"{.}bar\", \"b\");\n    jedis.sadd(\"{.}bar\", \"c\");\n\n    Set<String> expected = new HashSet<>();\n    expected.add(\"a\");\n    expected.add(\"b\");\n    expected.add(\"c\");\n\n    long status = jedis.sunionstore(\"{.}car\", \"{.}foo\", \"{.}bar\");\n    assertEquals(3, status);\n\n    assertEquals(expected, jedis.smembers(\"{.}car\"));\n  }\n\n  @Test\n  @Override\n  public void sdiff() {\n    jedis.sadd(\"foo{.}\", \"x\");\n    jedis.sadd(\"foo{.}\", \"a\");\n    jedis.sadd(\"foo{.}\", \"b\");\n    jedis.sadd(\"foo{.}\", \"c\");\n\n    jedis.sadd(\"bar{.}\", \"c\");\n\n    jedis.sadd(\"car{.}\", \"a\");\n    jedis.sadd(\"car{.}\", \"d\");\n\n    Set<String> expected = new HashSet<>();\n    expected.add(\"x\");\n    expected.add(\"b\");\n\n    Set<String> diff = jedis.sdiff(\"foo{.}\", \"bar{.}\", \"car{.}\");\n    assertEquals(expected, diff);\n  }\n\n  @Test\n  @Override\n  public void sdiffstore() {\n    jedis.sadd(\"foo{.}\", \"x\");\n    jedis.sadd(\"foo{.}\", \"a\");\n    jedis.sadd(\"foo{.}\", \"b\");\n    jedis.sadd(\"foo{.}\", \"c\");\n\n    jedis.sadd(\"bar{.}\", \"c\");\n\n    jedis.sadd(\"car{.}\", \"a\");\n    jedis.sadd(\"car{.}\", \"d\");\n\n    Set<String> expected = new HashSet<>();\n    expected.add(\"x\");\n    expected.add(\"b\");\n\n    long status = jedis.sdiffstore(\"tar{.}\", \"foo{.}\", \"bar{.}\", \"car{.}\");\n    assertEquals(2, status);\n    assertEquals(expected, jedis.smembers(\"tar{.}\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void sintercard() {\n    jedis.sadd(\"foo{.}\", \"a\");\n    jedis.sadd(\"foo{.}\", \"b\");\n\n    jedis.sadd(\"bar{.}\", \"a\");\n    jedis.sadd(\"bar{.}\", \"b\");\n    jedis.sadd(\"bar{.}\", \"c\");\n\n    long card = jedis.sintercard(\"foo{.}\", \"bar{.}\");\n    assertEquals(2, card);\n    long limitedCard = jedis.sintercard(1, \"foo{.}\", \"bar{.}\");\n    assertEquals(1, limitedCard);\n\n    // Binary\n    jedis.sadd(bfoo, ba);\n    jedis.sadd(bfoo, bb);\n\n    jedis.sadd(bfoo_same_hashslot, ba);\n    jedis.sadd(bfoo_same_hashslot, bb);\n    jedis.sadd(bfoo_same_hashslot, bc);\n\n    long bcard = jedis.sintercard(bfoo, bfoo_same_hashslot);\n    assertEquals(2, bcard);\n    long blimitedCard = jedis.sintercard(1, bfoo, bfoo_same_hashslot);\n    assertEquals(1, blimitedCard);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterSortedSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayListEquals;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.SortedSetCommandsTestBase;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZParams;\nimport redis.clients.jedis.params.ZRangeParams;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ClusterSortedSetCommandsTest extends SortedSetCommandsTestBase {\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bfoo_same_hashslot = { 0x01, 0x02, 0x03, 0x04, 0x03, 0x00, 0x03, 0x1b };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n\n  public ClusterSortedSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void zunion() {\n    jedis.zadd(\"{:}foo\", 1, \"a\");\n    jedis.zadd(\"{:}foo\", 2, \"b\");\n    jedis.zadd(\"{:}bar\", 2, \"a\");\n    jedis.zadd(\"{:}bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertThat(jedis.zunion(params, \"{:}foo\", \"{:}bar\"),\n        containsInAnyOrder(\"a\", \"b\"));\n\n    assertThat(jedis.zunionWithScores(params, \"{:}foo\", \"{:}bar\"),\n        containsInAnyOrder(\n            new Tuple(\"b\", new Double(9)),\n            new Tuple(\"a\", new Double(7))\n        ));\n  }\n\n  @Test\n  @Override\n  public void zunionstore() {\n    jedis.zadd(\"{:}foo\", 1, \"a\");\n    jedis.zadd(\"{:}foo\", 2, \"b\");\n    jedis.zadd(\"{:}bar\", 2, \"a\");\n    jedis.zadd(\"{:}bar\", 2, \"b\");\n\n    assertEquals(2, jedis.zunionstore(\"{:}dst\", \"{:}foo\", \"{:}bar\"));\n\n    List<Tuple> expected = new ArrayList<>();\n    expected.add(new Tuple(\"a\", new Double(3)));\n    expected.add(new Tuple(\"b\", new Double(4)));\n    assertEquals(expected, jedis.zrangeWithScores(\"{:}dst\", 0, 100));\n  }\n\n  @Test\n  @Override\n  public void zunionstoreParams() {\n    jedis.zadd(\"{:}foo\", 1, \"a\");\n    jedis.zadd(\"{:}foo\", 2, \"b\");\n    jedis.zadd(\"{:}bar\", 2, \"a\");\n    jedis.zadd(\"{:}bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(2, jedis.zunionstore(\"{:}dst\", params, \"{:}foo\", \"{:}bar\"));\n\n    List<Tuple> expected = new ArrayList<>();\n    expected.add(new Tuple(\"a\", new Double(7)));\n    expected.add(new Tuple(\"b\", new Double(9)));\n    assertEquals(expected, jedis.zrangeWithScores(\"{:}dst\", 0, 100));\n  }\n\n  @Test\n  @Override\n  public void zinter() {\n    jedis.zadd(\"foo{:}\", 1, \"a\");\n    jedis.zadd(\"foo{:}\", 2, \"b\");\n    jedis.zadd(\"bar{:}\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n    assertThat(jedis.zinter(params, \"foo{:}\", \"bar{:}\"),\n        containsInAnyOrder(\"a\"));\n\n    assertThat(jedis.zinterWithScores(params, \"foo{:}\", \"bar{:}\"),\n        containsInAnyOrder(new Tuple(\"a\", new Double(7))));\n  }\n\n  @Test\n  @Override\n  public void zinterstore() {\n    jedis.zadd(\"foo{:}\", 1, \"a\");\n    jedis.zadd(\"foo{:}\", 2, \"b\");\n    jedis.zadd(\"bar{:}\", 2, \"a\");\n\n    assertEquals(1, jedis.zinterstore(\"dst{:}\", \"foo{:}\", \"bar{:}\"));\n\n    assertEquals(Collections.singletonList(new Tuple(\"a\", new Double(3))),\n        jedis.zrangeWithScores(\"dst{:}\", 0, 100));\n  }\n\n  @Test\n  @Override\n  public void zintertoreParams() {\n    jedis.zadd(\"foo{:}\", 1, \"a\");\n    jedis.zadd(\"foo{:}\", 2, \"b\");\n    jedis.zadd(\"bar{:}\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    assertEquals(1, jedis.zinterstore(\"dst{:}\", params, \"foo{:}\", \"bar{:}\"));\n\n    assertEquals(Collections.singletonList(new Tuple(\"a\", new Double(7))),\n        jedis.zrangeWithScores(\"dst{:}\", 0, 100));\n  }\n\n  @Test\n  @Override\n  public void bzpopmax() {\n    assertNull(jedis.bzpopmax(1, \"f{:}oo\", \"b{:}ar\"));\n\n    jedis.zadd(\"f{:}oo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"f{:}oo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"b{:}ar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    assertEquals(new KeyValue<>(\"f{:}oo\", new Tuple(\"b\", 10d)), jedis.bzpopmax(0, \"f{:}oo\", \"b{:}ar\"));\n  }\n\n  @Test\n  @Override\n  public void bzpopmin() {\n    assertNull(jedis.bzpopmin(1, \"ba{:}r\", \"fo{:}o\"));\n\n    jedis.zadd(\"fo{:}o\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"fo{:}o\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    jedis.zadd(\"ba{:}r\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    assertEquals(new KeyValue<>(\"ba{:}r\", new Tuple(\"c\", 0.1d)), jedis.bzpopmin(0, \"ba{:}r\", \"fo{:}o\"));\n  }\n\n  @Test\n  @Override\n  public void zdiff() {\n    jedis.zadd(\"{:}foo\", 1.0, \"a\");\n    jedis.zadd(\"{:}foo\", 2.0, \"b\");\n    jedis.zadd(\"{:}bar\", 1.0, \"a\");\n\n    assertEquals(0, jedis.zdiff(\"{bar}1\", \"{bar}2\").size());\n\n    assertThat(jedis.zdiff(\"{:}foo\", \"{:}bar\"),\n        containsInAnyOrder(\"b\"));\n\n    assertThat(jedis.zdiffWithScores(\"{:}foo\", \"{:}bar\"),\n        containsInAnyOrder(new Tuple(\"b\", 2.0d)));\n  }\n\n  @Test\n  @Override\n  public void zdiffstore() {\n    jedis.zadd(\"foo{:}\", 1.0, \"a\");\n    jedis.zadd(\"foo{:}\", 2.0, \"b\");\n    jedis.zadd(\"bar{:}\", 1.0, \"a\");\n\n    assertEquals(0, jedis.zdiffstore(\"{bar}3\", \"{bar}1\", \"{bar}2\"));\n    assertEquals(1, jedis.zdiffstore(\"bar{:}3\", \"foo{:}\", \"bar{:}\"));\n    assertEquals(Collections.singletonList(\"b\"), jedis.zrange(\"bar{:}3\", 0, -1));\n  }\n\n  @Test\n  public void zrangestore() {\n    jedis.zadd(\"foo{.}\", 1, \"aa\");\n    jedis.zadd(\"foo{.}\", 2, \"c\");\n    jedis.zadd(\"foo{.}\", 3, \"bb\");\n\n    long stored = jedis.zrangestore(\"bar{.}\", \"foo{.}\", ZRangeParams.zrangeByScoreParams(1, 2));\n    assertEquals(2, stored);\n\n    List<String> range = jedis.zrange(\"bar{.}\", 0, -1);\n    List<String> expected = new ArrayList<>();\n    expected.add(\"aa\");\n    expected.add(\"c\");\n    assertEquals(expected, range);\n\n    // Binary\n    jedis.zadd(bfoo, 1d, ba);\n    jedis.zadd(bfoo, 10d, bb);\n    jedis.zadd(bfoo, 0.1d, bc);\n    jedis.zadd(bfoo, 2d, ba);\n\n    long bstored = jedis.zrangestore(bfoo_same_hashslot, bfoo, ZRangeParams.zrangeParams(0, 1).rev());\n    assertEquals(2, bstored);\n\n    List<byte[]> brange = jedis.zrevrange(bfoo_same_hashslot, 0, 1);\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bb);\n    bexpected.add(ba);\n    assertByteArrayListEquals(bexpected, brange);\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void zintercard() {\n    jedis.zadd(\"foo{.}\", 1, \"a\");\n    jedis.zadd(\"foo{.}\", 2, \"b\");\n    jedis.zadd(\"bar{.}\", 2, \"a\");\n    jedis.zadd(\"bar{.}\", 1, \"b\");\n\n    assertEquals(2, jedis.zintercard(\"foo{.}\", \"bar{.}\"));\n    assertEquals(1, jedis.zintercard(1, \"foo{.}\", \"bar{.}\"));\n\n    // Binary\n    jedis.zadd(bfoo, 1, ba);\n    jedis.zadd(bfoo, 2, bb);\n    jedis.zadd(bfoo_same_hashslot, 2, ba);\n    jedis.zadd(bfoo_same_hashslot, 2, bb);\n\n    assertEquals(2, jedis.zintercard(bfoo, bfoo_same_hashslot));\n    assertEquals(1, jedis.zintercard(1, bfoo, bfoo_same_hashslot));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsBinaryCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.StreamsBinaryCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterStreamsBinaryCommandsTest extends StreamsBinaryCommandsTestBase {\n\n  public ClusterStreamsBinaryCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return  ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.StreamsCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterStreamsCommandsTest extends StreamsCommandsTestBase {\n\n  public ClusterStreamsCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return  ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStringValuesCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.StringValuesCommandsTestBase;\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.resps.LCSMatchResult;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ClusterStringValuesCommandsTest extends StringValuesCommandsTestBase {\n\n  public ClusterStringValuesCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n\n  @Test\n  @Override\n  public void mget() {\n    List<String> values = jedis.mget(\"foo{^}\", \"bar{^}\");\n    List<String> expected = new ArrayList<>();\n    expected.add(null);\n    expected.add(null);\n\n    assertEquals(expected, values);\n\n    jedis.set(\"foo{^}\", \"bar\");\n\n    expected = new ArrayList<>();\n    expected.add(\"bar\");\n    expected.add(null);\n    values = jedis.mget(\"foo{^}\", \"bar{^}\");\n\n    assertEquals(expected, values);\n\n    jedis.set(\"bar{^}\", \"foo\");\n\n    expected = new ArrayList<>();\n    expected.add(\"bar\");\n    expected.add(\"foo\");\n    values = jedis.mget(\"foo{^}\", \"bar{^}\");\n\n    assertEquals(expected, values);\n  }\n\n  @Test\n  @Override\n  public void mset() {\n    String status = jedis.mset(\"{^}foo\", \"bar\", \"{^}bar\", \"foo\");\n    assertEquals(\"OK\", status);\n    assertEquals(\"bar\", jedis.get(\"{^}foo\"));\n    assertEquals(\"foo\", jedis.get(\"{^}bar\"));\n  }\n\n  @Test\n  @Override\n  public void msetnx() {\n    assertEquals(1, jedis.msetnx(\"{^}foo\", \"bar\", \"{^}bar\", \"foo\"));\n    assertEquals(\"bar\", jedis.get(\"{^}foo\"));\n    assertEquals(\"foo\", jedis.get(\"{^}bar\"));\n\n    assertEquals(0, jedis.msetnx(\"{^}foo\", \"bar1\", \"{^}bar2\", \"foo2\"));\n    assertEquals(\"bar\", jedis.get(\"{^}foo\"));\n    assertEquals(\"foo\", jedis.get(\"{^}bar\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\")\n  public void lcs() {\n    jedis.mset(\"key1{.}\", \"ohmytext\", \"key2{.}\", \"mynewtext\");\n\n    LCSMatchResult stringMatchResult = jedis.lcs(\"key1{.}\", \"key2{.}\", LCSParams.LCSParams());\n    assertEquals(\"mytext\", stringMatchResult.getMatchString());\n\n    stringMatchResult = jedis.lcs(\"key1{.}\", \"key2{.}\", LCSParams.LCSParams().idx().withMatchLen());\n    assertEquals(stringMatchResult.getLen(), 6);\n    assertEquals(2, stringMatchResult.getMatches().size());\n    stringMatchResult = jedis.lcs(\"key1{.}\", \"key2{.}\",\n      LCSParams.LCSParams().idx().minMatchLen(10));\n    assertEquals(0, stringMatchResult.getMatches().size());\n  }\n\n  @Test\n  @EnabledOnCommand(\"MSETEX\")\n  public void msetex_crossslot_works_with_client_side_splitting() {\n    // Use keys without a hashtag so they map to different hash slots\n    String k1 = \"cross:k1\";\n    String k2 = \"other:k2\";\n\n    MSetExParams params = new MSetExParams().nx().ex(5);\n\n    // Cross-slot msetex should work - client splits by slot\n    boolean result = jedis.msetex(params, k1, \"v1\", k2, \"v2\");\n    assertTrue(result);\n\n    // Verify values were set\n    assertEquals(\"v1\", jedis.get(k1));\n    assertEquals(\"v2\", jedis.get(k2));\n\n    // Verify TTL is set\n    assertTrue(jedis.ttl(k1) > 0);\n    assertTrue(jedis.ttl(k2) > 0);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterVectorSetCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.VectorSetCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class ClusterVectorSetCommandsTest extends VectorSetCommandsTestBase {\n\n  public ClusterVectorSetCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    return ClusterCommandsTestHelper.getCleanCluster(protocol);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    ClusterCommandsTestHelper.clearClusterData();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/cluster/search/FTHybridCommandsClusterTest.java",
    "content": "package redis.clients.jedis.commands.unified.cluster.search;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.commands.unified.cluster.ClusterCommandsTestHelper;\nimport redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class FTHybridCommandsClusterTest extends FTHybridCommandsTestBase {\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-stable\");\n  }\n\n  public FTHybridCommandsClusterTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n    UnifiedJedis cluster = ClusterCommandsTestHelper.getCleanCluster(protocol, endpoint);\n    return cluster;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/BinaryStreamsPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XCfgSetParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.resps.StreamEntryBinary;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static java.util.Collections.singletonMap;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static redis.clients.jedis.StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY;\nimport static redis.clients.jedis.util.StreamEntryBinaryListMatcher.equalsStreamEntries;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class BinaryStreamsPipelineCommandsTest extends PipelineCommandsTestBase {\n  protected static final byte[] STREAM_KEY_1 = \"{binary-stream}-1\".getBytes();\n  protected static final byte[] STREAM_KEY_2 = \"{binary-stream}-2\".getBytes();\n  protected static final byte[] GROUP_NAME = \"group-1\".getBytes();\n  protected static final byte[] CONSUMER_NAME = \"consumer-1\".getBytes();\n\n  protected static final byte[] FIELD_KEY_1 = \"binary-field-1\".getBytes();\n  protected static final byte[] BINARY_VALUE_1 = new byte[] { 0x00, 0x01, 0x02, 0x03, (byte) 0xFF };\n\n  protected static final byte[] FIELD_KEY_2 = \"binary-field-1\".getBytes();\n  protected static final byte[] BINARY_VALUE_2 = \"binary-value-2\".getBytes();\n  protected static final Map<byte[], byte[]> HASH_1 = singletonMap(FIELD_KEY_1, BINARY_VALUE_1);\n  protected static final Map<byte[], byte[]> HASH_2 = singletonMap(FIELD_KEY_2, BINARY_VALUE_2);\n\n  protected static final List<StreamEntryBinary> stream1Entries = new ArrayList<>();\n  protected static final List<StreamEntryBinary> stream2Entries = new ArrayList<>();\n\n  static {\n    stream1Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-1\"), HASH_1));\n    stream1Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-3\"), HASH_2));\n\n    stream2Entries.add(new StreamEntryBinary(new StreamEntryID(\"0-2\"), HASH_1));\n  }\n\n  public BinaryStreamsPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  /**\n   * Creates a map of stream keys to StreamEntryID objects.\n   * @param streamOffsets Array of stream key and offset pairs\n   * @return Map of stream keys to StreamEntryID objects\n   */\n  public static Map<byte[], StreamEntryID> offsets(Object... streamOffsets) {\n    if (streamOffsets.length % 2 != 0) {\n      throw new IllegalArgumentException(\"Stream offsets must be provided as key-value pairs\");\n    }\n\n    Map<byte[], StreamEntryID> result = new HashMap<>();\n    for (int i = 0; i < streamOffsets.length; i += 2) {\n      byte[] key = (byte[]) streamOffsets[i];\n      Object value = streamOffsets[i + 1];\n\n      StreamEntryID id;\n      if (value instanceof String) {\n        id = new StreamEntryID((String) value);\n      } else if (value instanceof StreamEntryID) {\n        id = (StreamEntryID) value;\n      } else {\n        throw new IllegalArgumentException(\"Offset must be a String or StreamEntryID\");\n      }\n\n      result.put(key, id);\n    }\n\n    return result;\n  }\n\n  @BeforeEach\n  public void setUpTestStream() {\n    client.del(STREAM_KEY_1);\n    client.del(STREAM_KEY_2);\n    try {\n      client.xgroupCreate(STREAM_KEY_1, GROUP_NAME,\n          StreamEntryID.XGROUP_LAST_ENTRY.toString().getBytes(), true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n    try {\n      client.xgroupCreate(STREAM_KEY_2, GROUP_NAME,\n          StreamEntryID.XGROUP_LAST_ENTRY.toString().getBytes(), true);\n    } catch (JedisDataException e) {\n      if (!e.getMessage().contains(\"BUSYGROUP\")) {\n        throw e;\n      }\n    }\n  }\n\n  @Test\n  public void xreadBinary() {\n\n    stream1Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Response<List<Map.Entry<byte[], List<StreamEntryBinary>>>> response = pipe.xreadBinary(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n    \n    pipe.sync();\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = response.get();\n\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadBinaryAsMap() {\n\n    stream1Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Response<Map<byte[], List<StreamEntryBinary>>> response = pipe.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\"));\n\n    pipe.sync();\n    Map<byte[], List<StreamEntryBinary>> actualEntries = response.get();\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadBinaryAsMapWithMultipleStreams() {\n\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n    stream2Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_2, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Response<Map<byte[], List<StreamEntryBinary>>> response = pipe.xreadBinaryAsMap(\n        XReadParams.xReadParams(), offsets(STREAM_KEY_1, \"0-0\", STREAM_KEY_2, \"0-0\"));\n\n    pipe.sync();\n    Map<byte[], List<StreamEntryBinary>> actualEntries = response.get();\n\n    assertThat(actualEntries.entrySet(), hasSize(2));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n    assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries));\n  }\n\n  @Test\n  public void xreadGroupBinary() {\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Response<List<Map.Entry<byte[], List<StreamEntryBinary>>>> response = pipe.xreadGroupBinary(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY));\n\n    pipe.sync();\n    List<Map.Entry<byte[], List<StreamEntryBinary>>> actualEntries = response.get();\n\n    // verify the result contains entries from one stream\n    // and is under the expected stream key\n    assertThat(actualEntries, hasSize(1));\n    assertArrayEquals(STREAM_KEY_1, actualEntries.get(0).getKey());\n\n    assertThat(actualEntries.get(0).getValue(), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadGroupBinaryAsMap() {\n    stream1Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Response<Map<byte[], List<StreamEntryBinary>>> response = pipe.xreadGroupBinaryAsMap(\n        GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY));\n\n    pipe.sync();\n    Map<byte[], List<StreamEntryBinary>> actualEntries = response.get();\n\n    assertThat(actualEntries.entrySet(), hasSize(1));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n  }\n\n  @Test\n  public void xreadGroupBinaryAsMapMultipleStreams() {\n    // Add entries to the streams\n    stream1Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_1, new XAddParams().id(entry.getID()), entry.getFields()));\n    stream2Entries.forEach(\n        entry -> client.xadd(STREAM_KEY_2, new XAddParams().id(entry.getID()), entry.getFields()));\n\n    Response<Map<byte[], List<StreamEntryBinary>>> response = pipe.xreadGroupBinaryAsMap(GROUP_NAME,\n        CONSUMER_NAME, XReadGroupParams.xReadGroupParams(),\n        offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY, STREAM_KEY_2,\n            XREADGROUP_UNDELIVERED_ENTRY));\n\n    pipe.sync();\n    Map<byte[], List<StreamEntryBinary>> actualEntries = response.get();\n\n    assertThat(actualEntries.entrySet(), hasSize(2));\n\n    assertThat(actualEntries.get(STREAM_KEY_1), equalsStreamEntries(stream1Entries));\n    assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void xcfgset() {\n    // Add an entry to create the stream\n    client.xadd(STREAM_KEY_1, new XAddParams().id(StreamEntryID.NEW_ENTRY), HASH_1);\n\n    // Configure idempotent producer settings via pipeline\n    Response<byte[]> response = pipe.xcfgset(STREAM_KEY_1,\n        XCfgSetParams.xCfgSetParams().idmpDuration(1000).idmpMaxsize(500));\n\n    pipe.sync();\n\n    assertArrayEquals(\"OK\".getBytes(), response.get());\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/GeoPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static redis.clients.jedis.util.GeoCoordinateMatcher.atCoordinates;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class GeoPipelineCommandsTest extends PipelineCommandsTestBase {\n\n  protected final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  protected final byte[] bA = { 0x0A };\n  protected final byte[] bB = { 0x0B };\n  protected final byte[] bC = { 0x0C };\n  protected final byte[] bNotexist = { 0x0F };\n\n  private static final double EPSILON = 1e-5;\n\n  public GeoPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void geoadd() {\n    pipe.geoadd(\"foo\", 1, 2, \"a\");\n    pipe.geoadd(\"foo\", 2, 3, \"a\");\n\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    coordinateMap.put(\"b\", new GeoCoordinate(2, 3));\n    coordinateMap.put(\"c\", new GeoCoordinate(3.314, 2.3241));\n\n    pipe.geoadd(\"foo\", coordinateMap);\n\n    // binary\n    pipe.geoadd(bfoo, 1, 2, bA);\n    pipe.geoadd(bfoo, 2, 3, bA);\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    bcoordinateMap.put(bB, new GeoCoordinate(2, 3));\n    bcoordinateMap.put(bC, new GeoCoordinate(3.314, 2.3241));\n\n    pipe.geoadd(bfoo, bcoordinateMap);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        0L,\n        2L,\n        1L,\n        0L,\n        2L\n    ));\n  }\n\n  @Test\n  public void geoaddWithParams() {\n    pipe.geoadd(\"foo\", 1, 2, \"a\");\n\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    pipe.geoadd(\"foo\", GeoAddParams.geoAddParams().nx(), coordinateMap);\n    pipe.geoadd(\"foo\", GeoAddParams.geoAddParams().xx().ch(), coordinateMap);\n\n    coordinateMap.clear();\n    coordinateMap.put(\"b\", new GeoCoordinate(6, 7));\n    // never add elements.\n    pipe.geoadd(\"foo\", GeoAddParams.geoAddParams().xx(), coordinateMap);\n    pipe.geoadd(\"foo\", GeoAddParams.geoAddParams().nx(), coordinateMap);\n\n    // binary\n    pipe.geoadd(bfoo, 1, 2, bA);\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    pipe.geoadd(bfoo, GeoAddParams.geoAddParams().nx(), bcoordinateMap);\n    pipe.geoadd(bfoo, GeoAddParams.geoAddParams().xx().ch(), bcoordinateMap);\n\n    bcoordinateMap.clear();\n    bcoordinateMap.put(bB, new GeoCoordinate(6, 7));\n    // never add elements.\n    pipe.geoadd(bfoo, GeoAddParams.geoAddParams().xx(), bcoordinateMap);\n    pipe.geoadd(bfoo, GeoAddParams.geoAddParams().nx(), bcoordinateMap);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        0L,\n        1L,\n        0L,\n        1L,\n        1L,\n        0L,\n        1L,\n        0L,\n        1L\n    ));\n  }\n\n  @Test\n  public void geodist() {\n    prepareGeoData();\n\n    Response<Double> dist1 = pipe.geodist(\"foo\", \"a\", \"b\");\n    Response<Double> dist2 = pipe.geodist(\"foo\", \"a\", \"b\", GeoUnit.KM);\n    Response<Double> dist3 = pipe.geodist(\"foo\", \"a\", \"b\", GeoUnit.MI);\n    Response<Double> dist4 = pipe.geodist(\"foo\", \"a\", \"b\", GeoUnit.FT);\n\n    // binary\n    Response<Double> dist5 = pipe.geodist(bfoo, bA, bB);\n    Response<Double> dist6 = pipe.geodist(bfoo, bA, bB, GeoUnit.KM);\n    Response<Double> dist7 = pipe.geodist(bfoo, bA, bB, GeoUnit.MI);\n    Response<Double> dist8 = pipe.geodist(bfoo, bA, bB, GeoUnit.FT);\n\n    pipe.sync();\n\n    assertThat(dist1.get(), closeTo(157149.0, 1.0));\n    assertThat(dist2.get(), closeTo(157.0, 1.0));\n    assertThat(dist3.get(), closeTo(97.0, 1.0));\n    assertThat(dist4.get(), closeTo(515583.0, 1.0));\n    assertThat(dist5.get(), closeTo(157149.0, 1.0));\n    assertThat(dist6.get(), closeTo(157.0, 1.0));\n    assertThat(dist7.get(), closeTo(97.0, 1.0));\n    assertThat(dist8.get(), closeTo(515583.0, 1.0));\n  }\n\n  @Test\n  public void geohash() {\n    prepareGeoData();\n\n    Response<List<String>> hashes = pipe.geohash(\"foo\", \"a\", \"b\", \"notexist\");\n    Response<List<byte[]>> bhashes = pipe.geohash(bfoo, bA, bB, bNotexist);\n\n    pipe.sync();\n\n    assertThat(hashes.get(), contains(\n        \"s0dnu20t9j0\",\n        \"s093jd0k720\",\n        null\n    ));\n\n    assertThat(bhashes.get(), contains(\n        SafeEncoder.encode(\"s0dnu20t9j0\"),\n        SafeEncoder.encode(\"s093jd0k720\"),\n        null\n    ));\n  }\n\n  @Test\n  public void geopos() {\n    prepareGeoData();\n\n    Response<List<GeoCoordinate>> coordinates = pipe.geopos(\"foo\", \"a\", \"b\", \"notexist\");\n    Response<List<GeoCoordinate>> bcoordinates = pipe.geopos(bfoo, bA, bB, bNotexist);\n\n    pipe.sync();\n\n    assertThat(coordinates.get(), contains(atCoordinates(3.0, 4.0),\n        atCoordinates(2.0, 3.0),\n        null\n    ));\n\n    assertThat(bcoordinates.get(), contains(atCoordinates(3.0, 4.0),\n        atCoordinates(2.0, 3.0),\n        null\n    ));\n  }\n\n  @Test\n  public void georadius() {\n    // prepare data\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    client.geoadd(\"Sicily\", coordinateMap);\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadius(\"Sicily\", 15, 37, 200, GeoUnit.KM);\n\n    // sort\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadius(\"Sicily\", 15, 37, 200,\n        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortDescending());\n\n    // sort, count 1\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadius(\"Sicily\", 15, 37, 200,\n        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n\n    // sort, count 1, withdist, withcoord\n    Response<List<GeoRadiusResponse>> members4 = pipe.georadius(\"Sicily\", 15, 37, 200,\n        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist().withHash());\n\n    // sort, count 1, with hash\n    Response<List<GeoRadiusResponse>> members5 = pipe.georadius(\"Sicily\", 15, 37, 200,\n        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withHash());\n\n    // sort, count 1, any\n    Response<List<GeoRadiusResponse>> members6 = pipe.georadius(\"Sicily\", 15, 37, 200,\n        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortDescending().count(1, true));\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(\"Palermo\", \"Catania\"));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Palermo\", \"Catania\"));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Catania\"));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Catania\"));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(56.4413, EPSILON)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(15.087269, 37.502669)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(3479447370796909L));\n\n    assertThat(members5.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Catania\"));\n    assertThat(members5.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members5.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members5.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(3479447370796909L));\n\n    assertThat(members6.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        anyOf(contains(\"Catania\"), contains(\"Palermo\")));\n    assertThat(members6.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members6.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members6.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusStore() {\n    // prepare data\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    client.geoadd(\"Sicily\", coordinateMap);\n\n    Response<Long> size = pipe.georadiusStore(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n\n    Response<List<String>> items = pipe.zrange(\"SicilyStore\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(size.get(), equalTo(2L));\n    assertThat(items.get(), contains(\"Palermo\", \"Catania\"));\n  }\n\n  @Test\n  public void georadiusReadonly() {\n    // prepare data\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    coordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    client.geoadd(\"Sicily\", coordinateMap);\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM);\n\n    // sort\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    // sort, count 1\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n\n    // sort, count 1, withdist, withcoord\n    Response<List<GeoRadiusResponse>> members4 = pipe.georadiusReadonly(\"Sicily\", 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(\"Palermo\", \"Catania\"));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Catania\", \"Palermo\"));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Catania\"));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Catania\"));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(56.4413, EPSILON)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(15.087269, 37.502669)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  public void georadiusBinary() {\n    // prepare data\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    client.geoadd(bfoo, bcoordinateMap);\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadius(bfoo, 15, 37, 200, GeoUnit.KM);\n\n    // sort\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadius(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    // sort, count 1\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadius(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n\n    // sort, count 1, withdist, withcoord\n    Response<List<GeoRadiusResponse>> members4 = pipe.georadius(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(bA, bB));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bB, bA));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bB));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bB));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(56.4413, EPSILON)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(15.087269, 37.502669)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusStoreBinary() {\n    // prepare data\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    client.geoadd(bfoo, bcoordinateMap);\n\n    Response<Long> size = pipe.georadiusStore(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n\n    Response<List<byte[]>> items = pipe.zrange(\"SicilyStore\".getBytes(), 0, -1);\n\n    pipe.sync();\n\n    assertThat(size.get(), equalTo(2L));\n    assertThat(items.get(), contains(bA, bB));\n  }\n\n  @Test\n  public void georadiusReadonlyBinary() {\n    // prepare data\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(13.361389, 38.115556));\n    bcoordinateMap.put(bB, new GeoCoordinate(15.087269, 37.502669));\n    client.geoadd(bfoo, bcoordinateMap);\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM);\n\n    // sort\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    // sort, count 1\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1));\n\n    // sort, count 1, withdist, withcoord\n    Response<List<GeoRadiusResponse>> members4 = pipe.georadiusReadonly(bfoo, 15, 37, 200, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(bA, bB));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bB, bA));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bB));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bB));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(56.4413, EPSILON)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(15.087269, 37.502669)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  public void georadiusByMember() {\n    client.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    client.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    client.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadiusByMember(\"Sicily\", \"Agrigento\", 100,\n        GeoUnit.KM);\n\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadiusByMember(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadiusByMember(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(\"Agrigento\", \"Palermo\"));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Agrigento\", \"Palermo\"));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Agrigento\"));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(13.583333, 37.316667)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusByMemberStore() {\n    client.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    client.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    client.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    Response<Long> size = pipe.georadiusByMemberStore(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n\n    Response<List<String>> items = pipe.zrange(\"SicilyStore\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(size.get(), equalTo(2L));\n    assertThat(items.get(), contains(\"Agrigento\", \"Palermo\"));\n  }\n\n  @Test\n  public void georadiusByMemberReadonly() {\n    client.geoadd(\"Sicily\", 13.583333, 37.316667, \"Agrigento\");\n    client.geoadd(\"Sicily\", 13.361389, 38.115556, \"Palermo\");\n    client.geoadd(\"Sicily\", 15.087269, 37.502669, \"Catania\");\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100,\n        GeoUnit.KM);\n\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadiusByMemberReadonly(\"Sicily\", \"Agrigento\", 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        containsInAnyOrder(\"Agrigento\", \"Palermo\"));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Agrigento\", \"Palermo\"));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"Agrigento\"));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(13.583333, 37.316667)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  public void georadiusByMemberBinary() {\n    client.geoadd(bfoo, 13.583333, 37.316667, bA);\n    client.geoadd(bfoo, 13.361389, 38.115556, bB);\n    client.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadiusByMember(bfoo, bA, 100, GeoUnit.KM);\n\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadiusByMember(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadiusByMember(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(bA, bB));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bA, bB));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bA));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(13.583333, 37.316667)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void georadiusByMemberStoreBinary() {\n    client.geoadd(bfoo, 13.583333, 37.316667, bA);\n    client.geoadd(bfoo, 13.361389, 38.115556, bB);\n    client.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    Response<Long> size = pipe.georadiusByMemberStore(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam(),\n        GeoRadiusStoreParam.geoRadiusStoreParam().store(\"SicilyStore\"));\n\n    Response<List<byte[]>> items = pipe.zrange(\"SicilyStore\".getBytes(), 0, -1);\n\n    pipe.sync();\n\n    assertThat(size.get(), equalTo(2L));\n    assertThat(items.get(), contains(bA, bB));\n  }\n\n  @Test\n  public void georadiusByMemberReadonlyBinary() {\n    client.geoadd(bfoo, 13.583333, 37.316667, bA);\n    client.geoadd(bfoo, 13.361389, 38.115556, bB);\n    client.geoadd(bfoo, 15.087269, 37.502669, bC);\n\n    Response<List<GeoRadiusResponse>> members1 = pipe.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM);\n\n    Response<List<GeoRadiusResponse>> members2 = pipe.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending());\n\n    Response<List<GeoRadiusResponse>> members3 = pipe.georadiusByMemberReadonly(bfoo, bA, 100, GeoUnit.KM,\n        GeoRadiusParam.geoRadiusParam().sortAscending().count(1).withCoord().withDist());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        containsInAnyOrder(bA, bB));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bA, bB));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(0.0, EPSILON)));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue(), nullValue()));\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L, 0L));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMember).collect(Collectors.toList()),\n        contains(bA));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(13.583333, 37.316667)));\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  public void geosearch() {\n    client.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    client.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n    client.geoadd(\"barcelona\", 2.583333d, 41.316667d, \"place3\");\n\n    // FROMLONLAT and BYRADIUS\n    Response<List<GeoRadiusResponse>> members1 = pipe.geosearch(\"barcelona\",\n        new GeoCoordinate(2.191d, 41.433d), 1000, GeoUnit.M);\n\n    // using Params\n    Response<List<GeoRadiusResponse>> members2 = pipe.geosearch(\"barcelona\", new GeoSearchParam().byRadius(3000, GeoUnit.M)\n        .fromLonLat(2.191d, 41.433d).desc());\n\n    // FROMMEMBER and BYRADIUS\n    Response<List<GeoRadiusResponse>> members3 = pipe.geosearch(\"barcelona\", \"place3\", 100, GeoUnit.KM);\n\n    // using Params\n    Response<List<GeoRadiusResponse>> members4 = pipe.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"place1\")\n        .byRadius(100, GeoUnit.KM).withDist().withCoord().withHash().count(2));\n\n    // FROMMEMBER and BYBOX\n    Response<List<GeoRadiusResponse>> members5 = pipe.geosearch(\"barcelona\", \"place3\", 100, 100, GeoUnit.KM);\n\n    // using Params\n    Response<List<GeoRadiusResponse>> members6 = pipe.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"place3\")\n        .byBox(100, 100, GeoUnit.KM).asc().count(1, true));\n\n    // FROMLONLAT and BYBOX\n    Response<List<GeoRadiusResponse>> members7 = pipe.geosearch(\"barcelona\", new GeoCoordinate(2.191, 41.433),\n        1, 1, GeoUnit.KM);\n\n    // using Params\n    Response<List<GeoRadiusResponse>> members8 = pipe.geosearch(\"barcelona\", new GeoSearchParam().byBox(1, 1, GeoUnit.KM)\n        .fromLonLat(2.191, 41.433).withDist().withCoord());\n\n    pipe.sync();\n\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place1\"));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON)));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(nullValue()));\n    assertThat(members1.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n\n    assertThat(members2.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place2\", \"place1\"));\n\n    assertThat(members3.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place2\", \"place1\", \"place3\"));\n\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place1\", \"place2\"));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0, EPSILON), closeTo(3.0674, EPSILON)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(2.1909389952632d, 41.433791470673d), atCoordinates(2.1873744593677d, 41.406342043777d)));\n    assertThat(members4.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(3471609698139488L, 3471609625421029L));\n\n    assertThat(members5.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place2\", \"place1\", \"place3\"));\n\n    assertThat(members6.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place2\"));\n\n    assertThat(members7.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place1\"));\n\n    assertThat(members8.get().stream().map(GeoRadiusResponse::getMemberByString).collect(Collectors.toList()),\n        contains(\"place1\"));\n    assertThat(members8.get().stream().map(GeoRadiusResponse::getDistance).collect(Collectors.toList()),\n        contains(closeTo(0.0881, EPSILON)));\n    assertThat(members8.get().stream().map(GeoRadiusResponse::getCoordinate).collect(Collectors.toList()),\n        contains(atCoordinates(2.1909389952632d, 41.433791470673d)));\n    assertThat(members8.get().stream().map(GeoRadiusResponse::getRawScore).collect(Collectors.toList()),\n        contains(0L));\n  }\n\n  @Test\n  public void geosearchSearchParamCombineFromMemberAndFromLonLat() {\n    assertThrows(IllegalArgumentException.class, () -> pipe.geosearch(\"barcelona\",\n        new GeoSearchParam().fromMember(\"foobar\").fromLonLat(10, 10)));\n  }\n\n  @Test\n  public void geosearchSearchParamWithoutFromMemberAndFromLonLat() {\n    assertThrows(IllegalArgumentException.class,\n        () -> pipe.geosearch(\"barcelona\", new GeoSearchParam().byRadius(10, GeoUnit.MI)));\n  }\n\n  @Test\n  public void geosearchSearchParamCombineByRadiousAndByBox() {\n    assertThrows(IllegalArgumentException.class, () -> pipe.geosearch(\"barcelona\",\n        new GeoSearchParam().byRadius(3000, GeoUnit.M).byBox(300, 300, GeoUnit.M)));\n  }\n\n  @Test\n  public void geosearchSearchParamWithoutByRadiousAndByBox() {\n    assertThrows(IllegalArgumentException.class,\n        () -> pipe.geosearch(\"barcelona\", new GeoSearchParam().fromMember(\"foobar\")));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void geosearchstore() {\n    client.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    client.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n    client.geoadd(\"barcelona\", 2.583333d, 41.316667d, \"place3\");\n\n    // FROMLONLAT and BYRADIUS\n    Response<Long> membersCount1 = pipe.geosearchStore(\"tel-aviv\", \"barcelona\",\n        new GeoCoordinate(2.191d, 41.433d), 1000, GeoUnit.M);\n\n    Response<List<String>> members1 = pipe.zrange(\"tel-aviv\", 0, -1);\n\n    Response<Long> membersCount2 = pipe.geosearchStore(\"tel-aviv\", \"barcelona\", new GeoSearchParam()\n        .byRadius(3000, GeoUnit.M)\n        .fromLonLat(new GeoCoordinate(2.191d, 41.433d)));\n\n    // FROMMEMBER and BYRADIUS\n    Response<Long> membersCount3 = pipe.geosearchStore(\"tel-aviv\", \"barcelona\", \"place3\", 100, GeoUnit.KM);\n\n    // FROMMEMBER and BYBOX\n    Response<Long> membersCount4 = pipe.geosearchStore(\"tel-aviv\", \"barcelona\", \"place3\", 100, 100, GeoUnit.KM);\n\n    // FROMLONLAT and BYBOX\n    Response<Long> membersCount5 = pipe.geosearchStore(\"tel-aviv\", \"barcelona\",\n        new GeoCoordinate(2.191, 41.433), 1, 1, GeoUnit.KM);\n\n    pipe.sync();\n\n    assertThat(membersCount1.get(), equalTo(1L));\n    assertThat(members1.get(), contains(\"place1\"));\n    assertThat(membersCount2.get(), equalTo(2L));\n    assertThat(membersCount3.get(), equalTo(3L));\n    assertThat(membersCount4.get(), equalTo(3L));\n    assertThat(membersCount5.get(), equalTo(1L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void geosearchstoreWithdist() {\n    client.geoadd(\"barcelona\", 2.1909389952632d, 41.433791470673d, \"place1\");\n    client.geoadd(\"barcelona\", 2.1873744593677d, 41.406342043777d, \"place2\");\n\n    Response<Long> members = pipe.geosearchStoreStoreDist(\"tel-aviv\", \"barcelona\",\n        new GeoSearchParam().byRadius(3000, GeoUnit.M).fromLonLat(2.191d, 41.433d));\n\n    Response<Double> score = pipe.zscore(\"tel-aviv\", \"place1\");\n\n    pipe.sync();\n\n    assertThat(members.get(), equalTo(2L));\n    assertThat(score.get(), closeTo(88.05060698409301, 5));\n  }\n\n  private void prepareGeoData() {\n    Map<String, GeoCoordinate> coordinateMap = new HashMap<>();\n    coordinateMap.put(\"a\", new GeoCoordinate(3, 4));\n    coordinateMap.put(\"b\", new GeoCoordinate(2, 3));\n    coordinateMap.put(\"c\", new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(3, client.geoadd(\"foo\", coordinateMap));\n\n    Map<byte[], GeoCoordinate> bcoordinateMap = new HashMap<>();\n    bcoordinateMap.put(bA, new GeoCoordinate(3, 4));\n    bcoordinateMap.put(bB, new GeoCoordinate(2, 3));\n    bcoordinateMap.put(bC, new GeoCoordinate(3.314, 2.3241));\n\n    assertEquals(3, client.geoadd(bfoo, bcoordinateMap));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/HashesPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport static redis.clients.jedis.util.AssertUtil.assertPipelineSyncAll;\n\nimport java.util.*;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class HashesPipelineCommandsTest extends PipelineCommandsTestBase {\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public HashesPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void hset() {\n    pipe.hset(\"foo\", \"bar\", \"car\");\n    pipe.hset(\"foo\", \"bar\", \"foo\");\n\n    // Binary\n    pipe.hset(bfoo, bbar, bcar);\n    pipe.hset(bfoo, bbar, bfoo);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(1L, 0L, 1L, 0L),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hget() {\n    pipe.hset(\"foo\", \"bar\", \"car\");\n    pipe.hget(\"bar\", \"foo\");\n    pipe.hget(\"foo\", \"car\");\n    pipe.hget(\"foo\", \"bar\");\n\n    // Binary\n    pipe.hset(bfoo, bbar, bcar);\n    pipe.hget(bbar, bfoo);\n    pipe.hget(bfoo, bcar);\n    pipe.hget(bfoo, bbar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            1L, null, null, \"car\",\n            1L, null, null, bcar),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hsetnx() {\n    pipe.hsetnx(\"foo\", \"bar\", \"car\");\n    pipe.hget(\"foo\", \"bar\");\n\n    pipe.hsetnx(\"foo\", \"bar\", \"foo\");\n    pipe.hget(\"foo\", \"bar\");\n\n    pipe.hsetnx(\"foo\", \"car\", \"bar\");\n    pipe.hget(\"foo\", \"car\");\n\n    // Binary\n    pipe.hsetnx(bfoo, bbar, bcar);\n    pipe.hget(bfoo, bbar);\n\n    pipe.hsetnx(bfoo, bbar, bfoo);\n    pipe.hget(bfoo, bbar);\n\n    pipe.hsetnx(bfoo, bcar, bbar);\n    pipe.hget(bfoo, bcar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            1L, \"car\", 0L, \"car\", 1L, \"bar\",\n            1L, bcar, 0L, bcar, 1L, bbar),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hmset() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hmset(\"foo\", hash);\n    pipe.hget(\"foo\", \"bar\");\n    pipe.hget(\"foo\", \"car\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hmset(bfoo, bhash);\n    pipe.hget(bfoo, bbar);\n    pipe.hget(bfoo, bcar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\"OK\", \"car\", \"bar\", \"OK\", bcar, bbar),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hsetVariadic() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n    pipe.hget(\"foo\", \"bar\");\n    pipe.hget(\"foo\", \"car\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n    pipe.hget(bfoo, bbar);\n    pipe.hget(bfoo, bcar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(2L, \"car\", \"bar\", 2L, bcar, bbar),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hmget() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hmset(\"foo\", hash);\n\n    pipe.hmget(\"foo\", \"bar\", \"car\", \"foo\");\n    List<String> expected = new ArrayList<>();\n    expected.add(\"car\");\n    expected.add(\"bar\");\n    expected.add(null);\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hmset(bfoo, bhash);\n\n    pipe.hmget(bfoo, bbar, bcar, bfoo);\n    List<byte[]> bexpected = new ArrayList<>();\n    bexpected.add(bcar);\n    bexpected.add(bbar);\n    bexpected.add(null);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            \"OK\", Arrays.asList(\"car\", \"bar\", null),\n            \"OK\", Arrays.asList(bcar, bbar, null)),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hincrBy() {\n    pipe.hincrBy(\"foo\", \"bar\", 1);\n    pipe.hincrBy(\"foo\", \"bar\", -1);\n    pipe.hincrBy(\"foo\", \"bar\", -10);\n\n    // Binary\n    pipe.hincrBy(bfoo, bbar, 1);\n    pipe.hincrBy(bfoo, bbar, -1);\n    pipe.hincrBy(bfoo, bbar, -10);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(1L, 0L, -10L, 1L, 0L, -10L),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hincrByFloat() {\n    pipe.hincrByFloat(\"foo\", \"bar\", 1.5d);\n    pipe.hincrByFloat(\"foo\", \"bar\", -1.5d);\n    pipe.hincrByFloat(\"foo\", \"bar\", -10.7d);\n\n    // Binary\n    pipe.hincrByFloat(bfoo, bbar, 1.5d);\n    pipe.hincrByFloat(bfoo, bbar, -1.5d);\n    pipe.hincrByFloat(bfoo, bbar, -10.7d);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(1.5, 0d, -10.7, 1.5, 0d, -10.7),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hexists() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n\n    pipe.hexists(\"bar\", \"foo\");\n    pipe.hexists(\"foo\", \"foo\");\n    pipe.hexists(\"foo\", \"bar\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n\n    pipe.hexists(bbar, bfoo);\n    pipe.hexists(bfoo, bfoo);\n    pipe.hexists(bfoo, bbar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            2L, false, false, true,\n            2L, false, false, true),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hdel() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n\n    pipe.hdel(\"bar\", \"foo\");\n    pipe.hdel(\"foo\", \"foo\");\n    pipe.hdel(\"foo\", \"bar\");\n    pipe.hget(\"foo\", \"bar\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n\n    pipe.hdel(bbar, bfoo);\n    pipe.hdel(bfoo, bfoo);\n    pipe.hdel(bfoo, bbar);\n    pipe.hget(bfoo, bbar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            2L, 0L, 0L, 1L, null,\n            2L, 0L, 0L, 1L, null),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hlen() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n\n    pipe.hlen(\"bar\");\n    pipe.hlen(\"foo\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n\n    pipe.hlen(bbar);\n    pipe.hlen(bfoo);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(2L, 0L, 2L, 2L, 0L, 2L),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hkeys() {\n    Map<String, String> hash = new LinkedHashMap<>();\n    hash.put(\"bar\", \"car\");\n    hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n\n    pipe.hkeys(\"foo\");\n    Set<String> expected = new LinkedHashSet<>();\n    expected.add(\"bar\");\n    expected.add(\"car\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new LinkedHashMap<>();\n    bhash.put(bbar, bcar);\n    bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n\n    pipe.hkeys(bfoo);\n    Set<byte[]> bexpected = new LinkedHashSet<>();\n    bexpected.add(bbar);\n    bexpected.add(bcar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            2L, new HashSet<>(Arrays.asList(\"bar\", \"car\")),\n            2L, new HashSet<>(Arrays.asList(bbar, bcar))),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hvals() {\n    Map<String, String> hash = new LinkedHashMap<>();\n    hash.put(\"bar\", \"car\");\n    //hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n\n    pipe.hvals(\"foo\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new LinkedHashMap<>();\n    bhash.put(bbar, bcar);\n    //bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n\n    pipe.hvals(bfoo);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(\n            //2L, Arrays.asList(\"bar\", \"car\"),\n            //2L, Arrays.asList(bbar, bcar)),\n            1L, Arrays.asList(\"car\"),\n            1L, Arrays.asList(bcar)),\n        pipe.syncAndReturnAll());\n  }\n\n  @Test\n  public void hgetAll() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"bar\", \"car\");\n    //hash.put(\"car\", \"bar\");\n    pipe.hset(\"foo\", hash);\n\n    pipe.hgetAll(\"foo\");\n\n    // Binary\n    Map<byte[], byte[]> bhash = new HashMap<>();\n    bhash.put(bbar, bcar);\n    //bhash.put(bcar, bbar);\n    pipe.hset(bfoo, bhash);\n\n    pipe.hgetAll(bfoo);\n\n//    assertPipelineSyncAll(\n//        Arrays.<Object>asList(\n//            1L, hash,\n//            1L, bhash),\n//        pipe.syncAndReturnAll());\n    pipe.syncAndReturnAll();\n  }\n\n  @Test\n  public void hstrlen() {\n    pipe.hstrlen(\"foo\", \"key\");\n    pipe.hset(\"foo\", \"key\", \"value\");\n    pipe.hstrlen(\"foo\", \"key\");\n\n    pipe.hstrlen(bfoo, bbar);\n    pipe.hset(bfoo, bbar, bcar);\n    pipe.hstrlen(bfoo, bbar);\n\n    assertPipelineSyncAll(\n        Arrays.<Object>asList(0L, 1L, 5L, 0L, 1L, 4L),\n        pipe.syncAndReturnAll());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/ListPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ListPipelineCommandsTest extends PipelineCommandsTestBase {\n\n  private final Logger logger = LoggerFactory.getLogger(getClass());\n\n  protected final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  protected final byte[] bfoo1 = { 0x01, 0x02, 0x03, 0x04, 0x05 };\n  protected final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  protected final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  protected final byte[] bA = { 0x0A };\n  protected final byte[] bB = { 0x0B };\n  protected final byte[] bC = { 0x0C };\n  protected final byte[] b1 = { 0x01 };\n  protected final byte[] b2 = { 0x02 };\n  protected final byte[] b3 = { 0x03 };\n  protected final byte[] bhello = { 0x04, 0x02 };\n  protected final byte[] bx = { 0x02, 0x04 };\n  protected final byte[] bdst = { 0x11, 0x12, 0x13, 0x14 };\n\n  public ListPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void rpush() {\n    pipe.rpush(\"foo\", \"bar\");\n    pipe.rpush(\"foo\", \"foo\");\n    pipe.rpush(\"foo\", \"bar\", \"foo\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        2L,\n        4L\n    ));\n\n    // Binary\n    pipe.rpush(bfoo, bbar);\n    pipe.rpush(bfoo, bfoo);\n    pipe.rpush(bfoo, bbar, bfoo);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        2L,\n        4L\n    ));\n  }\n\n  @Test\n  public void lpush() {\n    pipe.lpush(\"foo\", \"bar\");\n    pipe.lpush(\"foo\", \"foo\");\n    pipe.lpush(\"foo\", \"bar\", \"foo\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        2L,\n        4L\n    ));\n\n    // Binary\n    pipe.lpush(bfoo, bbar);\n    pipe.lpush(bfoo, bfoo);\n    pipe.lpush(bfoo, bbar, bfoo);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        2L,\n        4L\n    ));\n  }\n\n  @Test\n  public void llen() {\n    pipe.llen(\"foo\");\n    pipe.lpush(\"foo\", \"bar\");\n    pipe.lpush(\"foo\", \"car\");\n    pipe.llen(\"foo\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1L,\n        2L,\n        2L\n    ));\n\n    // Binary\n    pipe.llen(bfoo);\n    pipe.lpush(bfoo, bbar);\n    pipe.lpush(bfoo, bcar);\n    pipe.llen(bfoo);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1L,\n        2L,\n        2L\n    ));\n  }\n\n  @Test\n  public void llenNotOnList() {\n    pipe.set(\"foo\", \"bar\");\n    pipe.llen(\"foo\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(\"OK\"),\n        instanceOf(JedisDataException.class)\n    ));\n\n    // Binary\n    pipe.set(bfoo, bbar);\n    pipe.llen(bfoo);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(\"OK\"),\n        instanceOf(JedisDataException.class)\n    ));\n  }\n\n  @Test\n  public void lrange() {\n    pipe.rpush(\"foo\", \"a\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.rpush(\"foo\", \"c\");\n\n    Response<List<String>> range1 = pipe.lrange(\"foo\", 0, 2);\n    Response<List<String>> range2 = pipe.lrange(\"foo\", 0, 20);\n    Response<List<String>> range3 = pipe.lrange(\"foo\", 1, 2);\n    Response<List<String>> range4 = pipe.lrange(\"foo\", 2, 1);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"a\", \"b\", \"c\"));\n    assertThat(range2.get(), contains(\"a\", \"b\", \"c\"));\n    assertThat(range3.get(), contains(\"b\", \"c\"));\n    assertThat(range4.get(), empty());\n\n    // Binary\n    pipe.rpush(bfoo, bA);\n    pipe.rpush(bfoo, bB);\n    pipe.rpush(bfoo, bC);\n\n    Response<List<byte[]>> brange1 = pipe.lrange(bfoo, 0, 2);\n    Response<List<byte[]>> brange2 = pipe.lrange(bfoo, 0, 20);\n    Response<List<byte[]>> brange3 = pipe.lrange(bfoo, 1, 2);\n    Response<List<byte[]>> brange4 = pipe.lrange(bfoo, 2, 1);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bA, bB, bC));\n    assertThat(brange2.get(), contains(bA, bB, bC));\n    assertThat(brange3.get(), contains(bB, bC));\n    assertThat(brange4.get(), empty());\n  }\n\n  @Test\n  public void ltrim() {\n    pipe.lpush(\"foo\", \"1\");\n    pipe.lpush(\"foo\", \"2\");\n    pipe.lpush(\"foo\", \"3\");\n\n    Response<String> status = pipe.ltrim(\"foo\", 0, 1);\n    Response<Long> len = pipe.llen(\"foo\");\n    Response<List<String>> range = pipe.lrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(status.get(), equalTo(\"OK\"));\n    assertThat(len.get(), equalTo(2L));\n    assertThat(range.get(), contains(\"3\", \"2\"));\n\n    // Binary\n    pipe.lpush(bfoo, b1);\n    pipe.lpush(bfoo, b2);\n    pipe.lpush(bfoo, b3);\n\n    Response<String> bstatus = pipe.ltrim(bfoo, 0, 1);\n    Response<Long> blen = pipe.llen(bfoo);\n    Response<List<byte[]>> brange = pipe.lrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(bstatus.get(), equalTo(\"OK\"));\n    assertThat(blen.get(), equalTo(2L));\n    assertThat(brange.get(), contains(b3, b2));\n  }\n\n  @Test\n  public void lset() {\n    pipe.lpush(\"foo\", \"1\");\n    pipe.lpush(\"foo\", \"2\");\n    pipe.lpush(\"foo\", \"3\");\n\n    Response<String> status = pipe.lset(\"foo\", 1, \"bar\");\n    Response<List<String>> range = pipe.lrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(status.get(), equalTo(\"OK\"));\n    assertThat(range.get(), contains(\"3\", \"bar\", \"1\"));\n\n    // Binary\n    pipe.lpush(bfoo, b1);\n    pipe.lpush(bfoo, b2);\n    pipe.lpush(bfoo, b3);\n\n    Response<String> bstatus = pipe.lset(bfoo, 1, bbar);\n    Response<List<byte[]>> brange = pipe.lrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(bstatus.get(), equalTo(\"OK\"));\n    assertThat(brange.get(), contains(b3, bbar, b1));\n  }\n\n  @Test\n  public void lindex() {\n    pipe.lpush(\"foo\", \"1\");\n    pipe.lpush(\"foo\", \"2\");\n    pipe.lpush(\"foo\", \"3\");\n\n    Response<String> index1 = pipe.lindex(\"foo\", 0);\n    Response<String> index2 = pipe.lindex(\"foo\", 100);\n\n    pipe.sync();\n\n    assertThat(index1.get(), equalTo(\"3\"));\n    assertThat(index2.get(), nullValue());\n\n    // Binary\n    pipe.lpush(bfoo, b1);\n    pipe.lpush(bfoo, b2);\n    pipe.lpush(bfoo, b3);\n\n    Response<byte[]> bindex1 = pipe.lindex(bfoo, 0);\n    Response<byte[]> bindex2 = pipe.lindex(bfoo, 100);\n\n    pipe.sync();\n\n    assertThat(bindex1.get(), equalTo(b3));\n    assertThat(bindex2.get(), nullValue());\n  }\n\n  @Test\n  public void lrem() {\n    pipe.lpush(\"foo\", \"hello\");\n    pipe.lpush(\"foo\", \"hello\");\n    pipe.lpush(\"foo\", \"x\");\n    pipe.lpush(\"foo\", \"hello\");\n    pipe.lpush(\"foo\", \"c\");\n    pipe.lpush(\"foo\", \"b\");\n    pipe.lpush(\"foo\", \"a\");\n\n    Response<Long> result1 = pipe.lrem(\"foo\", -2, \"hello\");\n    Response<List<String>> range = pipe.lrange(\"foo\", 0, 1000);\n    Response<Long> result2 = pipe.lrem(\"bar\", 100, \"foo\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(2L));\n    assertThat(range.get(), contains(\"a\", \"b\", \"c\", \"hello\", \"x\"));\n    assertThat(result2.get(), equalTo(0L));\n\n    // Binary\n    pipe.lpush(bfoo, bhello);\n    pipe.lpush(bfoo, bhello);\n    pipe.lpush(bfoo, bx);\n    pipe.lpush(bfoo, bhello);\n    pipe.lpush(bfoo, bC);\n    pipe.lpush(bfoo, bB);\n    pipe.lpush(bfoo, bA);\n\n    Response<Long> bresult1 = pipe.lrem(bfoo, -2, bhello);\n    Response<List<byte[]>> brange = pipe.lrange(bfoo, 0, 1000);\n    Response<Long> bresult2 = pipe.lrem(bbar, 100, bfoo);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(2L));\n    assertThat(brange.get(), contains(bA, bB, bC, bhello, bx));\n    assertThat(bresult2.get(), equalTo(0L));\n  }\n\n  @Test\n  public void lpop() {\n    Response<String> response1 = pipe.lpop(\"foo\");\n    Response<List<String>> response2 = pipe.lpop(\"foo\", 0);\n\n    pipe.rpush(\"foo\", \"a\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.rpush(\"foo\", \"c\");\n\n    Response<String> response3 = pipe.lpop(\"foo\");\n    Response<List<String>> response4 = pipe.lpop(\"foo\", 10);\n    Response<String> response5 = pipe.lpop(\"foo\");\n    Response<List<String>> response6 = pipe.lpop(\"foo\", 1);\n\n    pipe.sync();\n\n    assertThat(response1.get(), nullValue());\n    assertThat(response2.get(), nullValue());\n    assertThat(response3.get(), equalTo(\"a\"));\n    assertThat(response4.get(), contains(\"b\", \"c\"));\n    assertThat(response5.get(), nullValue());\n    assertThat(response6.get(), nullValue());\n\n    // Binary\n    Response<byte[]> bresponse1 = pipe.lpop(bfoo);\n    Response<List<byte[]>> bresponse2 = pipe.lpop(bfoo, 0);\n\n    pipe.rpush(bfoo, bA);\n    pipe.rpush(bfoo, bB);\n    pipe.rpush(bfoo, bC);\n\n    Response<byte[]> bresponse3 = pipe.lpop(bfoo);\n    Response<List<byte[]>> bresponse4 = pipe.lpop(bfoo, 10);\n    Response<byte[]> bresponse5 = pipe.lpop(bfoo);\n    Response<List<byte[]>> bresponse6 = pipe.lpop(bfoo, 1);\n\n    pipe.sync();\n\n    assertThat(bresponse1.get(), nullValue());\n    assertThat(bresponse2.get(), nullValue());\n    assertThat(bresponse3.get(), equalTo(bA));\n    assertThat(bresponse4.get(), contains(bB, bC));\n    assertThat(bresponse5.get(), nullValue());\n    assertThat(bresponse6.get(), nullValue());\n  }\n\n  @Test\n  public void rpop() {\n    Response<String> response1 = pipe.rpop(\"foo\");\n    Response<List<String>> response2 = pipe.rpop(\"foo\", 0);\n\n    pipe.rpush(\"foo\", \"a\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.rpush(\"foo\", \"c\");\n\n    Response<String> response3 = pipe.rpop(\"foo\");\n    Response<List<String>> response4 = pipe.rpop(\"foo\", 10);\n    Response<String> response5 = pipe.rpop(\"foo\");\n    Response<List<String>> response6 = pipe.rpop(\"foo\", 1);\n\n    pipe.sync();\n\n    assertThat(response1.get(), nullValue());\n    assertThat(response2.get(), nullValue());\n    assertThat(response3.get(), equalTo(\"c\"));\n    assertThat(response4.get(), contains(\"b\", \"a\"));\n    assertThat(response5.get(), nullValue());\n    assertThat(response6.get(), nullValue());\n\n    // Binary\n    Response<byte[]> bresponse1 = pipe.rpop(bfoo);\n    Response<List<byte[]>> bresponse2 = pipe.rpop(bfoo, 0);\n\n    pipe.rpush(bfoo, bA);\n    pipe.rpush(bfoo, bB);\n    pipe.rpush(bfoo, bC);\n\n    Response<byte[]> bresponse3 = pipe.rpop(bfoo);\n    Response<List<byte[]>> bresponse4 = pipe.rpop(bfoo, 10);\n    Response<byte[]> bresponse5 = pipe.rpop(bfoo);\n    Response<List<byte[]>> bresponse6 = pipe.rpop(bfoo, 1);\n\n    pipe.sync();\n\n    assertThat(bresponse1.get(), nullValue());\n    assertThat(bresponse2.get(), nullValue());\n    assertThat(bresponse3.get(), equalTo(bC));\n    assertThat(bresponse4.get(), contains(bB, bA));\n    assertThat(bresponse5.get(), nullValue());\n    assertThat(bresponse6.get(), nullValue());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void rpoplpush() {\n    pipe.rpush(\"foo\", \"a\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.rpush(\"foo\", \"c\");\n\n    pipe.rpush(\"dst\", \"foo\");\n    pipe.rpush(\"dst\", \"bar\");\n\n    Response<String> element = pipe.rpoplpush(\"foo\", \"dst\");\n    Response<List<String>> srcRange = pipe.lrange(\"foo\", 0, 1000);\n    Response<List<String>> dstRange = pipe.lrange(\"dst\", 0, 1000);\n\n    pipe.sync();\n\n    assertThat(element.get(), equalTo(\"c\"));\n    assertThat(srcRange.get(), contains(\"a\", \"b\"));\n    assertThat(dstRange.get(), contains(\"c\", \"foo\", \"bar\"));\n\n    // Binary\n    pipe.rpush(bfoo, bA);\n    pipe.rpush(bfoo, bB);\n    pipe.rpush(bfoo, bC);\n\n    pipe.rpush(bdst, bfoo);\n    pipe.rpush(bdst, bbar);\n\n    Response<byte[]> belement = pipe.rpoplpush(bfoo, bdst);\n    Response<List<byte[]>> bsrcRange = pipe.lrange(bfoo, 0, 1000);\n    Response<List<byte[]>> bdstRange = pipe.lrange(bdst, 0, 1000);\n\n    pipe.sync();\n\n    assertThat(belement.get(), equalTo(bC));\n    assertThat(bsrcRange.get(), contains(bA, bB));\n    assertThat(bdstRange.get(), contains(bC, bfoo, bbar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blpop() throws InterruptedException {\n    Response<List<String>> result1 = pipe.blpop(1, \"foo\");\n\n    pipe.lpush(\"foo\", \"bar\");\n\n    Response<List<String>> result2 = pipe.blpop(1, \"foo\");\n\n    // Multi keys\n    Response<List<String>> result3 = pipe.blpop(1, \"foo\", \"foo1\");\n\n    pipe.lpush(\"foo\", \"bar\");\n    pipe.lpush(\"foo1\", \"bar1\");\n\n    Response<List<String>> result4 = pipe.blpop(1, \"foo1\", \"foo\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), nullValue());\n    assertThat(result2.get(), contains(\"foo\", \"bar\"));\n    assertThat(result3.get(), nullValue());\n    assertThat(result4.get(), contains(\"foo1\", \"bar1\"));\n\n    // Binary\n    pipe.lpush(bfoo, bbar);\n\n    Response<List<byte[]>> bresult1 = pipe.blpop(1, bfoo);\n\n    // Binary Multi keys\n    Response<List<byte[]>> bresult2 = pipe.blpop(1, bfoo, bfoo1);\n\n    pipe.lpush(bfoo, bbar);\n    pipe.lpush(bfoo1, bcar);\n\n    Response<List<byte[]>> bresult3 = pipe.blpop(1, bfoo1, bfoo);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), contains(bfoo, bbar));\n    assertThat(bresult2.get(), nullValue());\n    assertThat(bresult3.get(), contains(bfoo1, bcar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blpopDouble() {\n    Response<KeyValue<String, String>> result1 = pipe.blpop(0.1, \"foo\");\n\n    pipe.lpush(\"foo\", \"bar\");\n\n    Response<KeyValue<String, String>> result2 = pipe.blpop(3.2, \"foo\");\n\n    // Multi keys\n    Response<KeyValue<String, String>> result3 = pipe.blpop(0.18, \"foo\", \"foo1\");\n\n    pipe.lpush(\"foo\", \"bar\");\n    pipe.lpush(\"foo1\", \"bar1\");\n\n    Response<KeyValue<String, String>> result4 = pipe.blpop(1d, \"foo1\", \"foo\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), nullValue());\n    assertThat(result2.get(), equalTo(new KeyValue<>(\"foo\", \"bar\")));\n    assertThat(result3.get(), nullValue());\n    assertThat(result4.get(), equalTo(new KeyValue<>(\"foo1\", \"bar1\")));\n\n    // Binary\n    pipe.lpush(bfoo, bbar);\n\n    Response<KeyValue<byte[], byte[]>> bresult1 = pipe.blpop(3.12, bfoo);\n\n    // Binary Multi keys\n    Response<KeyValue<byte[], byte[]>> bresult2 = pipe.blpop(0.11, bfoo, bfoo1);\n\n    pipe.lpush(bfoo, bbar);\n    pipe.lpush(bfoo1, bcar);\n\n    Response<KeyValue<byte[], byte[]>> bresult3 = pipe.blpop(1d, bfoo1, bfoo);\n\n    pipe.sync();\n\n    assertThat(bresult1.get().getKey(), equalTo(bfoo));\n    assertThat(bresult1.get().getValue(), equalTo(bbar));\n    assertThat(bresult2.get(), nullValue());\n    assertThat(bresult3.get().getKey(), equalTo(bfoo1));\n    assertThat(bresult3.get().getValue(), equalTo(bcar));\n  }\n\n  @Test\n  @Timeout(5)\n  public void blpopDoubleWithSleep() {\n    Response<KeyValue<String, String>> result = pipe.blpop(0.04, \"foo\");\n    pipe.sync();\n\n    assertThat(result.get(), nullValue());\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(30);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      client.lpush(\"foo\", \"bar\");\n    }).start();\n\n    result = pipe.blpop(1.2, \"foo\");\n    pipe.sync();\n\n    assertThat(result.get().getKey(), equalTo(\"foo\"));\n    assertThat(result.get().getValue(), equalTo(\"bar\"));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpop() {\n    Response<List<String>> result1 = pipe.brpop(1, \"foo\");\n\n    pipe.lpush(\"foo\", \"bar\");\n\n    Response<List<String>> result2 = pipe.brpop(1, \"foo\");\n\n    // Multi keys\n    Response<List<String>> result3 = pipe.brpop(1, \"foo\", \"foo1\");\n\n    pipe.lpush(\"foo\", \"bar\");\n    pipe.lpush(\"foo1\", \"bar1\");\n\n    Response<List<String>> result4 = pipe.brpop(1, \"foo1\", \"foo\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), nullValue());\n    assertThat(result2.get(), contains(\"foo\", \"bar\"));\n    assertThat(result3.get(), nullValue());\n    assertThat(result4.get(), contains(\"foo1\", \"bar1\"));\n\n    // Binary\n    pipe.lpush(bfoo, bbar);\n\n    Response<List<byte[]>> bresult1 = pipe.brpop(1, bfoo);\n\n    // Binary Multi keys\n    Response<List<byte[]>> bresult2 = pipe.brpop(1, bfoo, bfoo1);\n\n    pipe.lpush(bfoo, bbar);\n    pipe.lpush(bfoo1, bcar);\n\n    Response<List<byte[]>> bresult3 = pipe.brpop(1, bfoo1, bfoo);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), contains(bfoo, bbar));\n    assertThat(bresult2.get(), nullValue());\n    assertThat(bresult3.get(), contains(bfoo1, bcar));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpopDouble() {\n    Response<KeyValue<String, String>> result1 = pipe.brpop(0.1, \"foo\");\n\n    pipe.lpush(\"foo\", \"bar\");\n\n    Response<KeyValue<String, String>> result2 = pipe.brpop(3.2, \"foo\");\n\n    // Multi keys\n    Response<KeyValue<String, String>> result3 = pipe.brpop(0.18, \"foo\", \"foo1\");\n\n    pipe.lpush(\"foo\", \"bar\");\n    pipe.lpush(\"foo1\", \"bar1\");\n\n    Response<KeyValue<String, String>> result4 = pipe.brpop(1d, \"foo1\", \"foo\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), nullValue());\n    assertThat(result2.get(), equalTo(new KeyValue<>(\"foo\", \"bar\")));\n    assertThat(result3.get(), nullValue());\n    assertThat(result4.get(), equalTo(new KeyValue<>(\"foo1\", \"bar1\")));\n\n    // Binary\n    pipe.lpush(bfoo, bbar);\n\n    Response<KeyValue<byte[], byte[]>> bresult1 = pipe.brpop(3.12, bfoo);\n\n    // Binary Multi keys\n    Response<KeyValue<byte[], byte[]>> bresult2 = pipe.brpop(0.11, bfoo, bfoo1);\n\n    pipe.lpush(bfoo, bbar);\n    pipe.lpush(bfoo1, bcar);\n\n    Response<KeyValue<byte[], byte[]>> bresult3 = pipe.brpop(1d, bfoo1, bfoo);\n\n    pipe.sync();\n\n    assertThat(bresult1.get().getKey(), equalTo(bfoo));\n    assertThat(bresult1.get().getValue(), equalTo(bbar));\n    assertThat(bresult2.get(), nullValue());\n    assertThat(bresult3.get().getKey(), equalTo(bfoo1));\n    assertThat(bresult3.get().getValue(), equalTo(bcar));\n  }\n\n  @Test\n  @Timeout(5)\n  public void brpopDoubleWithSleep() {\n    Response<KeyValue<String, String>> result = pipe.brpop(0.04, \"foo\");\n    pipe.sync();\n\n    assertThat(result.get(), nullValue());\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(30);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      client.lpush(\"foo\", \"bar\");\n    }).start();\n\n    result = pipe.brpop(1.2, \"foo\");\n    pipe.sync();\n\n    assertThat(result.get().getKey(), equalTo(\"foo\"));\n    assertThat(result.get().getValue(), equalTo(\"bar\"));\n  }\n\n  @Test\n  public void lpushx() {\n    pipe.lpushx(\"foo\", \"bar\");\n    pipe.lpush(\"foo\", \"a\");\n    pipe.lpushx(\"foo\", \"b\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1L,\n        2L\n    ));\n\n    // Binary\n    pipe.lpushx(bfoo, bbar);\n    pipe.lpush(bfoo, bA);\n    pipe.lpushx(bfoo, bB);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1L,\n        2L\n    ));\n  }\n\n  @Test\n  public void rpushx() {\n    pipe.rpushx(\"foo\", \"bar\");\n    pipe.lpush(\"foo\", \"a\");\n    pipe.rpushx(\"foo\", \"b\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1L,\n        2L\n    ));\n\n    // Binary\n    pipe.rpushx(bfoo, bbar);\n    pipe.lpush(bfoo, bA);\n    pipe.rpushx(bfoo, bB);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1L,\n        2L\n    ));\n  }\n\n  @Test\n  public void linsert() {\n    Response<Long> result1 = pipe.linsert(\"foo\", ListPosition.BEFORE, \"bar\", \"car\");\n\n    pipe.lpush(\"foo\", \"a\");\n\n    Response<Long> result2 = pipe.linsert(\"foo\", ListPosition.AFTER, \"a\", \"b\");\n\n    Response<List<String>> range = pipe.lrange(\"foo\", 0, 100);\n\n    Response<Long> result3 = pipe.linsert(\"foo\", ListPosition.BEFORE, \"bar\", \"car\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(0L));\n    assertThat(result2.get(), equalTo(2L));\n    assertThat(range.get(), contains(\"a\", \"b\"));\n    assertThat(result3.get(), equalTo(-1L));\n\n    // Binary\n    Response<Long> bresult1 = pipe.linsert(bfoo, ListPosition.BEFORE, bbar, bcar);\n\n    pipe.lpush(bfoo, bA);\n\n    Response<Long> bresult2 = pipe.linsert(bfoo, ListPosition.AFTER, bA, bB);\n\n    Response<List<byte[]>> brange = pipe.lrange(bfoo, 0, 100);\n\n    Response<Long> bresult3 = pipe.linsert(bfoo, ListPosition.BEFORE, bbar, bcar);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(0L));\n    assertThat(bresult2.get(), equalTo(2L));\n    assertThat(brange.get(), contains(bA, bB));\n    assertThat(bresult3.get(), equalTo(-1L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void brpoplpush() {\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      client.lpush(\"foo\", \"a\");\n    }).start();\n\n    Response<String> element = pipe.brpoplpush(\"foo\", \"bar\", 0);\n    Response<Long> len = pipe.llen(\"bar\");\n    Response<List<String>> range = pipe.lrange(\"bar\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(element.get(), equalTo(\"a\"));\n    assertThat(len.get(), equalTo(1L));\n    assertThat(range.get(), contains(\"a\"));\n\n    // Binary\n\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      client.lpush(bfoo, bA);\n    }).start();\n\n    Response<byte[]> belement = pipe.brpoplpush(bfoo, bbar, 0);\n    Response<Long> blen = pipe.llen(bbar);\n    Response<List<byte[]>> brange = pipe.lrange(bbar, 0, -1);\n\n    pipe.sync();\n\n    assertThat(belement.get(), equalTo(bA));\n    assertThat(blen.get(), equalTo(1L));\n    assertThat(brange.get(), contains(bA));\n  }\n\n  @Test\n  public void lpos() {\n    pipe.rpush(\"foo\", \"a\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.rpush(\"foo\", \"c\");\n    pipe.sync();\n\n    pipe.lpos(\"foo\", \"b\");\n    pipe.lpos(\"foo\", \"d\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        nullValue()\n    ));\n\n    pipe.rpush(\"foo\", \"a\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.rpush(\"foo\", \"b\");\n    pipe.sync();\n\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams());\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(3));\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2));\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-5));\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(1).maxlen(2));\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2).maxlen(2));\n    pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2).maxlen(2));\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        equalTo(5L),\n        equalTo(4L),\n        nullValue(),\n        equalTo(1L),\n        nullValue(),\n        equalTo(4L)\n    ));\n\n    Response<List<Long>> posList1 = pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams(), 2);\n    Response<List<Long>> posList2 = pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams(), 0);\n    Response<List<Long>> posList3 = pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2), 0);\n    Response<List<Long>> posList4 = pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(2).maxlen(5), 0);\n    Response<List<Long>> posList5 = pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-2), 0);\n    Response<List<Long>> posList6 = pipe.lpos(\"foo\", \"b\", LPosParams.lPosParams().rank(-1).maxlen(5), 2);\n\n    pipe.sync();\n\n    assertThat(posList1.get(), contains(1L, 4L));\n    assertThat(posList2.get(), contains(1L, 4L, 5L));\n    assertThat(posList3.get(), contains(4L, 5L));\n    assertThat(posList4.get(), contains(4L));\n    assertThat(posList5.get(), contains(4L, 1L));\n    assertThat(posList6.get(), contains(5L, 4L));\n\n    // Binary\n    pipe.rpush(bfoo, bA);\n    pipe.rpush(bfoo, bB);\n    pipe.rpush(bfoo, bC);\n    pipe.sync();\n\n    pipe.lpos(bfoo, bB);\n    pipe.lpos(bfoo, b3);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        nullValue()\n    ));\n\n    pipe.rpush(bfoo, bA);\n    pipe.rpush(bfoo, bB);\n    pipe.rpush(bfoo, bA);\n    pipe.sync();\n\n    pipe.lpos(bfoo, bB, LPosParams.lPosParams().rank(2));\n    pipe.lpos(bfoo, bB, LPosParams.lPosParams().rank(-2).maxlen(5));\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(4L),\n        equalTo(1L)\n    ));\n\n    Response<List<Long>> bposList1 = pipe.lpos(bfoo, bA, LPosParams.lPosParams().maxlen(6), 0);\n    Response<List<Long>> bposList2 = pipe.lpos(bfoo, bA, LPosParams.lPosParams().maxlen(6).rank(2), 1);\n\n    pipe.sync();\n\n    assertThat(bposList1.get(), contains(0L, 3L, 5L));\n    assertThat(bposList2.get(), contains(3L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lmove() {\n    pipe.rpush(\"foo\", \"bar1\", \"bar2\", \"bar3\");\n\n    Response<String> item1 = pipe.lmove(\"foo\", \"bar\", ListDirection.RIGHT, ListDirection.LEFT);\n    Response<List<String>> range1 = pipe.lrange(\"bar\", 0, -1);\n    Response<List<String>> range2 = pipe.lrange(\"foo\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(item1.get(), equalTo(\"bar3\"));\n    assertThat(range1.get(), contains(\"bar3\"));\n    assertThat(range2.get(), contains(\"bar1\", \"bar2\"));\n\n    // Binary\n    pipe.rpush(bfoo, b1, b2, b3);\n\n    Response<byte[]> bitem1 = pipe.lmove(bfoo, bbar, ListDirection.RIGHT, ListDirection.LEFT);\n    Response<List<byte[]>> brange1 = pipe.lrange(bbar, 0, -1);\n    Response<List<byte[]>> brange2 = pipe.lrange(bfoo, 0, -1);\n\n    pipe.sync();\n\n    assertThat(bitem1.get(), equalTo(b3));\n    assertThat(brange1.get(), contains(b3));\n    assertThat(brange2.get(), contains(b1, b2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blmove() {\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      client.rpush(\"foo\", \"bar1\", \"bar2\", \"bar3\");\n    }).start();\n\n    Response<String> response = pipe.blmove(\"foo\", \"bar\", ListDirection.RIGHT, ListDirection.LEFT, 0);\n    Response<List<String>> range1 = pipe.lrange(\"bar\", 0, -1);\n    Response<List<String>> range2 = pipe.lrange(\"foo\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(response.get(), equalTo(\"bar3\"));\n    assertThat(range1.get(), contains(\"bar3\"));\n    assertThat(range2.get(), contains(\"bar1\", \"bar2\"));\n\n    // Binary\n    new Thread(() -> {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        logger.error(\"\", e);\n      }\n      client.rpush(bfoo, b1, b2, b3);\n    }).start();\n\n    Response<byte[]> bresponse = pipe.blmove(bfoo, bbar, ListDirection.RIGHT, ListDirection.LEFT, 0);\n    Response<List<byte[]>> brange1 = pipe.lrange(bbar, 0, -1);\n    Response<List<byte[]>> brange2 = pipe.lrange(bfoo, 0, -1);\n\n    pipe.sync();\n\n    assertThat(bresponse.get(), equalTo(b3));\n    assertThat(brange1.get(), contains(b3));\n    assertThat(brange2.get(), contains(b1, b2));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void lmpop() {\n    String mylist1 = \"mylist1\";\n    String mylist2 = \"mylist2\";\n\n    // add elements to list\n    pipe.lpush(mylist1, \"one1\", \"two1\", \"three1\", \"four1\", \"five1\");\n    pipe.lpush(mylist2, \"one2\", \"two2\", \"three2\", \"four2\", \"five2\");\n\n    Response<KeyValue<String, List<String>>> elements1 = pipe.lmpop(ListDirection.LEFT, mylist1, mylist2);\n    Response<KeyValue<String, List<String>>> elements2 = pipe.lmpop(ListDirection.LEFT, 5, mylist1, mylist2);\n    Response<KeyValue<String, List<String>>> elements3 = pipe.lmpop(ListDirection.RIGHT, 100, mylist1, mylist2);\n    Response<KeyValue<String, List<String>>> elements4 = pipe.lmpop(ListDirection.RIGHT, mylist1, mylist2);\n\n    pipe.sync();\n\n    assertThat(elements1.get().getKey(), equalTo(mylist1));\n    assertThat(elements1.get().getValue(), contains(\"five1\"));\n\n    assertThat(elements2.get().getKey(), equalTo(mylist1));\n    assertThat(elements2.get().getValue(), contains(\"four1\", \"three1\", \"two1\", \"one1\"));\n\n    assertThat(elements3.get().getKey(), equalTo(mylist2));\n    assertThat(elements3.get().getValue(), contains(\"one2\", \"two2\", \"three2\", \"four2\", \"five2\"));\n\n    assertThat(elements4.get(), nullValue());\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void blmpopSimple() {\n    String mylist1 = \"mylist1\";\n    String mylist2 = \"mylist2\";\n\n    // add elements to list\n    pipe.lpush(mylist1, \"one1\", \"two1\", \"three1\", \"four1\", \"five1\");\n    pipe.lpush(mylist2, \"one2\", \"two2\", \"three2\", \"four2\", \"five2\");\n\n    Response<KeyValue<String, List<String>>> elements1 = pipe.blmpop(1L, ListDirection.LEFT, mylist1, mylist2);\n    Response<KeyValue<String, List<String>>> elements2 = pipe.blmpop(1L, ListDirection.LEFT, 5, mylist1, mylist2);\n    Response<KeyValue<String, List<String>>> elements3 = pipe.blmpop(1L, ListDirection.RIGHT, 100, mylist1, mylist2);\n    Response<KeyValue<String, List<String>>> elements4 = pipe.blmpop(1L, ListDirection.RIGHT, mylist1, mylist2);\n\n    pipe.sync();\n\n    assertThat(elements1.get().getKey(), equalTo(mylist1));\n    assertThat(elements1.get().getValue(), contains(\"five1\"));\n\n    assertThat(elements2.get().getKey(), equalTo(mylist1));\n    assertThat(elements2.get().getValue(), contains(\"four1\", \"three1\", \"two1\", \"one1\"));\n\n    assertThat(elements3.get().getKey(), equalTo(mylist2));\n    assertThat(elements3.get().getValue(), contains(\"one2\", \"two2\", \"three2\", \"four2\", \"five2\"));\n\n    assertThat(elements4.get(), nullValue());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/PipelineCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.commands.CommandsTestsParameters;\nimport redis.clients.jedis.commands.unified.client.RedisClientCommandsTestHelper;\n\n@Tag(\"integration\")\npublic abstract class PipelineCommandsTestBase {\n\n  protected RedisClient client;\n  protected Pipeline pipe;\n  /**\n   * Input data for parameterized tests. In principle all subclasses of this class should be\n   * parameterized tests, to run with several versions of RESP.\n   * @see CommandsTestsParameters#respVersions()\n   */\n  protected final RedisProtocol protocol;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      RedisClientCommandsTestHelper::getEndpointConfig);\n  @RegisterExtension\n  public static EnvCondition conditionalOnEnvCondition = new EnvCondition();\n\n  /**\n   * The RESP protocol is to be injected by the subclasses, usually via JUnit\n   * parameterized tests, because most of the subclassed tests are meant to be\n   * executed against multiple RESP versions. For the special cases where a single\n   * RESP version is relevant, we still force the subclass to be explicit and\n   * call this constructor.\n   *\n   * @param protocol The RESP protocol to use during the tests.\n   */\n  public PipelineCommandsTestBase(RedisProtocol protocol) {\n    this.protocol = protocol;\n  }\n\n  @BeforeEach\n  public void setUp() {\n    client = RedisClientCommandsTestHelper.getClient(protocol);\n    RedisClientCommandsTestHelper.clearData();\n    pipe = client.pipelined();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    pipe.close();\n    client.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/SetPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.nullValue;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArrayCollectionContainsAll;\nimport static redis.clients.jedis.util.AssertUtil.assertByteArraySetEquals;\nimport static redis.clients.jedis.util.AssertUtil.assertCollectionContainsAll;\nimport static redis.clients.jedis.util.ByteArrayUtil.byteArrayCollectionRemoveAll;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SetPipelineCommandsTest extends PipelineCommandsTestBase {\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bd = { 0x0D };\n  final byte[] bx = { 0x42 };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public SetPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void sadd() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"a\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        0L\n    ));\n\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, ba);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        0L\n    ));\n  }\n\n  @Test\n  public void smembers() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    Response<Set<String>> members = pipe.smembers(\"foo\");\n\n    pipe.sync();\n\n    assertThat(members.get(), containsInAnyOrder(\"a\", \"b\"));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    Response<Set<byte[]>> bmembers = pipe.smembers(bfoo);\n\n    pipe.sync();\n\n    assertThat(bmembers.get(), containsInAnyOrder(ba, bb));\n  }\n\n  @Test\n  public void srem() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    Response<Long> status1 = pipe.srem(\"foo\", \"a\");\n    Response<Set<String>> members = pipe.smembers(\"foo\");\n    Response<Long> status2 = pipe.srem(\"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(status1.get(), equalTo(1L));\n    assertThat(members.get(), containsInAnyOrder(\"b\"));\n    assertThat(status2.get(), equalTo(0L));\n\n    // Binary\n\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    Response<Long> bstatus1 = pipe.srem(bfoo, ba);\n    Response<Set<byte[]>> bmembers = pipe.smembers(bfoo);\n    Response<Long> bstatus2 = pipe.srem(bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bstatus1.get(), equalTo(1L));\n    assertThat(bmembers.get(), containsInAnyOrder(bb));\n    assertThat(bstatus2.get(), equalTo(0L));\n  }\n\n  @Test\n  public void spop() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    Response<String> member1 = pipe.spop(\"foo\");\n    Response<Set<String>> members = pipe.smembers(\"foo\");\n    Response<String> member2 = pipe.spop(\"bar\");\n\n    pipe.sync();\n\n    assertThat(member1.get(), anyOf(equalTo(\"a\"), equalTo(\"b\")));\n    assertThat(members.get(), hasSize(1));\n    assertThat(member2.get(), nullValue());\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    Response<byte[]> bmember1 = pipe.spop(bfoo);\n    Response<Set<byte[]>> bmembers = pipe.smembers(bfoo);\n    Response<byte[]> bmember2 = pipe.spop(bbar);\n\n    pipe.sync();\n\n    assertThat(bmember1.get(), anyOf(equalTo(ba), equalTo(bb)));\n    assertThat(bmembers.get(), hasSize(1));\n    assertThat(bmember2.get(), nullValue());\n  }\n\n  @Test\n  public void spopWithCount() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n    pipe.sadd(\"foo\", \"c\");\n\n    Response<Set<String>> members1 = pipe.spop(\"foo\", 2);\n    Response<Set<String>> members2 = pipe.spop(\"foo\", 2);\n    Response<Set<String>> members3 = pipe.spop(\"foo\", 2);\n\n    pipe.sync();\n\n    assertThat(members1.get(), hasSize(2));\n    assertThat(members2.get(), hasSize(1));\n    assertThat(members3.get(), empty());\n\n    Set<String> superSet = new HashSet<>();\n    superSet.add(\"c\");\n    superSet.add(\"b\");\n    superSet.add(\"a\");\n\n    assertCollectionContainsAll(superSet, members1.get());\n    superSet.removeAll(members1.get());\n\n    assertThat(members2.get(), equalTo(superSet));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n    pipe.sadd(bfoo, bc);\n\n    Response<Set<byte[]>> bmembers1 = pipe.spop(bfoo, 2);\n    Response<Set<byte[]>> bmembers2 = pipe.spop(bfoo, 2);\n    Response<Set<byte[]>> bmembers3 = pipe.spop(bfoo, 2);\n\n    pipe.sync();\n\n    assertThat(bmembers1.get(), hasSize(2));\n    assertThat(bmembers2.get(), hasSize(1));\n    assertThat(bmembers3.get(), empty());\n\n    Set<byte[]> bsuperSet = new HashSet<>();\n    bsuperSet.add(bc);\n    bsuperSet.add(bb);\n    bsuperSet.add(ba);\n\n    assertByteArrayCollectionContainsAll(bsuperSet, bmembers1.get());\n    byteArrayCollectionRemoveAll(bsuperSet, bmembers1.get());\n\n    assertByteArraySetEquals(bsuperSet, bmembers2.get());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void smove() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    pipe.sadd(\"bar\", \"c\");\n\n    Response<Long> status1 = pipe.smove(\"foo\", \"bar\", \"a\");\n    Response<Set<String>> srcMembers = pipe.smembers(\"foo\");\n    Response<Set<String>> dstMembers = pipe.smembers(\"bar\");\n    Response<Long> status2 = pipe.smove(\"foo\", \"bar\", \"a\");\n\n    pipe.sync();\n\n    assertThat(status1.get(), equalTo(1L));\n    assertThat(srcMembers.get(), containsInAnyOrder(\"b\"));\n    assertThat(dstMembers.get(), containsInAnyOrder(\"a\", \"c\"));\n    assertThat(status2.get(), equalTo(0L));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    pipe.sadd(bbar, bc);\n\n    Response<Long> bstatus1 = pipe.smove(bfoo, bbar, ba);\n    Response<Set<byte[]>> bsrcMembers = pipe.smembers(bfoo);\n    Response<Set<byte[]>> bdstMembers = pipe.smembers(bbar);\n    Response<Long> bstatus2 = pipe.smove(bfoo, bbar, ba);\n\n    pipe.sync();\n\n    assertThat(bstatus1.get(), equalTo(1L));\n    assertThat(bsrcMembers.get(), containsInAnyOrder(bb));\n    assertThat(bdstMembers.get(), containsInAnyOrder(ba, bc));\n    assertThat(bstatus2.get(), equalTo(0L));\n  }\n\n  @Test\n  public void scard() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n    pipe.sync();\n\n    pipe.scard(\"foo\");\n    pipe.scard(\"bar\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        2L,\n        0L\n    ));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n    pipe.sync();\n\n    pipe.scard(bfoo);\n    pipe.scard(bbar);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        2L,\n        0L\n    ));\n  }\n\n  @Test\n  public void sismember() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n    pipe.sync();\n\n    pipe.sismember(\"foo\", \"a\");\n    pipe.sismember(\"foo\", \"c\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        true,\n        false\n    ));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n    pipe.sync();\n\n    pipe.sismember(bfoo, ba);\n    pipe.sismember(bfoo, bc);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        true,\n        false\n    ));\n  }\n\n  @Test\n  public void smismember() {\n    pipe.sadd(\"foo\", \"a\", \"b\");\n\n    Response<List<Boolean>> response = pipe.smismember(\"foo\", \"a\", \"c\");\n\n    pipe.sync();\n\n    assertThat(response.get(), contains(true, false));\n\n    // Binary\n    pipe.sadd(bfoo, ba, bb);\n\n    Response<List<Boolean>> bresponse = pipe.smismember(bfoo, ba, bc);\n\n    pipe.sync();\n\n    assertThat(bresponse.get(), contains(true, false));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sinter() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    pipe.sadd(\"bar\", \"b\");\n    pipe.sadd(\"bar\", \"c\");\n\n    Response<Set<String>> intersection = pipe.sinter(\"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(intersection.get(), containsInAnyOrder(\"b\"));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    pipe.sadd(bbar, bb);\n    pipe.sadd(bbar, bc);\n\n    Response<Set<byte[]>> bintersection = pipe.sinter(bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bintersection.get(), containsInAnyOrder(bb));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sinterstore() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    pipe.sadd(\"bar\", \"b\");\n    pipe.sadd(\"bar\", \"c\");\n\n    Response<Long> status = pipe.sinterstore(\"car\", \"foo\", \"bar\");\n    Response<Set<String>> members = pipe.smembers(\"car\");\n\n    pipe.sync();\n\n    assertThat(status.get(), equalTo(1L));\n    assertThat(members.get(), containsInAnyOrder(\"b\"));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    pipe.sadd(bbar, bb);\n    pipe.sadd(bbar, bc);\n\n    Response<Long> bstatus = pipe.sinterstore(bcar, bfoo, bbar);\n    Response<Set<byte[]>> bmembers = pipe.smembers(bcar);\n\n    pipe.sync();\n\n    assertThat(bstatus.get(), equalTo(1L));\n    assertThat(bmembers.get(), containsInAnyOrder(bb));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sintercard() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    pipe.sadd(\"bar\", \"a\");\n    pipe.sadd(\"bar\", \"b\");\n    pipe.sadd(\"bar\", \"c\");\n\n    Response<Long> card = pipe.sintercard(\"foo\", \"bar\");\n    Response<Long> limitedCard = pipe.sintercard(1, \"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(card.get(), equalTo(2L));\n    assertThat(limitedCard.get(), equalTo(1L));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    pipe.sadd(bbar, ba);\n    pipe.sadd(bbar, bb);\n    pipe.sadd(bbar, bc);\n\n    Response<Long> bcard = pipe.sintercard(bfoo, bbar);\n    Response<Long> blimitedCard = pipe.sintercard(1, bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bcard.get(), equalTo(2L));\n    assertThat(blimitedCard.get(), equalTo(1L));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sunion() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    pipe.sadd(\"bar\", \"b\");\n    pipe.sadd(\"bar\", \"c\");\n\n    Response<Set<String>> union = pipe.sunion(\"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(union.get(), containsInAnyOrder(\"a\", \"b\", \"c\"));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    pipe.sadd(bbar, bb);\n    pipe.sadd(bbar, bc);\n\n    Response<Set<byte[]>> bunion = pipe.sunion(bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bunion.get(), containsInAnyOrder(ba, bb, bc));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sunionstore() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    pipe.sadd(\"bar\", \"b\");\n    pipe.sadd(\"bar\", \"c\");\n\n    Response<Long> status = pipe.sunionstore(\"car\", \"foo\", \"bar\");\n    Response<Set<String>> members = pipe.smembers(\"car\");\n\n    pipe.sync();\n\n    assertThat(status.get(), equalTo(3L));\n    assertThat(members.get(), containsInAnyOrder(\"a\", \"b\", \"c\"));\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    pipe.sadd(bbar, bb);\n    pipe.sadd(bbar, bc);\n\n    Response<Long> bstatus = pipe.sunionstore(bcar, bfoo, bbar);\n    Response<Set<byte[]>> bmembers = pipe.smembers(bcar);\n\n    pipe.sync();\n\n    assertThat(bstatus.get(), equalTo(3L));\n    assertThat(bmembers.get(), containsInAnyOrder(ba, bb, bc));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sdiff() {\n    pipe.sadd(\"foo\", \"x\");\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n    pipe.sadd(\"foo\", \"c\");\n\n    pipe.sadd(\"bar\", \"c\");\n\n    pipe.sadd(\"car\", \"a\");\n    pipe.sadd(\"car\", \"d\");\n\n    Response<Set<String>> diff = pipe.sdiff(\"foo\", \"bar\", \"car\");\n\n    pipe.sync();\n\n    assertThat(diff.get(), containsInAnyOrder(\"b\", \"x\"));\n\n    // Binary\n    pipe.sadd(bfoo, bx);\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n    pipe.sadd(bfoo, bc);\n\n    pipe.sadd(bbar, bc);\n\n    pipe.sadd(bcar, ba);\n    pipe.sadd(bcar, bd);\n\n    Response<Set<byte[]>> bdiff = pipe.sdiff(bfoo, bbar, bcar);\n\n    pipe.sync();\n\n    assertThat(bdiff.get(), containsInAnyOrder(bb, bx));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void sdiffstore() {\n    pipe.sadd(\"foo\", \"x\");\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n    pipe.sadd(\"foo\", \"c\");\n\n    pipe.sadd(\"bar\", \"c\");\n\n    pipe.sadd(\"car\", \"a\");\n    pipe.sadd(\"car\", \"d\");\n\n    Response<Long> status = pipe.sdiffstore(\"tar\", \"foo\", \"bar\", \"car\");\n    Response<Set<String>> members = pipe.smembers(\"tar\");\n\n    pipe.sync();\n\n    assertThat(status.get(), equalTo(2L));\n    assertThat(members.get(), containsInAnyOrder(\"b\", \"x\"));\n\n    // Binary\n    pipe.sadd(bfoo, bx);\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n    pipe.sadd(bfoo, bc);\n\n    pipe.sadd(bbar, bc);\n\n    pipe.sadd(bcar, ba);\n    pipe.sadd(bcar, bd);\n\n    Response<Long> bstatus = pipe.sdiffstore(\"tar\".getBytes(), bfoo, bbar, bcar);\n    Response<Set<byte[]>> bmembers = pipe.smembers(\"tar\".getBytes());\n\n    pipe.sync();\n\n    assertThat(bstatus.get(), equalTo(2L));\n    assertThat(bmembers.get(), containsInAnyOrder(bb, bx));\n  }\n\n  @Test\n  public void srandmember() {\n    pipe.sadd(\"foo\", \"a\");\n    pipe.sadd(\"foo\", \"b\");\n\n    Response<String> member1 = pipe.srandmember(\"foo\");\n    Response<Set<String>> allMembers = pipe.smembers(\"foo\");\n    Response<List<String>> members1 = pipe.srandmember(\"foo\", 2);\n    Response<String> member2 = pipe.srandmember(\"bar\");\n    Response<List<String>> members2 = pipe.srandmember(\"bar\", 2);\n\n    pipe.sync();\n\n    assertThat(member1.get(), anyOf(equalTo(\"a\"), equalTo(\"b\")));\n    assertThat(allMembers.get(), containsInAnyOrder(\"a\", \"b\"));\n    assertThat(members1.get(), containsInAnyOrder(\"a\", \"b\"));\n    assertThat(member2.get(), nullValue());\n    assertThat(members2.get(), empty());\n\n    // Binary\n    pipe.sadd(bfoo, ba);\n    pipe.sadd(bfoo, bb);\n\n    Response<byte[]> bmember1 = pipe.srandmember(bfoo);\n    Response<List<byte[]>> bmembers1 = pipe.srandmember(bfoo, 2);\n    Response<byte[]> bmember2 = pipe.srandmember(bbar);\n    Response<List<String>> bmembers2 = pipe.srandmember(\"bbar\", 2);\n\n    pipe.sync();\n\n    assertThat(bmember1.get(), anyOf(equalTo(ba), equalTo(bb)));\n    assertThat(bmembers1.get(), containsInAnyOrder(ba, bb));\n    assertThat(bmember2.get(), nullValue());\n    assertThat(bmembers2.get(), empty());\n  }\n\n  @Test\n  public void sscan() {\n    pipe.sadd(\"foo\", \"a\", \"b\");\n\n    Response<ScanResult<String>> result = pipe.sscan(\"foo\", SCAN_POINTER_START);\n\n    pipe.sync();\n\n    assertThat(result.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(result.get().getResult(), not(empty()));\n\n    // binary\n    pipe.sadd(bfoo, ba, bb);\n\n    Response<ScanResult<byte[]>> bResult = pipe.sscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    pipe.sync();\n\n    assertThat(bResult.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(bResult.get().getResult(), not(empty()));\n  }\n\n  @Test\n  public void sscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    pipe.sadd(\"foo\", \"b\", \"a\", \"aa\");\n    Response<ScanResult<String>> result = pipe.sscan(\"foo\", SCAN_POINTER_START, params);\n\n    pipe.sync();\n\n    assertThat(result.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(result.get().getResult(), not(empty()));\n\n    // binary\n    pipe.sadd(bfoo, bbar1, bbar2, bbar3);\n\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    Response<ScanResult<byte[]>> bResult = pipe.sscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    pipe.sync();\n\n    assertThat(bResult.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(bResult.get().getResult(), not(empty()));\n  }\n\n  @Test\n  public void sscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    pipe.sadd(\"foo\", \"a1\", \"a2\", \"a3\", \"a4\", \"a5\");\n\n    Response<ScanResult<String>> result = pipe.sscan(\"foo\", SCAN_POINTER_START, params);\n\n    pipe.sync();\n\n    assertThat(result.get().getResult(), not(empty()));\n\n    // binary\n    pipe.sadd(bfoo, bbar1, bbar2, bbar3);\n\n    params = new ScanParams();\n    params.count(2);\n\n    Response<ScanResult<byte[]>> bResult = pipe.sscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    pipe.sync();\n    assertThat(bResult.get().getResult(), not(empty()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/SortedSetPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.in;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START;\nimport static redis.clients.jedis.params.ScanParams.SCAN_POINTER_START_BINARY;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\nimport redis.clients.jedis.params.ZParams;\nimport redis.clients.jedis.params.ZRangeParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.AssertUtil;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SortedSetPipelineCommandsTest extends PipelineCommandsTestBase {\n\n  final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n  final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };\n  final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };\n  final byte[] ba = { 0x0A };\n  final byte[] bb = { 0x0B };\n  final byte[] bc = { 0x0C };\n  final byte[] bInclusiveB = { 0x5B, 0x0B };\n  final byte[] bExclusiveC = { 0x28, 0x0C };\n  final byte[] bLexMinusInf = { 0x2D };\n  final byte[] bLexPlusInf = { 0x2B };\n\n  final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n  final byte[] bbarstar = { 0x05, 0x06, 0x07, 0x08, '*' };\n\n  public SortedSetPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void zadd() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        1L,\n        1L,\n        0L,\n        1L,\n        1L,\n        1L,\n        0L\n    ));\n  }\n\n  @Test\n  public void zaddWithParams() {\n    pipe.del(\"foo\");\n\n    // xx: never add new member\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().xx());\n\n    pipe.zadd(\"foo\", 1d, \"a\");\n\n    // nx: never update current member\n    pipe.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n    pipe.zscore(\"foo\", \"a\");\n\n    Map<String, Double> scoreMembers = new HashMap<String, Double>();\n    scoreMembers.put(\"a\", 2d);\n    scoreMembers.put(\"b\", 1d);\n    // ch: return count of members not only added, but also updated\n    pipe.zadd(\"foo\", scoreMembers, ZAddParams.zAddParams().ch());\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        0L,\n        1L,\n        0L,\n        1d,\n        2L\n    ));\n\n    // lt: only update existing elements if the new score is less than the current score.\n    pipe.zadd(\"foo\", 3d, \"a\", ZAddParams.zAddParams().lt());\n    pipe.zscore(\"foo\", \"a\");\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().lt());\n    pipe.zscore(\"foo\", \"a\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        2d,\n        0L,\n        1d\n    ));\n\n    // gt: only update existing elements if the new score is greater than the current score.\n    pipe.zadd(\"foo\", 0d, \"b\", ZAddParams.zAddParams().gt());\n    pipe.zscore(\"foo\", \"b\");\n    pipe.zadd(\"foo\", 2d, \"b\", ZAddParams.zAddParams().gt());\n    pipe.zscore(\"foo\", \"b\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1d,\n        0L,\n        2d\n    ));\n\n    // incr: don't update already existing elements.\n    pipe.zaddIncr(\"foo\", 1d, \"b\", ZAddParams.zAddParams().nx());\n    pipe.zscore(\"foo\", \"b\");\n    // incr: update elements that already exist.\n    pipe.zaddIncr(\"foo\", 1d, \"b\", ZAddParams.zAddParams().xx());\n    pipe.zscore(\"foo\", \"b\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        nullValue(),\n        equalTo(2d),\n        equalTo(3d),\n        equalTo(3d)\n    ));\n\n    // binary\n    pipe.del(bfoo);\n\n    // xx: never add new member\n    pipe.zadd(bfoo, 1d, ba, ZAddParams.zAddParams().xx());\n\n    pipe.zadd(bfoo, 1d, ba);\n\n    // nx: never update current member\n    pipe.zadd(bfoo, 2d, ba, ZAddParams.zAddParams().nx());\n    pipe.zscore(bfoo, ba);\n\n    Map<byte[], Double> binaryScoreMembers = new HashMap<>();\n    binaryScoreMembers.put(ba, 2d);\n    binaryScoreMembers.put(bb, 1d);\n    // ch: return count of members not only added, but also updated\n    pipe.zadd(bfoo, binaryScoreMembers, ZAddParams.zAddParams().ch());\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        0L,\n        1L,\n        0L,\n        1d,\n        2L\n    ));\n\n    // lt: only update existing elements if the new score is less than the current score.\n    pipe.zadd(bfoo, 3d, ba, ZAddParams.zAddParams().lt());\n    pipe.zscore(bfoo, ba);\n    pipe.zadd(bfoo, 1d, ba, ZAddParams.zAddParams().lt());\n    pipe.zscore(bfoo, ba);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        2d,\n        0L,\n        1d\n    ));\n\n    // gt: only update existing elements if the new score is greater than the current score.\n    pipe.zadd(bfoo, 0d, bb, ZAddParams.zAddParams().gt());\n    pipe.zscore(bfoo, bb);\n    pipe.zadd(bfoo, 2d, bb, ZAddParams.zAddParams().gt());\n    pipe.zscore(bfoo, bb);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        0L,\n        1d,\n        0L,\n        2d\n    ));\n\n    // incr: don't update already existing elements.\n    pipe.zaddIncr(bfoo, 1d, bb, ZAddParams.zAddParams().nx());\n    pipe.zscore(bfoo, bb);\n    // incr: update elements that already exist.\n    pipe.zaddIncr(bfoo, 1d, bb, ZAddParams.zAddParams().xx());\n    pipe.zscore(bfoo, bb);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        nullValue(),\n        equalTo(2d),\n        equalTo(3d),\n        equalTo(3d)\n    ));\n  }\n\n  @Test\n  public void zrange() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<String>> range1 = pipe.zrange(\"foo\", 0, 1);\n    Response<List<String>> range2 = pipe.zrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"c\", \"a\"));\n    assertThat(range2.get(), contains(\"c\", \"a\", \"b\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<byte[]>> brange1 = pipe.zrange(bfoo, 0, 1);\n    Response<List<byte[]>> brange2 = pipe.zrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bc, ba));\n    assertThat(brange2.get(), contains(bc, ba, bb));\n  }\n\n  @Test\n  public void zrangeByLex() {\n    pipe.zadd(\"foo\", 1, \"aa\");\n    pipe.zadd(\"foo\", 1, \"c\");\n    pipe.zadd(\"foo\", 1, \"bb\");\n    pipe.zadd(\"foo\", 1, \"d\");\n\n    // exclusive aa ~ inclusive c\n    Response<List<String>> range1 = pipe.zrangeByLex(\"foo\", \"(aa\", \"[c\");\n\n    // with LIMIT\n    Response<List<String>> range2 = pipe.zrangeByLex(\"foo\", \"-\", \"+\", 1, 2);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"bb\", \"c\"));\n    assertThat(range2.get(), contains(\"bb\", \"c\"));\n  }\n\n  @Test\n  public void zrangeByLexBinary() {\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 1, bc);\n    pipe.zadd(bfoo, 1, bb);\n\n    Response<List<byte[]>> brange1 = pipe.zrangeByLex(bfoo, bInclusiveB, bExclusiveC);\n\n    // with LIMIT\n    Response<List<byte[]>> brange2 = pipe.zrangeByLex(bfoo, bLexMinusInf, bLexPlusInf, 0, 2);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bb));\n    assertThat(brange2.get(), contains(ba, bb));\n  }\n\n  @Test\n  public void zrevrangeByLex() {\n    pipe.zadd(\"foo\", 1, \"aa\");\n    pipe.zadd(\"foo\", 1, \"c\");\n    pipe.zadd(\"foo\", 1, \"bb\");\n    pipe.zadd(\"foo\", 1, \"d\");\n\n    // exclusive aa ~ inclusive c\n    Response<List<String>> range1 = pipe.zrevrangeByLex(\"foo\", \"[c\", \"(aa\");\n\n    // with LIMIT\n    Response<List<String>> range2 = pipe.zrevrangeByLex(\"foo\", \"+\", \"-\", 1, 2);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"c\", \"bb\"));\n    assertThat(range2.get(), contains(\"c\", \"bb\"));\n  }\n\n  @Test\n  public void zrevrangeByLexBinary() {\n    // binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 1, bc);\n    pipe.zadd(bfoo, 1, bb);\n\n    Response<List<byte[]>> brange1 = pipe.zrevrangeByLex(bfoo, bExclusiveC, bInclusiveB);\n\n    // with LIMIT\n    Response<List<byte[]>> brange2 = pipe.zrevrangeByLex(bfoo, bLexPlusInf, bLexMinusInf, 0, 2);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bb));\n    assertThat(brange2.get(), contains(bc, bb));\n  }\n\n  @Test\n  public void zrevrange() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<String>> range1 = pipe.zrevrange(\"foo\", 0, 1);\n    Response<List<String>> range2 = pipe.zrevrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"b\", \"a\"));\n    assertThat(range2.get(), contains(\"b\", \"a\", \"c\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<byte[]>> brange1 = pipe.zrevrange(bfoo, 0, 1);\n    Response<List<byte[]>> brange2 = pipe.zrevrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bb, ba));\n    assertThat(brange2.get(), contains(bb, ba, bc));\n  }\n\n  @Test\n  public void zrangeParams() {\n    pipe.zadd(\"foo\", 1, \"aa\");\n    pipe.zadd(\"foo\", 1, \"c\");\n    pipe.zadd(\"foo\", 1, \"bb\");\n    pipe.zadd(\"foo\", 1, \"d\");\n\n    Response<List<String>> range1 = pipe.zrange(\"foo\", ZRangeParams.zrangeByLexParams(\"[c\", \"(aa\").rev());\n    Response<List<Tuple>> range2 = pipe.zrangeWithScores(\"foo\", ZRangeParams.zrangeByScoreParams(0, 1));\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"c\", \"bb\"));\n    assertThat(range2.get().stream().map(Tuple::getElement).collect(Collectors.toList()), contains(\"aa\", \"bb\", \"c\", \"d\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 1, bc);\n    pipe.zadd(bfoo, 1, bb);\n\n    Response<List<byte[]>> brange1 = pipe.zrange(bfoo, ZRangeParams.zrangeByLexParams(bExclusiveC, bInclusiveB).rev());\n    Response<List<Tuple>> brange2 = pipe.zrangeWithScores(bfoo, ZRangeParams.zrangeByScoreParams(0, 1).limit(0, 3));\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bb));\n    assertThat(brange2.get().stream().map(Tuple::getBinaryElement).collect(Collectors.toList()), contains(ba, bb, bc));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zrangestore() {\n    pipe.zadd(\"foo\", 1, \"aa\");\n    pipe.zadd(\"foo\", 2, \"c\");\n    pipe.zadd(\"foo\", 3, \"bb\");\n\n    Response<Long> stored = pipe.zrangestore(\"bar\", \"foo\", ZRangeParams.zrangeByScoreParams(1, 2));\n    Response<List<String>> range = pipe.zrange(\"bar\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(stored.get(), equalTo(2L));\n    assertThat(range.get(), contains(\"aa\", \"c\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<Long> bstored = pipe.zrangestore(bbar, bfoo, ZRangeParams.zrangeParams(0, 1).rev());\n    Response<List<byte[]>> brange = pipe.zrevrange(bbar, 0, 1);\n\n    pipe.sync();\n\n    assertThat(bstored.get(), equalTo(2L));\n    assertThat(brange.get(), contains(bb, ba));\n  }\n\n  @Test\n  public void zrem() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 2d, \"b\");\n\n    Response<Long> result1 = pipe.zrem(\"foo\", \"a\");\n    Response<List<String>> range = pipe.zrange(\"foo\", 0, 100);\n    Response<Long> result2 = pipe.zrem(\"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(1L));\n    assertThat(range.get(), contains(\"b\"));\n    assertThat(result2.get(), equalTo(0L));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 2d, bb);\n\n    Response<Long> bresult1 = pipe.zrem(bfoo, ba);\n    Response<List<byte[]>> brange = pipe.zrange(bfoo, 0, 100);\n    Response<Long> bresult2 = pipe.zrem(bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(1L));\n    assertThat(brange.get(), contains(bb));\n    assertThat(bresult2.get(), equalTo(0L));\n  }\n\n  @Test\n  public void zincrby() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 2d, \"b\");\n\n    Response<Double> result = pipe.zincrby(\"foo\", 2d, \"a\");\n    Response<List<String>> range = pipe.zrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result.get(), closeTo(3d, 0.001));\n    assertThat(range.get(), contains(\"b\", \"a\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 2d, bb);\n\n    Response<Double> bresult = pipe.zincrby(bfoo, 2d, ba);\n    Response<List<byte[]>> brange = pipe.zrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), closeTo(3d, 0.001));\n    assertThat(brange.get(), contains(bb, ba));\n  }\n\n  @Test\n  public void zincrbyWithParams() {\n    pipe.del(\"foo\");\n\n    // xx: never add new member\n    Response<Double> result1 = pipe.zincrby(\"foo\", 2d, \"a\", ZIncrByParams.zIncrByParams().xx());\n\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    // nx: never update current member\n    Response<Double> result2 = pipe.zincrby(\"foo\", 1d, \"a\", ZIncrByParams.zIncrByParams().nx());\n    Response<Double> result3 = pipe.zscore(\"foo\", \"a\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), nullValue());\n    assertThat(result2.get(), nullValue());\n    assertThat(result3.get(), closeTo(2d, 0.001));\n\n    // Binary\n\n    pipe.del(bfoo);\n\n    // xx: never add new member\n    Response<Double> bresult1 = pipe.zincrby(bfoo, 2d, ba, ZIncrByParams.zIncrByParams().xx());\n\n    pipe.zadd(bfoo, 2d, ba);\n\n    // nx: never update current member\n    Response<Double> bresult2 = pipe.zincrby(bfoo, 1d, ba, ZIncrByParams.zIncrByParams().nx());\n    Response<Double> bresult3 = pipe.zscore(bfoo, ba);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), nullValue());\n    assertThat(bresult2.get(), nullValue());\n    assertThat(bresult3.get(), closeTo(2d, 0.001));\n  }\n\n  @Test\n  public void zrank() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 2d, \"b\");\n\n    pipe.zrank(\"foo\", \"a\");\n    pipe.zrank(\"foo\", \"b\");\n    pipe.zrank(\"car\", \"b\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        equalTo(1L),\n        equalTo(0L),\n        equalTo(1L),\n        nullValue()\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 2d, bb);\n\n    pipe.zrank(bfoo, ba);\n    pipe.zrank(bfoo, bb);\n    pipe.zrank(bcar, bb);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        equalTo(1L),\n        equalTo(0L),\n        equalTo(1L),\n        nullValue()\n    ));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.2.0\", message = \"Starting with Redis version 7.2.0: Added the optional WITHSCORE argument.\")\n  public void zrankWithScore() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 2d, \"b\");\n\n    Response<KeyValue<Long, Double>> keyValue1 = pipe.zrankWithScore(\"foo\", \"a\");\n    Response<KeyValue<Long, Double>> keyValue2 = pipe.zrankWithScore(\"foo\", \"b\");\n    Response<KeyValue<Long, Double>> keyValue3 = pipe.zrankWithScore(\"car\", \"b\");\n\n    pipe.sync();\n\n    assertThat(keyValue1.get(), equalTo(new KeyValue<>(0L, 1d)));\n    assertThat(keyValue2.get(), equalTo(new KeyValue<>(1L, 2d)));\n    assertThat(keyValue3.get(), nullValue());\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 2d, bb);\n\n    Response<KeyValue<Long, Double>> bKeyValue1 = pipe.zrankWithScore(bfoo, ba);\n    Response<KeyValue<Long, Double>> bKeyValue2 = pipe.zrankWithScore(bfoo, bb);\n    Response<KeyValue<Long, Double>> bKeyValue3 = pipe.zrankWithScore(bcar, bb);\n\n    pipe.sync();\n\n    assertThat(bKeyValue1.get(), equalTo(new KeyValue<>(0L, 1d)));\n    assertThat(bKeyValue2.get(), equalTo(new KeyValue<>(1L, 2d)));\n    assertThat(bKeyValue3.get(), nullValue());\n  }\n\n  @Test\n  public void zrevrank() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 2d, \"b\");\n\n    pipe.zrevrank(\"foo\", \"a\");\n    pipe.zrevrank(\"foo\", \"b\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        equalTo(1L),\n        equalTo(1L),\n        equalTo(0L)\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 2d, bb);\n    pipe.zrevrank(bfoo, ba);\n    pipe.zrevrank(bfoo, bb);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(1L),\n        equalTo(1L),\n        equalTo(1L),\n        equalTo(0L)\n    ));\n  }\n\n  @Test\n  public void zrangeWithScores() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<Tuple>> range1 = pipe.zrangeWithScores(\"foo\", 0, 1);\n    Response<List<Tuple>> range2 = pipe.zrangeWithScores(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"a\", 2d)\n    ));\n    assertThat(range2.get(), contains(\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"a\", 2d),\n        new Tuple(\"b\", 10d)\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Tuple>> brange1 = pipe.zrangeWithScores(bfoo, 0, 1);\n    Response<List<Tuple>> brange2 = pipe.zrangeWithScores(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(\n        new Tuple(bc, 0.1d),\n        new Tuple(ba, 2d)\n    ));\n    assertThat(brange2.get(), contains(\n        new Tuple(bc, 0.1d),\n        new Tuple(ba, 2d),\n        new Tuple(bb, 10d)\n    ));\n  }\n\n  @Test\n  public void zrevrangeWithScores() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<Tuple>> range1 = pipe.zrevrangeWithScores(\"foo\", 0, 1);\n    Response<List<Tuple>> range2 = pipe.zrevrangeWithScores(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\n        new Tuple(\"b\", 10d),\n        new Tuple(\"a\", 2d)\n    ));\n    assertThat(range2.get(), contains(\n        new Tuple(\"b\", 10d),\n        new Tuple(\"a\", 2d),\n        new Tuple(\"c\", 0.1d)\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Tuple>> brange1 = pipe.zrevrangeWithScores(bfoo, 0, 1);\n    Response<List<Tuple>> brange2 = pipe.zrevrangeWithScores(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(\n        new Tuple(bb, 10d),\n        new Tuple(ba, 2d)\n    ));\n    assertThat(brange2.get(), contains(\n        new Tuple(bb, 10d),\n        new Tuple(ba, 2d),\n        new Tuple(bc, 0.1d)\n    ));\n  }\n\n  @Test\n  public void zcard() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<Long> result = pipe.zcard(\"foo\");\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(3L));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<Long> bresult = pipe.zcard(bfoo);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(3L));\n  }\n\n  @Test\n  public void zscore() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<Double> result1 = pipe.zscore(\"foo\", \"b\");\n    Response<Double> result2 = pipe.zscore(\"foo\", \"c\");\n    Response<Double> result3 = pipe.zscore(\"foo\", \"s\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), closeTo(10d, 0.001));\n    assertThat(result2.get(), closeTo(0.1d, 0.001));\n    assertThat(result3.get(), nullValue());\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<Double> bresult1 = pipe.zscore(bfoo, bb);\n    Response<Double> bresult2 = pipe.zscore(bfoo, bc);\n    Response<Double> bresult3 = pipe.zscore(bfoo, SafeEncoder.encode(\"s\"));\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), closeTo(10d, 0.001));\n    assertThat(bresult2.get(), closeTo(0.1d, 0.001));\n    assertThat(bresult3.get(), nullValue());\n  }\n\n  @Test\n  public void zmscore() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<Double>> score = pipe.zmscore(\"foo\", \"b\", \"c\", \"s\");\n\n    pipe.sync();\n\n    assertThat(score.get(), contains(10d, 0.1d, null));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Double>> bscore = pipe.zmscore(bfoo, bb, bc, SafeEncoder.encode(\"s\"));\n\n    pipe.sync();\n\n    assertThat(bscore.get(), contains(10d, 0.1d, null));\n  }\n\n  @Test\n  public void zpopmax() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"d\");\n\n    pipe.sync();\n\n    pipe.zpopmax(\"foo\");\n    pipe.zpopmax(\"foo\");\n    pipe.zpopmax(\"foo\");\n    pipe.zpopmax(\"foo\");\n    pipe.zpopmax(\"foo\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(new Tuple(\"b\", 10d)),\n        equalTo(new Tuple(\"d\", 2d)),\n        equalTo(new Tuple(\"a\", 1d)),\n        equalTo(new Tuple(\"c\", 0.1d)),\n        nullValue()\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    pipe.sync();\n\n    pipe.zpopmax(bfoo);\n    pipe.zpopmax(bfoo);\n    pipe.zpopmax(bfoo);\n    pipe.zpopmax(bfoo);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(new Tuple(bb, 10d)),\n        equalTo(new Tuple(ba, 2d)),\n        equalTo(new Tuple(bc, 0.1d)),\n        nullValue()\n    ));\n  }\n\n  @Test\n  public void zpopmaxWithCount() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"d\");\n    pipe.zadd(\"foo\", 0.03, \"e\");\n\n    Response<List<Tuple>> actual1 = pipe.zpopmax(\"foo\", 2);\n    Response<List<Tuple>> actual2 = pipe.zpopmax(\"foo\", 3);\n    Response<List<Tuple>> actual3 = pipe.zpopmax(\"foo\", 1);\n\n    pipe.sync();\n\n    assertThat(actual1.get(), contains(\n        new Tuple(\"b\", 10d),\n        new Tuple(\"d\", 2d)\n    ));\n\n    assertThat(actual2.get(), contains(\n        new Tuple(\"a\", 1d),\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"e\", 0.03d)\n    ));\n\n    assertThat(actual3.get(), empty());\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Tuple>> bactual1 = pipe.zpopmax(bfoo, 1);\n    Response<List<Tuple>> bactual2 = pipe.zpopmax(bfoo, 1);\n    Response<List<Tuple>> bactual3 = pipe.zpopmax(bfoo, 1);\n    Response<List<Tuple>> bactual4 = pipe.zpopmax(bfoo, 1);\n\n    pipe.sync();\n\n    assertThat(bactual1.get(), contains(\n        new Tuple(bb, 10d)\n    ));\n\n    assertThat(bactual2.get(), contains(\n        new Tuple(ba, 2d)\n    ));\n\n    assertThat(bactual3.get(), contains(\n        new Tuple(bc, 0.1d)\n    ));\n\n    assertThat(bactual4.get(), empty());\n  }\n\n  @Test\n  public void zpopmin() {\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    Response<List<Tuple>> range = pipe.zpopmin(\"foo\", 2);\n    Response<Tuple> item = pipe.zpopmin(\"foo\");\n\n    pipe.sync();\n\n    assertThat(range.get(), contains(\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"a\", 1d)\n    ));\n\n    assertThat(item.get(), equalTo(new Tuple(\"b\", 10d)));\n\n    // Binary\n\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Tuple>> brange = pipe.zpopmin(bfoo, 2);\n    Response<Tuple> bitem = pipe.zpopmin(bfoo);\n\n    pipe.sync();\n\n    assertThat(brange.get(), contains(\n        new Tuple(bc, 0.1d),\n        new Tuple(ba, 2d)\n    ));\n\n    assertThat(bitem.get(), equalTo(new Tuple(bb, 10d)));\n  }\n\n  @Test\n  public void zcount() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    pipe.sync();\n\n    pipe.zcount(\"foo\", 0.01d, 2.1d);\n    pipe.zcount(\"foo\", \"(0.01\", \"+inf\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        2L,\n        3L\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    pipe.sync();\n\n    pipe.zcount(bfoo, 0.01d, 2.1d);\n    pipe.zcount(bfoo, SafeEncoder.encode(\"(0.01\"), SafeEncoder.encode(\"+inf\"));\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        2L,\n        3L\n    ));\n  }\n\n  @Test\n  public void zlexcount() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 1, \"b\");\n    pipe.zadd(\"foo\", 1, \"c\");\n    pipe.zadd(\"foo\", 1, \"aa\");\n\n    pipe.sync();\n\n    pipe.zlexcount(\"foo\", \"[aa\", \"(c\");\n    pipe.zlexcount(\"foo\", \"-\", \"+\");\n    pipe.zlexcount(\"foo\", \"-\", \"(c\");\n    pipe.zlexcount(\"foo\", \"[aa\", \"+\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        2L,\n        4L,\n        3L,\n        3L\n    ));\n  }\n\n  @Test\n  public void zlexcountBinary() {\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 1, bc);\n    pipe.zadd(bfoo, 1, bb);\n\n    pipe.sync();\n\n    pipe.zlexcount(bfoo, bInclusiveB, bExclusiveC);\n    pipe.zlexcount(bfoo, bLexMinusInf, bLexPlusInf);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        1L,\n        3L\n    ));\n  }\n\n  @Test\n  public void zrangebyscore() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<String>> range1 = pipe.zrangeByScore(\"foo\", 0d, 2d);\n    Response<List<String>> range2 = pipe.zrangeByScore(\"foo\", 0d, 2d, 0, 1);\n    Response<List<String>> range3 = pipe.zrangeByScore(\"foo\", 0d, 2d, 1, 1);\n    Response<List<String>> range4 = pipe.zrangeByScore(\"foo\", \"-inf\", \"(2\");\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"c\", \"a\"));\n    assertThat(range2.get(), contains(\"c\"));\n    assertThat(range3.get(), contains(\"a\"));\n    assertThat(range4.get(), contains(\"c\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<byte[]>> brange1 = pipe.zrangeByScore(bfoo, 0d, 2d);\n    Response<List<byte[]>> brange2 = pipe.zrangeByScore(bfoo, 0d, 2d, 0, 1);\n    Response<List<byte[]>> brange3 = pipe.zrangeByScore(bfoo, 0d, 2d, 1, 1);\n    Response<List<byte[]>> brange4 = pipe.zrangeByScore(bfoo, SafeEncoder.encode(\"-inf\"), SafeEncoder.encode(\"(2\"));\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(bc, ba));\n    assertThat(brange2.get(), contains(bc));\n    assertThat(brange3.get(), contains(ba));\n    assertThat(brange4.get(), contains(bc));\n  }\n\n  @Test\n  public void zrevrangebyscore() {\n    pipe.zadd(\"foo\", 1.0d, \"a\");\n    pipe.zadd(\"foo\", 2.0d, \"b\");\n    pipe.zadd(\"foo\", 3.0d, \"c\");\n    pipe.zadd(\"foo\", 4.0d, \"d\");\n    pipe.zadd(\"foo\", 5.0d, \"e\");\n\n    Response<List<String>> range1 = pipe.zrevrangeByScore(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    Response<List<String>> range2 = pipe.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    Response<List<String>> range3 = pipe.zrevrangeByScore(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    Response<List<String>> range4 = pipe.zrevrangeByScore(\"foo\", 4d, 2d);\n    Response<List<String>> range5 = pipe.zrevrangeByScore(\"foo\", \"4\", \"2\", 0, 2);\n    Response<List<String>> range6 = pipe.zrevrangeByScore(\"foo\", \"+inf\", \"(4\");\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\"c\"));\n    assertThat(range2.get(), contains(\"c\", \"b\"));\n    assertThat(range3.get(), contains(\"b\"));\n    assertThat(range4.get(), contains(\"d\", \"c\", \"b\"));\n    assertThat(range5.get(), contains(\"d\", \"c\"));\n    assertThat(range6.get(), contains(\"e\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<byte[]>> brange1 = pipe.zrevrangeByScore(bfoo, 2d, 0d);\n    Response<List<byte[]>> brange2 = pipe.zrevrangeByScore(bfoo, 2d, 0d, 0, 1);\n    Response<List<byte[]>> brange3 = pipe.zrevrangeByScore(bfoo, SafeEncoder.encode(\"+inf\"), SafeEncoder.encode(\"(2\"));\n    Response<List<byte[]>> brange4 = pipe.zrevrangeByScore(bfoo, 2d, 0d, 1, 1);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(ba, bc));\n    assertThat(brange2.get(), contains(ba));\n    assertThat(brange3.get(), contains(bb));\n    assertThat(brange4.get(), contains(bc));\n  }\n\n  @Test\n  public void zrangebyscoreWithScores() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<List<Tuple>> range1 = pipe.zrangeByScoreWithScores(\"foo\", 0d, 2d);\n    Response<List<Tuple>> range2 = pipe.zrangeByScoreWithScores(\"foo\", 0d, 2d, 0, 1);\n    Response<List<Tuple>> range3 = pipe.zrangeByScoreWithScores(\"foo\", 0d, 2d, 1, 1);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"a\", 2d)\n    ));\n\n    assertThat(range2.get(), contains(\n        new Tuple(\"c\", 0.1d)\n    ));\n\n    assertThat(range3.get(), contains(\n        new Tuple(\"a\", 2d)\n    ));\n\n    // Binary\n\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Tuple>> brange1 = pipe.zrangeByScoreWithScores(bfoo, 0d, 2d);\n    Response<List<Tuple>> brange2 = pipe.zrangeByScoreWithScores(bfoo, 0d, 2d, 0, 1);\n    Response<List<Tuple>> brange3 = pipe.zrangeByScoreWithScores(bfoo, 0d, 2d, 1, 1);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(\n        new Tuple(bc, 0.1d),\n        new Tuple(ba, 2d)\n    ));\n\n    assertThat(brange2.get(), contains(\n        new Tuple(bc, 0.1d)\n    ));\n\n    assertThat(brange3.get(), contains(\n        new Tuple(ba, 2d)\n    ));\n  }\n\n  @Test\n  public void zrevrangebyscoreWithScores() {\n    pipe.zadd(\"foo\", 1.0d, \"a\");\n    pipe.zadd(\"foo\", 2.0d, \"b\");\n    pipe.zadd(\"foo\", 3.0d, \"c\");\n    pipe.zadd(\"foo\", 4.0d, \"d\");\n    pipe.zadd(\"foo\", 5.0d, \"e\");\n\n    Response<List<Tuple>> range1 = pipe.zrevrangeByScoreWithScores(\"foo\", 3d, Double.NEGATIVE_INFINITY, 0, 1);\n    Response<List<Tuple>> range2 = pipe.zrevrangeByScoreWithScores(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 0, 2);\n    Response<List<Tuple>> range3 = pipe.zrevrangeByScoreWithScores(\"foo\", 3.5d, Double.NEGATIVE_INFINITY, 1, 1);\n    Response<List<Tuple>> range4 = pipe.zrevrangeByScoreWithScores(\"foo\", 4d, 2d);\n\n    pipe.sync();\n\n    assertThat(range1.get(), contains(\n        new Tuple(\"c\", 3.0d)\n    ));\n\n    assertThat(range2.get(), contains(\n        new Tuple(\"c\", 3.0d),\n        new Tuple(\"b\", 2.0d)\n    ));\n\n    assertThat(range3.get(), contains(\n        new Tuple(\"b\", 2.0d)\n    ));\n\n    assertThat(range4.get(), contains(\n        new Tuple(\"d\", 4.0d),\n        new Tuple(\"c\", 3.0d),\n        new Tuple(\"b\", 2.0d)\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<List<Tuple>> brange1 = pipe.zrevrangeByScoreWithScores(bfoo, 2d, 0d);\n    Response<List<Tuple>> brange2 = pipe.zrevrangeByScoreWithScores(bfoo, 2d, 0d, 0, 1);\n    Response<List<Tuple>> brange3 = pipe.zrevrangeByScoreWithScores(bfoo, 2d, 0d, 1, 1);\n\n    pipe.sync();\n\n    assertThat(brange1.get(), contains(\n        new Tuple(ba, 2d),\n        new Tuple(bc, 0.1d)\n    ));\n\n    assertThat(brange2.get(), contains(\n        new Tuple(ba, 2d)\n    ));\n\n    assertThat(brange3.get(), contains(\n        new Tuple(bc, 0.1d)\n    ));\n  }\n\n  @Test\n  public void zremrangeByRank() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<Long> result = pipe.zremrangeByRank(\"foo\", 0, 0);\n    Response<List<String>> items = pipe.zrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(1L));\n    assertThat(items.get(), contains(\"a\", \"b\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<Long> bresult = pipe.zremrangeByRank(bfoo, 0, 0);\n    Response<List<byte[]>> bitems = pipe.zrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(1L));\n    assertThat(bitems.get(), contains(ba, bb));\n  }\n\n  @Test\n  public void zremrangeByScore() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 10d, \"b\");\n    pipe.zadd(\"foo\", 0.1d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"a\");\n\n    Response<Long> result = pipe.zremrangeByScore(\"foo\", 0, 2);\n    Response<List<String>> items = pipe.zrange(\"foo\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(2L));\n    assertThat(items.get(), contains(\"b\"));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bfoo, 0.1d, bc);\n    pipe.zadd(bfoo, 2d, ba);\n\n    Response<Long> bresult = pipe.zremrangeByScore(bfoo, 0, 2);\n    Response<List<byte[]>> bitems = pipe.zrange(bfoo, 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(2L));\n    assertThat(bitems.get(), contains(bb));\n  }\n\n  @Test\n  public void zremrangeByScoreExclusive() {\n    pipe.zadd(\"foo\", 1d, \"a\");\n    pipe.zadd(\"foo\", 0d, \"c\");\n    pipe.zadd(\"foo\", 2d, \"b\");\n\n    Response<Long> result = pipe.zremrangeByScore(\"foo\", \"(0\", \"(2\");\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(1L));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 0d, bc);\n    pipe.zadd(bfoo, 2d, bb);\n\n    Response<Long> bresult = pipe.zremrangeByScore(bfoo, \"(0\".getBytes(), \"(2\".getBytes());\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(1L));\n  }\n\n  @Test\n  public void zremrangeByLex() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 1, \"b\");\n    pipe.zadd(\"foo\", 1, \"c\");\n    pipe.zadd(\"foo\", 1, \"aa\");\n\n    Response<Long> result = pipe.zremrangeByLex(\"foo\", \"[aa\", \"(c\");\n    Response<List<String>> items = pipe.zrangeByLex(\"foo\", \"-\", \"+\");\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(2L));\n    assertThat(items.get(), contains(\"a\", \"c\"));\n  }\n\n  @Test\n  public void zremrangeByLexBinary() {\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 1, bc);\n    pipe.zadd(bfoo, 1, bb);\n\n    Response<Long> bresult = pipe.zremrangeByLex(bfoo, bInclusiveB, bExclusiveC);\n    Response<List<byte[]>> bitems = pipe.zrangeByLex(bfoo, bLexMinusInf, bLexPlusInf);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(1L));\n    assertThat(bitems.get(), contains(ba, bc));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunion() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n    pipe.zadd(\"bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    Response<List<String>> union1 = pipe.zunion(params, \"foo\", \"bar\");\n    Response<List<Tuple>> union2 = pipe.zunionWithScores(params, \"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(union1.get(), contains(\"a\", \"b\"));\n    assertThat(union2.get(), contains(\n        new Tuple(\"a\", new Double(7)),\n        new Tuple(\"b\", new Double(9))\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n    pipe.zadd(bbar, 2, bb);\n\n    Response<List<byte[]>> bunion1 = pipe.zunion(params, bfoo, bbar);\n    Response<List<Tuple>> bunion2 = pipe.zunionWithScores(params, bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bunion1.get(), contains(ba, bb));\n    assertThat(bunion2.get(), contains(\n        new Tuple(ba, new Double(7)),\n        new Tuple(bb, new Double(9))\n    ));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunionstore() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n    pipe.zadd(\"bar\", 2, \"b\");\n\n    Response<Long> result = pipe.zunionstore(\"dst\", \"foo\", \"bar\");\n    Response<List<Tuple>> items = pipe.zrangeWithScores(\"dst\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(2L));\n    assertThat(items.get(), contains(\n        new Tuple(\"a\", new Double(3)),\n        new Tuple(\"b\", new Double(4))\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n    pipe.zadd(bbar, 2, bb);\n\n    Response<Long> bresult = pipe.zunionstore(SafeEncoder.encode(\"dst\"), bfoo, bbar);\n    Response<List<Tuple>> bitems = pipe.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(2L));\n    assertThat(bitems.get(), contains(\n        new Tuple(ba, new Double(3)),\n        new Tuple(bb, new Double(4))\n    ));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zunionstoreParams() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n    pipe.zadd(\"bar\", 2, \"b\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n\n    Response<Long> result = pipe.zunionstore(\"dst\", params, \"foo\", \"bar\");\n    Response<List<Tuple>> items = pipe.zrangeWithScores(\"dst\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result.get(), equalTo(2L));\n    assertThat(items.get(), contains(\n        new Tuple(\"a\", new Double(7)),\n        new Tuple(\"b\", new Double(9))\n    ));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n    pipe.zadd(bbar, 2, bb);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n\n    Response<Long> bresult = pipe.zunionstore(SafeEncoder.encode(\"dst\"), bparams, bfoo, bbar);\n    Response<List<Tuple>> bitems = pipe.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult.get(), equalTo(2L));\n    assertThat(bitems.get(), contains(\n        new Tuple(ba, new Double(7)),\n        new Tuple(bb, new Double(9))\n    ));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zinter() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n    Response<List<String>> inter1 = pipe.zinter(params, \"foo\", \"bar\");\n\n    Response<List<Tuple>> inter2 = pipe.zinterWithScores(params, \"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(inter1.get(), contains(\"a\"));\n    assertThat(inter2.get(), contains(new Tuple(\"a\", new Double(7))));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n    Response<List<byte[]>> binter1 = pipe.zinter(params, bfoo, bbar);\n\n    Response<List<Tuple>> binter2 = pipe.zinterWithScores(bparams, bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(binter1.get(), contains(ba));\n    assertThat(binter2.get(), contains(new Tuple(ba, new Double(7))));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zinterstore() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n\n    Response<Long> result1 = pipe.zinterstore(\"dst\", \"foo\", \"bar\");\n    Response<List<Tuple>> items1 = pipe.zrangeWithScores(\"dst\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(1L));\n    assertThat(items1.get(), contains(new Tuple(\"a\", new Double(3))));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n\n    Response<Long> bresult1 = pipe.zinterstore(SafeEncoder.encode(\"dst\"), bfoo, bbar);\n    Response<List<Tuple>> bitems1 = pipe.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(1L));\n    assertThat(bitems1.get(), contains(new Tuple(ba, new Double(3))));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zintertoreParams() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n\n    ZParams params = new ZParams();\n    params.weights(2, 2.5);\n    params.aggregate(ZParams.Aggregate.SUM);\n    Response<Long> result1 = pipe.zinterstore(\"dst\", params, \"foo\", \"bar\");\n\n    Response<List<Tuple>> items1 = pipe.zrangeWithScores(\"dst\", 0, 100);\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(1L));\n    assertThat(items1.get(), contains(new Tuple(\"a\", new Double(7))));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n\n    ZParams bparams = new ZParams();\n    bparams.weights(2, 2.5);\n    bparams.aggregate(ZParams.Aggregate.SUM);\n    Response<Long> bresult1 = pipe.zinterstore(SafeEncoder.encode(\"dst\"), bparams, bfoo, bbar);\n\n    Response<List<Tuple>> bitems1 = pipe.zrangeWithScores(SafeEncoder.encode(\"dst\"), 0, 100);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(1L));\n    assertThat(bitems1.get(), contains(new Tuple(ba, new Double(7))));\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zintercard() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"bar\", 2, \"a\");\n    pipe.zadd(\"bar\", 1, \"b\");\n\n    Response<Long> result1 = pipe.zintercard(\"foo\", \"bar\");\n    Response<Long> result2 = pipe.zintercard(1, \"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(2L));\n    assertThat(result2.get(), equalTo(1L));\n\n    // Binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 2, bb);\n    pipe.zadd(bbar, 2, ba);\n    pipe.zadd(bbar, 2, bb);\n\n    Response<Long> bresult1 = pipe.zintercard(bfoo, bbar);\n    Response<Long> bresult2 = pipe.zintercard(1, bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(2L));\n    assertThat(bresult2.get(), equalTo(1L));\n  }\n\n  @Test\n  public void zscan() {\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 2, \"b\");\n\n    Response<ScanResult<Tuple>> result = pipe.zscan(\"foo\", SCAN_POINTER_START);\n\n    pipe.sync();\n\n    assertThat(result.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(result.get().getResult().stream().map(Tuple::getElement).collect(Collectors.toList()),\n        containsInAnyOrder(\"a\", \"b\"));\n\n    // binary\n    pipe.zadd(bfoo, 1, ba);\n    pipe.zadd(bfoo, 1, bb);\n\n    Response<ScanResult<Tuple>> bResult = pipe.zscan(bfoo, SCAN_POINTER_START_BINARY);\n\n    pipe.sync();\n\n    assertThat(bResult.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(bResult.get().getResult().stream().map(Tuple::getBinaryElement).collect(Collectors.toList()),\n        containsInAnyOrder(ba, bb));\n  }\n\n  @Test\n  public void zscanMatch() {\n    ScanParams params = new ScanParams();\n    params.match(\"a*\");\n\n    pipe.zadd(\"foo\", 2, \"b\");\n    pipe.zadd(\"foo\", 1, \"a\");\n    pipe.zadd(\"foo\", 11, \"aa\");\n    Response<ScanResult<Tuple>> result = pipe.zscan(\"foo\", SCAN_POINTER_START, params);\n\n    pipe.sync();\n\n    assertThat(result.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(result.get().getResult().stream().map(Tuple::getElement).collect(Collectors.toList()),\n        containsInAnyOrder(\"a\", \"aa\"));\n\n    // binary\n    params = new ScanParams();\n    params.match(bbarstar);\n\n    pipe.zadd(bfoo, 2, bbar1);\n    pipe.zadd(bfoo, 1, bbar2);\n    pipe.zadd(bfoo, 11, bbar3);\n    Response<ScanResult<Tuple>> bResult = pipe.zscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    pipe.sync();\n\n    assertThat(bResult.get().getCursor(), equalTo(SCAN_POINTER_START));\n    assertThat(bResult.get().getResult().stream().map(Tuple::getBinaryElement).collect(Collectors.toList()),\n        containsInAnyOrder(bbar1, bbar2, bbar3));\n  }\n\n  @Test\n  public void zscanCount() {\n    ScanParams params = new ScanParams();\n    params.count(2);\n\n    pipe.zadd(\"foo\", 1, \"a1\");\n    pipe.zadd(\"foo\", 2, \"a2\");\n    pipe.zadd(\"foo\", 3, \"a3\");\n    pipe.zadd(\"foo\", 4, \"a4\");\n    pipe.zadd(\"foo\", 5, \"a5\");\n\n    Response<ScanResult<Tuple>> result = pipe.zscan(\"foo\", SCAN_POINTER_START, params);\n\n    pipe.sync();\n\n    assertThat(result.get().getResult(), not(empty()));\n\n    // binary\n    params = new ScanParams();\n    params.count(2);\n\n    pipe.zadd(bfoo, 2, bbar1);\n    pipe.zadd(bfoo, 1, bbar2);\n    pipe.zadd(bfoo, 11, bbar3);\n\n    Response<ScanResult<Tuple>> bResult = pipe.zscan(bfoo, SCAN_POINTER_START_BINARY, params);\n\n    pipe.sync();\n\n    assertThat(bResult.get().getResult(), not(empty()));\n  }\n\n  @Test\n  public void infinity() {\n    pipe.zadd(\"key\", Double.POSITIVE_INFINITY, \"pos\");\n\n    Response<Double> score1 = pipe.zscore(\"key\", \"pos\");\n\n    pipe.zadd(\"key\", Double.NEGATIVE_INFINITY, \"neg\");\n\n    Response<Double> score2 = pipe.zscore(\"key\", \"neg\");\n\n    pipe.zadd(\"key\", 0d, \"zero\");\n\n    Response<List<Tuple>> set = pipe.zrangeWithScores(\"key\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(score1.get(), equalTo(Double.POSITIVE_INFINITY));\n    assertThat(score2.get(), equalTo(Double.NEGATIVE_INFINITY));\n    assertThat(set.get().stream().map(Tuple::getScore).collect(Collectors.toList()),\n        contains(Double.NEGATIVE_INFINITY, 0d, Double.POSITIVE_INFINITY));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bzpopmax() {\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"bar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n\n    Response<KeyValue<String, Tuple>> item1 = pipe.bzpopmax(0, \"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(item1.get().getKey(), equalTo(\"foo\"));\n    assertThat(item1.get().getValue(), equalTo(new Tuple(\"b\", 10d)));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bbar, 0.1d, bc);\n\n    Response<KeyValue<byte[], Tuple>> bitem1 = pipe.bzpopmax(0, bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bitem1.get().getKey(), equalTo(bfoo));\n    assertThat(bitem1.get().getValue(), equalTo(new Tuple(bb, 10d)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void bzpopmin() {\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"bar\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n\n    Response<KeyValue<String, Tuple>> item1 = pipe.bzpopmin(0, \"bar\", \"foo\");\n\n    pipe.sync();\n\n    assertThat(item1.get(), equalTo(new KeyValue<>(\"bar\", new Tuple(\"c\", 0.1))));\n\n    // Binary\n    pipe.zadd(bfoo, 1d, ba);\n    pipe.zadd(bfoo, 10d, bb);\n    pipe.zadd(bbar, 0.1d, bc);\n\n    Response<KeyValue<byte[], Tuple>> bitem1 = pipe.bzpopmin(0, bbar, bfoo);\n\n    pipe.sync();\n\n    assertThat(bitem1.get().getKey(), equalTo(bbar));\n    assertThat(bitem1.get().getValue(), equalTo(new Tuple(bc, 0.1)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zdiff() {\n    pipe.zadd(\"foo\", 1.0, \"a\");\n    pipe.zadd(\"foo\", 2.0, \"b\");\n    pipe.zadd(\"bar\", 1.0, \"a\");\n\n    Response<List<String>> diff1 = pipe.zdiff(\"bar1\", \"bar2\");\n    Response<List<String>> diff2 = pipe.zdiff(\"foo\", \"bar\");\n    Response<List<Tuple>> diff3 = pipe.zdiffWithScores(\"foo\", \"bar\");\n\n    pipe.sync();\n\n    assertThat(diff1.get(), empty());\n    assertThat(diff2.get(), contains(\"b\"));\n    assertThat(diff3.get(), contains(new Tuple(\"b\", 2.0d)));\n\n    // binary\n    pipe.zadd(bfoo, 1.0, ba);\n    pipe.zadd(bfoo, 2.0, bb);\n    pipe.zadd(bbar, 1.0, ba);\n\n    Response<List<byte[]>> bdiff1 = pipe.zdiff(bbar1, bbar2);\n    Response<List<byte[]>> bdiff2 = pipe.zdiff(bfoo, bbar);\n    Response<List<Tuple>> bdiff3 = pipe.zdiffWithScores(bfoo, bbar);\n\n    pipe.sync();\n\n    assertThat(bdiff1.get(), empty());\n    assertThat(bdiff2.get(), contains(bb));\n    assertThat(bdiff3.get(), contains(new Tuple(bb, 2.0d)));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void zdiffstore() {\n    pipe.zadd(\"foo\", 1.0, \"a\");\n    pipe.zadd(\"foo\", 2.0, \"b\");\n    pipe.zadd(\"bar\", 1.0, \"a\");\n\n    Response<Long> result1 = pipe.zdiffstore(\"bar3\", \"bar1\", \"bar2\");\n    Response<Long> result2 = pipe.zdiffstore(\"bar3\", \"foo\", \"bar\");\n    Response<List<String>> items = pipe.zrange(\"bar3\", 0, -1);\n\n    pipe.sync();\n\n    assertThat(result1.get(), equalTo(0L));\n    assertThat(result2.get(), equalTo(1L));\n    assertThat(items.get(), contains(\"b\"));\n\n    // binary\n    pipe.zadd(bfoo, 1.0, ba);\n    pipe.zadd(bfoo, 2.0, bb);\n    pipe.zadd(bbar, 1.0, ba);\n\n    Response<Long> bresult1 = pipe.zdiffstore(bbar3, bbar1, bbar2);\n    Response<Long> bresult2 = pipe.zdiffstore(bbar3, bfoo, bbar);\n    Response<List<byte[]>> bitems = pipe.zrange(bbar3, 0, -1);\n\n    pipe.sync();\n\n    assertThat(bresult1.get(), equalTo(0L));\n    assertThat(bresult2.get(), equalTo(1L));\n    assertThat(bitems.get(), contains(bb));\n  }\n\n  @Test\n  public void zrandmember() {\n    Response<String> item1 = pipe.zrandmember(\"foo\");\n    Response<List<String>> items1 = pipe.zrandmember(\"foo\", 1);\n    Response<List<Tuple>> items2 = pipe.zrandmemberWithScores(\"foo\", 1);\n\n    pipe.sync();\n\n    assertThat(item1.get(), nullValue());\n    assertThat(items1.get(), empty());\n    assertThat(items2.get(), empty());\n\n    Map<String, Double> hash = new HashMap<>();\n    hash.put(\"bar1\", 1d);\n    hash.put(\"bar2\", 10d);\n    hash.put(\"bar3\", 0.1d);\n    pipe.zadd(\"foo\", hash);\n\n    Response<String> item2 = pipe.zrandmember(\"foo\");\n    Response<List<String>> items3 = pipe.zrandmember(\"foo\", 2);\n    Response<List<Tuple>> items4 = pipe.zrandmemberWithScores(\"foo\", 2);\n\n    pipe.sync();\n\n    assertThat(item2.get(), in(hash.keySet()));\n    assertThat(items3.get(), hasSize(2));\n    assertThat(items4.get(), hasSize(2));\n    items4.get().forEach(t -> assertEquals(hash.get(t.getElement()), t.getScore(), 0d));\n\n    // Binary\n    Response<byte[]> bitem1 = pipe.zrandmember(bfoo);\n    Response<List<byte[]>> bitems1 = pipe.zrandmember(bfoo, 1);\n    Response<List<Tuple>> bitems2 = pipe.zrandmemberWithScores(bfoo, 1);\n\n    pipe.sync();\n\n    assertThat(bitem1.get(), nullValue());\n    assertThat(bitems1.get(), empty());\n    assertThat(bitems2.get(), empty());\n\n    Map<byte[], Double> bhash = new HashMap<>();\n    bhash.put(bbar1, 1d);\n    bhash.put(bbar2, 10d);\n    bhash.put(bbar3, 0.1d);\n    pipe.zadd(bfoo, bhash);\n\n    Response<byte[]> bitem2 = pipe.zrandmember(bfoo);\n    Response<List<byte[]>> bitems3 = pipe.zrandmember(bfoo, 2);\n    Response<List<Tuple>> bitems4 = pipe.zrandmemberWithScores(bfoo, 2);\n\n    pipe.sync();\n\n    AssertUtil.assertByteArrayCollectionContains(bhash.keySet(), bitem2.get());\n    assertThat(bitems3.get(), hasSize(2));\n    assertThat(bitems4.get(), hasSize(2));\n    bitems4.get().forEach(t -> assertEquals(getScoreFromByteMap(bhash, t.getBinaryElement()), t.getScore(), 0d));\n  }\n\n  private Double getScoreFromByteMap(Map<byte[], Double> bhash, byte[] key) {\n    for (Map.Entry<byte[], Double> en : bhash.entrySet()) {\n      if (Arrays.equals(en.getKey(), key)) {\n        return en.getValue();\n      }\n    }\n    return null;\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void zmpop() {\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    Response<KeyValue<String, List<Tuple>>> single = pipe.zmpop(SortedSetOption.MAX, \"foo\");\n    Response<KeyValue<String, List<Tuple>>> range = pipe.zmpop(SortedSetOption.MIN, 2, \"foo\");\n    Response<KeyValue<String, List<Tuple>>> nullRange = pipe.zmpop(SortedSetOption.MAX, \"foo\");\n\n    pipe.sync();\n\n    assertThat(single.get().getValue(), contains(new Tuple(\"b\", 10d)));\n\n    assertThat(range.get().getValue(), contains(\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"a\", 1d)\n    ));\n\n    assertThat(nullRange.get(), nullValue());\n  }\n\n  @Test\n  @SinceRedisVersion(value=\"7.0.0\")\n  public void bzmpopSimple() {\n    pipe.zadd(\"foo\", 1d, \"a\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 10d, \"b\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 0.1d, \"c\", ZAddParams.zAddParams().nx());\n    pipe.zadd(\"foo\", 2d, \"a\", ZAddParams.zAddParams().nx());\n\n    Response<KeyValue<String, List<Tuple>>> single = pipe.bzmpop(1L, SortedSetOption.MAX, \"foo\");\n    Response<KeyValue<String, List<Tuple>>> range = pipe.bzmpop(1L, SortedSetOption.MIN, 2, \"foo\");\n    Response<KeyValue<String, List<Tuple>>> nullRange = pipe.bzmpop(1L, SortedSetOption.MAX, \"foo\");\n\n    pipe.sync();\n\n    assertThat(single.get().getValue(), contains(new Tuple(\"b\", 10d)));\n\n    assertThat(range.get().getValue(), contains(\n        new Tuple(\"c\", 0.1d),\n        new Tuple(\"a\", 1d)\n    ));\n\n    assertThat(nullRange.get(), nullValue());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/pipeline/StreamsPipelineCommandsTest.java",
    "content": "package redis.clients.jedis.commands.unified.pipeline;\n\nimport static java.util.Collections.singletonMap;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.both;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.hasToString;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.EnabledOnCommand;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.utils.RedisVersion;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport org.hamcrest.Matchers;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.Pipeline;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XAutoClaimParams;\nimport redis.clients.jedis.params.XCfgSetParams;\nimport redis.clients.jedis.params.XClaimParams;\nimport redis.clients.jedis.params.XPendingParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.params.XTrimParams;\nimport redis.clients.jedis.resps.StreamConsumerFullInfo;\nimport redis.clients.jedis.resps.StreamConsumerInfo;\nimport redis.clients.jedis.resps.StreamConsumersInfo;\nimport redis.clients.jedis.resps.StreamEntry;\nimport redis.clients.jedis.resps.StreamFullInfo;\nimport redis.clients.jedis.resps.StreamGroupFullInfo;\nimport redis.clients.jedis.resps.StreamGroupInfo;\nimport redis.clients.jedis.resps.StreamInfo;\nimport redis.clients.jedis.resps.StreamPendingEntry;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class StreamsPipelineCommandsTest extends PipelineCommandsTestBase {\n\n  public StreamsPipelineCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void xaddWrongNumberOfArguments() {\n    Map<String, String> map1 = new HashMap<>();\n    pipe.xadd(\"stream1\", (StreamEntryID) null, map1);\n\n    assertThat(pipe.syncAndReturnAll(),\n        contains(\n            both(instanceOf(JedisDataException.class)).and(hasToString(containsString(\"wrong number of arguments\")))\n        ));\n  }\n\n  @Test\n  public void xadd() {\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n    pipe.xadd(\"xadd-stream1\", (StreamEntryID) null, map1);\n\n    Map<String, String> map2 = new HashMap<>();\n    map2.put(\"f1\", \"v1\");\n    map2.put(\"f2\", \"v2\");\n    pipe.xadd(\"xadd-stream1\", (StreamEntryID) null, map2);\n\n    List<?> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class)\n    ));\n\n    assertThat((StreamEntryID) results.get(1),\n        greaterThan((StreamEntryID) results.get(0)));\n  }\n\n  @Test\n  public void xaddMaxLen() {\n    Map<String, String> map4 = new HashMap<>();\n    map4.put(\"f2\", \"v2\");\n    map4.put(\"f3\", \"v3\");\n    StreamEntryID idIn = new StreamEntryID(1000, 1L);\n    pipe.xadd(\"xadd-stream2\", idIn, map4);\n\n    Map<String, String> map5 = new HashMap<>();\n    map5.put(\"f4\", \"v4\");\n    map5.put(\"f5\", \"v5\");\n    pipe.xadd(\"xadd-stream2\", (StreamEntryID) null, map5);\n\n    pipe.xlen(\"xadd-stream2\");\n\n    Map<String, String> map6 = new HashMap<>();\n    map6.put(\"f4\", \"v4\");\n    map6.put(\"f5\", \"v5\");\n    pipe.xadd(\"xadd-stream2\", map6, XAddParams.xAddParams().maxLen(2));\n\n    pipe.xlen(\"xadd-stream2\");\n\n    List<?> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        equalTo(idIn),\n        instanceOf(StreamEntryID.class),\n        equalTo(2L),\n        instanceOf(StreamEntryID.class),\n        equalTo(2L)\n    ));\n\n    assertThat((StreamEntryID) results.get(1),\n        greaterThan((StreamEntryID) results.get(0)));\n\n    assertThat((StreamEntryID) results.get(3),\n        greaterThan((StreamEntryID) results.get(1)));\n  }\n\n  @Test\n  public void xaddWithParamsWrongNumberOfArguments() {\n    pipe.xadd(\"stream1\", new HashMap<>(), XAddParams.xAddParams());\n    pipe.xadd(\"stream1\", XAddParams.xAddParams(), new HashMap<>());\n\n    assertThat(pipe.syncAndReturnAll(),\n        contains(\n            both(instanceOf(JedisDataException.class)).and(hasToString(containsString(\"wrong number of arguments\"))),\n            both(instanceOf(JedisDataException.class)).and(hasToString(containsString(\"wrong number of arguments\")))\n        ));\n  }\n\n  @Test\n  public void xaddWithParams() {\n    pipe.xadd(\"xadd-stream1\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n\n    Map<String, String> map2 = new HashMap<>();\n    map2.put(\"f1\", \"v1\");\n    map2.put(\"f2\", \"v2\");\n    pipe.xadd(\"xadd-stream1\", map2, XAddParams.xAddParams());\n\n    List<?> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class)\n    ));\n\n    assertThat((StreamEntryID) results.get(1),\n        greaterThan((StreamEntryID) results.get(0)));\n  }\n\n  @Test\n  public void xaddWithParamsTrim() {\n    Map<String, String> map3 = new HashMap<>();\n    map3.put(\"f2\", \"v2\");\n    map3.put(\"f3\", \"v3\");\n    StreamEntryID idIn = new StreamEntryID(1000, 1L);\n    pipe.xadd(\"xadd-stream2\", XAddParams.xAddParams().id(idIn), map3);\n\n    Map<String, String> map4 = new HashMap<>();\n    map4.put(\"f2\", \"v2\");\n    map4.put(\"f3\", \"v3\");\n    StreamEntryID idIn2 = new StreamEntryID(2000, 1L);\n    pipe.xadd(\"xadd-stream2\", map4, XAddParams.xAddParams().id(idIn2));\n\n    Map<String, String> map5 = new HashMap<>();\n    map5.put(\"f4\", \"v4\");\n    map5.put(\"f5\", \"v5\");\n    pipe.xadd(\"xadd-stream2\", XAddParams.xAddParams(), map5);\n\n    pipe.xlen(\"xadd-stream2\");\n\n    Map<String, String> map6 = new HashMap<>();\n    map6.put(\"f4\", \"v4\");\n    map6.put(\"f5\", \"v5\");\n    pipe.xadd(\"xadd-stream2\", map6, XAddParams.xAddParams().maxLen(3).exactTrimming());\n\n    pipe.xlen(\"xadd-stream2\");\n\n    List<?> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        equalTo(idIn),\n        equalTo(idIn2),\n        instanceOf(StreamEntryID.class),\n        equalTo(3L),\n        instanceOf(StreamEntryID.class),\n        equalTo(3L)\n    ));\n\n    assertThat((StreamEntryID) results.get(2),\n        greaterThan((StreamEntryID) results.get(1)));\n\n    assertThat((StreamEntryID) results.get(4),\n        greaterThan((StreamEntryID) results.get(2)));\n  }\n\n  @Test\n  public void xaddWithParamsNoMkStream() {\n    pipe.xadd(\"xadd-stream3\", XAddParams.xAddParams().noMkStream().maxLen(3).exactTrimming(), singletonMap(\"f1\", \"v1\"));\n\n    assertThat(pipe.syncAndReturnAll(),\n        contains(\n            nullValue()\n        ));\n\n    assertFalse(client.exists(\"xadd-stream3\"));\n  }\n\n  @Test\n  public void xaddWithParamsMinId() {\n    Map<String, String> map6 = new HashMap<>();\n    map6.put(\"f4\", \"v4\");\n    map6.put(\"f5\", \"v5\");\n\n    StreamEntryID id = new StreamEntryID(2);\n    pipe.xadd(\"xadd-stream3\", map6, XAddParams.xAddParams().minId(\"2\").id(id));\n\n    pipe.xlen(\"xadd-stream3\");\n\n    StreamEntryID id1 = new StreamEntryID(3);\n    pipe.xadd(\"xadd-stream3\", XAddParams.xAddParams().minId(\"4\").id(id1), map6);\n\n    pipe.xlen(\"xadd-stream3\");\n\n    List<?> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        equalTo(id),\n        equalTo(1L),\n        equalTo(id1),\n        equalTo(0L)\n    ));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.0.0\", message = \"Added support for XADD ID auto sequence is introduced in 7.0.0\")\n  public void xaddParamsId() {\n    String key = \"kk\";\n    Map<String, String> map = singletonMap(\"ff\", \"vv\");\n\n    pipe.xadd(key, XAddParams.xAddParams().id(new StreamEntryID(0, 1)), map);\n    pipe.xadd(key, XAddParams.xAddParams().id(2, 3), map);\n    pipe.xadd(key, XAddParams.xAddParams().id(4), map);\n    pipe.xadd(key, XAddParams.xAddParams().id(\"5-6\"), map);\n    pipe.xadd(key, XAddParams.xAddParams().id(\"7-8\".getBytes()), map);\n    pipe.xadd(key, XAddParams.xAddParams(), map);\n\n    List<Object> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        equalTo(new StreamEntryID(0, 1)),\n        equalTo(new StreamEntryID(2, 3)),\n        equalTo(new StreamEntryID(4, 0)),\n        equalTo(new StreamEntryID(5, 6)),\n        equalTo(new StreamEntryID(7, 8)),\n        instanceOf(StreamEntryID.class)\n    ));\n\n    assertThat((StreamEntryID) results.get(5),\n        greaterThan((StreamEntryID) results.get(4)));\n  }\n\n  @Test\n  public void xdel() {\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n\n    pipe.xadd(\"xdel-stream\", (StreamEntryID) null, map1);\n    pipe.xadd(\"xdel-stream\", (StreamEntryID) null, map1);\n\n    List<Object> results = pipe.syncAndReturnAll();\n\n    assertThat(results, contains(\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class)\n    ));\n\n    StreamEntryID id1 = (StreamEntryID) results.get(1);\n\n    pipe.xlen(\"xdel-stream\");\n    pipe.xdel(\"xdel-stream\", id1);\n    pipe.xlen(\"xdel-stream\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        2L,\n        1L,\n        1L\n    ));\n  }\n\n  @Test\n  public void xlen() {\n    pipe.xlen(\"xlen-stream\");\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"f1\", \"v1\");\n    pipe.xadd(\"xlen-stream\", (StreamEntryID) null, map);\n\n    pipe.xlen(\"xlen-stream\");\n\n    pipe.xadd(\"xlen-stream\", (StreamEntryID) null, map);\n\n    pipe.xlen(\"xlen-stream\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        equalTo(0L),\n        instanceOf(StreamEntryID.class),\n        equalTo(1L),\n        instanceOf(StreamEntryID.class),\n        equalTo(2L)\n    ));\n  }\n\n  @Test\n  public void xrange() {\n    Response<List<StreamEntry>> range = pipe.xrange(\"xrange-stream\", null, (StreamEntryID) null, Integer.MAX_VALUE);\n\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    Map<String, String> map2 = singletonMap(\"f2\", \"v2\");\n    Map<String, String> map3 = singletonMap(\"f3\", \"v3\");\n\n    Response<StreamEntryID> id1Response = pipe.xadd(\"xrange-stream\", (StreamEntryID) null, map1);\n\n    Response<StreamEntryID> id2Response = pipe.xadd(\"xrange-stream\", (StreamEntryID) null, map2);\n\n    pipe.sync();\n\n    assertThat(range.get(), empty());\n    assertThat(id1Response.get(), notNullValue());\n    assertThat(id2Response.get(), notNullValue());\n\n    StreamEntryID id1 = id1Response.get();\n    StreamEntryID id2 = id2Response.get();\n\n    Response<List<StreamEntry>> range2 = pipe.xrange(\"xrange-stream\", null, (StreamEntryID) null, 3);\n    Response<List<StreamEntry>> range3 = pipe.xrange(\"xrange-stream\", id1, null, 2);\n    Response<List<StreamEntry>> range4 = pipe.xrange(\"xrange-stream\", id1, id2, 2);\n    Response<List<StreamEntry>> range5 = pipe.xrange(\"xrange-stream\", id1, id2, 1);\n    Response<List<StreamEntry>> range6 = pipe.xrange(\"xrange-stream\", id2, null, 4);\n\n    Response<StreamEntryID> id3Response = pipe.xadd(\"xrange-stream\", (StreamEntryID) null, map3);\n\n    pipe.sync();\n\n    assertThat(range2.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2));\n    assertThat(range3.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2));\n    assertThat(range4.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2));\n    assertThat(range5.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1));\n    assertThat(range6.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2));\n\n    assertThat(id3Response.get(), notNullValue());\n\n    StreamEntryID id3 = id3Response.get();\n\n    Response<List<StreamEntry>> range7 = pipe.xrange(\"xrange-stream\", id3, id3, 4);\n    Response<List<StreamEntry>> range8 = pipe.xrange(\"xrange-stream\", null, (StreamEntryID) null);\n    Response<List<StreamEntry>> range9 = pipe.xrange(\"xrange-stream\", StreamEntryID.MINIMUM_ID, StreamEntryID.MAXIMUM_ID);\n\n    pipe.sync();\n\n    assertThat(range7.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id3));\n    assertThat(range8.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2, id3));\n    assertThat(range9.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2, id3));\n  }\n\n  @Test\n  public void xrangeExclusive() {\n    StreamEntryID id1 = client.xadd(\"xrange-stream\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n    StreamEntryID id2 = client.xadd(\"xrange-stream\", (StreamEntryID) null, singletonMap(\"f2\", \"v2\"));\n\n    Response<List<StreamEntry>> range1 = pipe.xrange(\"xrange-stream\", id1.toString(), \"+\", 2);\n    Response<List<StreamEntry>> range2 = pipe.xrange(\"xrange-stream\", \"(\" + id1, \"+\", 2);\n\n    pipe.sync();\n\n    assertThat(range1.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2));\n    assertThat(range2.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void xreadWithParams() {\n\n    final String stream1 = \"xread-stream1\";\n    final String stream2 = \"xread-stream2\";\n\n    Map<String, StreamEntryID> streamQuery1 = singletonMap(stream1, new StreamEntryID());\n\n    // Before creating Stream\n    pipe.xread(XReadParams.xReadParams().block(1), streamQuery1);\n    pipe.xread(XReadParams.xReadParams(), streamQuery1);\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        nullValue(),\n        nullValue()\n    ));\n\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(stream1, (StreamEntryID) null, map1);\n\n    Map<String, String> map2 = singletonMap(\"f2\", \"v2\");\n    StreamEntryID id2 = client.xadd(stream2, (StreamEntryID) null, map2);\n\n    // Read only a single Stream\n    Response<List<Entry<String, List<StreamEntry>>>> streams1 =\n        pipe.xread(XReadParams.xReadParams().count(1).block(1), streamQuery1);\n\n    Response<List<Entry<String, List<StreamEntry>>>> streams2 =\n        pipe.xread(XReadParams.xReadParams().block(1), singletonMap(stream1, id1));\n\n    Response<List<Entry<String, List<StreamEntry>>>> streams3 =\n        pipe.xread(XReadParams.xReadParams(), singletonMap(stream1, id1));\n\n    pipe.sync();\n\n    assertThat(streams1.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(stream1));\n\n    assertThat(streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(id1));\n\n    assertThat(streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map1));\n\n    assertThat(streams2.get(), nullValue());\n\n    assertThat(streams3.get(), nullValue());\n\n    // Read from two Streams\n    Map<String, StreamEntryID> streamQuery2 = new LinkedHashMap<>();\n    streamQuery2.put(stream1, new StreamEntryID());\n    streamQuery2.put(stream2, new StreamEntryID());\n\n    Response<List<Entry<String, List<StreamEntry>>>> streams4 =\n        pipe.xread(XReadParams.xReadParams().count(2).block(1), streamQuery2);\n\n    pipe.sync();\n\n    assertThat(streams4.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(stream1, stream2));\n\n    assertThat(streams4.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(id1, id2));\n\n    assertThat(streams4.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map1, map2));\n  }\n\n  @Test\n  public void xreadBlockZero() throws InterruptedException {\n    final AtomicReference<List<Entry<String, List<StreamEntry>>>> readRef = new AtomicReference<>();\n    Thread t = new Thread(new Runnable() {\n      @Override\n      public void run() {\n        long startTime = System.currentTimeMillis();\n        Pipeline blockPipe = client.pipelined();\n        Map<String, StreamEntryID> streamQuery = singletonMap(\"block0-stream\", new StreamEntryID());\n        Response<List<Entry<String, List<StreamEntry>>>> read =\n            blockPipe.xread(XReadParams.xReadParams().block(0), streamQuery);\n        blockPipe.sync();\n        long endTime = System.currentTimeMillis();\n        assertTrue(endTime - startTime > 500);\n        assertNotNull(read);\n        readRef.set(read.get());\n      }\n    }, \"xread-block-0-thread\");\n    t.start();\n    Thread.sleep(1000);\n    StreamEntryID addedId = client.xadd(\"block0-stream\", (StreamEntryID) null, singletonMap(\"foo\", \"bar\"));\n    t.join();\n\n    assertThat(readRef.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"block0-stream\"));\n\n    assertThat(readRef.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(addedId));\n  }\n\n  @Test\n  public void xtrim() {\n    Map<String, String> map1 = new HashMap<String, String>();\n    map1.put(\"f1\", \"v1\");\n\n    for (int i = 1; i <= 5; i++) {\n      pipe.xadd(\"xtrim-stream\", (StreamEntryID) null, map1);\n    }\n\n    pipe.xlen(\"xtrim-stream\");\n\n    pipe.xtrim(\"xtrim-stream\", 3, false);\n\n    pipe.xlen(\"xtrim-stream\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        equalTo(5L),\n        equalTo(2L),\n        equalTo(3L)\n    ));\n  }\n\n  @Test\n  public void xtrimWithParams() {\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(\"f1\", \"v1\");\n    for (int i = 1; i <= 5; i++) {\n      pipe.xadd(\"xtrim-stream\", new StreamEntryID(\"0-\" + i), map1);\n    }\n\n    pipe.xlen(\"xtrim-stream\");\n\n    pipe.xtrim(\"xtrim-stream\", XTrimParams.xTrimParams().maxLen(3).exactTrimming());\n\n    pipe.xlen(\"xtrim-stream\");\n\n    // minId\n    pipe.xtrim(\"xtrim-stream\", XTrimParams.xTrimParams().minId(\"0-4\").exactTrimming());\n\n    pipe.xlen(\"xtrim-stream\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        instanceOf(StreamEntryID.class),\n        equalTo(5L),\n        equalTo(2L),\n        equalTo(3L),\n        equalTo(1L),\n        equalTo(2L)\n    ));\n  }\n\n  @Test\n  public void xrevrange() {\n    Response<List<StreamEntry>> range = pipe.xrevrange(\"xrevrange-stream\", null, (StreamEntryID) null, Integer.MAX_VALUE);\n\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    Response<StreamEntryID> id1Response = pipe.xadd(\"xrevrange-stream\", (StreamEntryID) null, map1);\n    Map<String, String> map2 = singletonMap(\"f2\", \"v2\");\n    Response<StreamEntryID> id2Response = pipe.xadd(\"xrevrange-stream\", (StreamEntryID) null, map2);\n\n    pipe.sync();\n\n    assertThat(range.get(), empty());\n    assertThat(id1Response.get(), notNullValue());\n    assertThat(id2Response.get(), notNullValue());\n\n    StreamEntryID id1 = id1Response.get();\n    StreamEntryID id2 = id2Response.get();\n\n    Response<List<StreamEntry>> range2 = pipe.xrevrange(\"xrevrange-stream\", null, (StreamEntryID) null, 3);\n    Response<List<StreamEntry>> range3 = pipe.xrevrange(\"xrevrange-stream\", null, id1, 2);\n    Response<List<StreamEntry>> range4 = pipe.xrevrange(\"xrevrange-stream\", id2, id1, 2);\n    Response<List<StreamEntry>> range5 = pipe.xrevrange(\"xrevrange-stream\", id2, id1, 1);\n    Response<List<StreamEntry>> range6 = pipe.xrevrange(\"xrevrange-stream\", null, id2, 4);\n\n    Map<String, String> map3 = singletonMap(\"f3\", \"v3\");\n    Response<StreamEntryID> id3Response = pipe.xadd(\"xrevrange-stream\", (StreamEntryID) null, map3);\n\n    pipe.sync();\n\n    assertThat(range2.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2, id1));\n    assertThat(range3.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2, id1));\n    assertThat(range4.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2, id1));\n    assertThat(range5.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2));\n    assertThat(range6.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2));\n\n    assertThat(id3Response.get(), notNullValue());\n\n    StreamEntryID id3 = id3Response.get();\n\n    Response<List<StreamEntry>> range7 = pipe.xrevrange(\"xrevrange-stream\", id3, id3, 4);\n    Response<List<StreamEntry>> range8 = pipe.xrevrange(\"xrevrange-stream\", null, (StreamEntryID) null);\n    Response<List<StreamEntry>> range9 = pipe.xrevrange(\"xrevrange-stream\", StreamEntryID.MAXIMUM_ID, StreamEntryID.MINIMUM_ID);\n\n    pipe.sync();\n\n    assertThat(range7.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id3));\n    assertThat(range8.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id3, id2, id1));\n    assertThat(range9.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id3, id2, id1));\n  }\n\n  @Test\n  public void xrevrangeExclusive() {\n    StreamEntryID id1 = client.xadd(\"xrange-stream\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n    StreamEntryID id2 = client.xadd(\"xrange-stream\", (StreamEntryID) null, singletonMap(\"f2\", \"v2\"));\n\n    Response<List<StreamEntry>> range1 = pipe.xrevrange(\"xrange-stream\", \"+\", id1.toString(), 2);\n    Response<List<StreamEntry>> range2 = pipe.xrevrange(\"xrange-stream\", \"+\", \"(\" + id1, 2);\n\n    pipe.sync();\n\n    assertThat(range1.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2, id1));\n    assertThat(range2.get().stream().map(StreamEntry::getID).collect(Collectors.toList()), contains(id2));\n  }\n\n  @Test\n  public void xgroup() {\n    StreamEntryID id1 = client.xadd(\"xgroup-stream\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n\n    pipe.xgroupCreate(\"xgroup-stream\", \"consumer-group-name\", null, false);\n    pipe.xgroupSetID(\"xgroup-stream\", \"consumer-group-name\", id1);\n    pipe.xgroupCreate(\"xgroup-stream\", \"consumer-group-name1\", StreamEntryID.XGROUP_LAST_ENTRY, false);\n\n    pipe.xgroupDestroy(\"xgroup-stream\", \"consumer-group-name\");\n    pipe.xgroupDelConsumer(\"xgroup-stream\", \"consumer-group-name1\", \"myconsumer1\");\n    pipe.xgroupCreateConsumer(\"xgroup-stream\", \"consumer-group-name1\", \"myconsumer2\");\n    pipe.xgroupDelConsumer(\"xgroup-stream\", \"consumer-group-name1\", \"myconsumer2\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        \"OK\",\n        \"OK\",\n        \"OK\",\n        1L,\n        0L,\n        true,\n        0L\n    ));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void xreadGroupWithParams() {\n    // Simple xreadGroup with NOACK\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xreadGroup-stream1\", (StreamEntryID) null, map1);\n\n    client.xgroupCreate(\"xreadGroup-stream1\", \"xreadGroup-group\", null, false);\n\n    Map<String, StreamEntryID> streamQuery1 = singletonMap(\"xreadGroup-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    Response<List<Entry<String, List<StreamEntry>>>> streams1 =\n        pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n            XReadGroupParams.xReadGroupParams().count(1).noAck(), streamQuery1);\n\n    pipe.sync();\n\n    assertThat(streams1.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"xreadGroup-stream1\"));\n\n    assertThat(streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(id1));\n\n    assertThat(streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map1));\n\n    Map<String, String> map2 = singletonMap(\"f2\", \"v2\");\n    StreamEntryID id2 = client.xadd(\"xreadGroup-stream1\", (StreamEntryID) null, map2);\n\n    Map<String, String> map3 = singletonMap(\"f3\", \"v3\");\n    StreamEntryID id3 = client.xadd(\"xreadGroup-stream2\", (StreamEntryID) null, map3);\n\n    client.xgroupCreate(\"xreadGroup-stream2\", \"xreadGroup-group\", null, false);\n\n    // Read only a single Stream\n    Response<List<Entry<String, List<StreamEntry>>>> streams2 = pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1).noAck(), streamQuery1);\n\n    // Read from two Streams\n    Map<String, StreamEntryID> streamQuery2 = new LinkedHashMap<>();\n    streamQuery2.put(\"xreadGroup-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    streamQuery2.put(\"xreadGroup-stream2\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    Response<List<Entry<String, List<StreamEntry>>>> streams3 = pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).noAck(), streamQuery2);\n\n    pipe.sync();\n\n    assertThat(streams2.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"xreadGroup-stream1\"));\n\n    assertThat(streams2.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(id2));\n\n    assertThat(streams2.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map2));\n\n    assertThat(streams3.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"xreadGroup-stream2\"));\n\n    assertThat(streams3.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(id3));\n\n    assertThat(streams3.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map3));\n\n    // Read only fresh messages\n    Map<String, String> map4 = singletonMap(\"f4\", \"v4\");\n    StreamEntryID id4 = client.xadd(\"xreadGroup-stream1\", (StreamEntryID) null, map4);\n\n    Map<String, StreamEntryID> streamQueryFresh = singletonMap(\"xreadGroup-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    Response<List<Entry<String, List<StreamEntry>>>> streams4 = pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(4).block(100).noAck(), streamQueryFresh);\n\n    pipe.sync();\n\n    assertThat(streams4.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"xreadGroup-stream1\"));\n\n    assertThat(streams4.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(id4));\n\n    assertThat(streams4.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map4));\n  }\n\n  @Test\n  public void xreadGroupWithParamsWhenPendingMessageIsDiscarded() {\n    // Add two message to stream\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n\n    XAddParams xAddParams = XAddParams.xAddParams().id(StreamEntryID.NEW_ENTRY).maxLen(2);\n    StreamEntryID firstMessageEntryId = client.xadd(\"xreadGroup-discard-stream1\", xAddParams, map1);\n\n    client.xadd(\"xreadGroup-discard-stream1\", xAddParams, singletonMap(\"f2\", \"v2\"));\n\n    pipe.xgroupCreate(\"xreadGroup-discard-stream1\", \"xreadGroup-group\", null, false);\n\n    Map<String, StreamEntryID> streamQuery1 = singletonMap(\"xreadGroup-discard-stream1\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    Response<List<Entry<String, List<StreamEntry>>>> streams1 =\n        pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n            XReadGroupParams.xReadGroupParams().count(1), streamQuery1);\n\n    pipe.sync();\n\n    assertThat(streams1.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"xreadGroup-discard-stream1\"));\n\n    assertThat(streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(firstMessageEntryId));\n\n    assertThat(streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(map1));\n\n    // Add third message, the fields of pending message1 will be discarded by redis-server\n    client.xadd(\"xreadGroup-discard-stream1\", xAddParams, singletonMap(\"f3\", \"v3\"));\n\n    Map<String, StreamEntryID> streamQueryPending = singletonMap(\"xreadGroup-discard-stream1\", new StreamEntryID());\n\n    Response<List<Entry<String, List<StreamEntry>>>> pendingMessages =\n        pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n            XReadGroupParams.xReadGroupParams().count(1).noAck(), streamQueryPending);\n\n    pipe.sync();\n\n    assertThat(pendingMessages.get().stream().map(Entry::getKey).collect(Collectors.toList()),\n        contains(\"xreadGroup-discard-stream1\"));\n\n    assertThat(pendingMessages.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList()), contains(firstMessageEntryId));\n\n    assertThat(pendingMessages.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getFields).collect(Collectors.toList()), contains(nullValue()));\n  }\n\n  @Test\n  public void xack() {\n    pipe.xadd(\"xack-stream\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n\n    pipe.xgroupCreate(\"xack-stream\", \"xack-group\", null, false);\n\n    Map<String, StreamEntryID> streamQuery1 = singletonMap(\"xack-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    // Empty Stream\n    Response<List<Entry<String, List<StreamEntry>>>> streams1 =\n        pipe.xreadGroup(\"xack-group\", \"xack-consumer\",\n            XReadGroupParams.xReadGroupParams().count(1).block(1), streamQuery1);\n\n    pipe.sync();\n\n    List<StreamEntryID> ids = streams1.get().stream().map(Entry::getValue).flatMap(List::stream)\n        .map(StreamEntry::getID).collect(Collectors.toList());\n    assertThat(ids, hasSize(1));\n\n    Response<Long> xackResponse = pipe.xack(\"xack-stream\", \"xack-group\", ids.get(0));\n\n    pipe.sync();\n\n    assertThat(xackResponse.get(), equalTo(1L));\n  }\n\n  @Test\n  public void xpendingWithParams() {\n    Map<String, String> map = singletonMap(\"f1\", \"v1\");\n\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", (StreamEntryID) null, map);\n\n    assertEquals(\"OK\", client.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false));\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n\n    // Read the event from Stream put it on pending\n    Response<List<Entry<String, List<StreamEntry>>>> range = pipe.xreadGroup(\"xpending-group\",\n        \"xpending-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1), streamQeury1);\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending1 =\n        pipe.xpending(\"xpending-stream\", \"xpending-group\",\n            new XPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    // Without consumer\n    Response<List<StreamPendingEntry>> pending2 =\n        pipe.xpending(\"xpending-stream\", \"xpending-group\",\n            new XPendingParams().count(3));\n\n    // with idle\n    Response<List<StreamPendingEntry>> pending3 =\n        pipe.xpending(\"xpending-stream\", \"xpending-group\",\n            new XPendingParams().idle(Duration.ofMinutes(1).toMillis()).count(3));\n\n    pipe.sync();\n\n    assertThat(pending1.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending1.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(pending1.get().stream().map(StreamPendingEntry::getDeliveredTimes).collect(Collectors.toList()),\n        contains(1L));\n\n    assertThat(pending2.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending2.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(pending2.get().stream().map(StreamPendingEntry::getDeliveredTimes).collect(Collectors.toList()),\n        contains(1L));\n\n    assertThat(pending3.get(), empty());\n  }\n\n  @Test\n  public void xpendingRange() {\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", (StreamEntryID) null, singletonMap(\"f1\", \"v1\"));\n    StreamEntryID id2 = client.xadd(\"xpending-stream\", (StreamEntryID) null, singletonMap(\"f2\", \"v2\"));\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // read 1 message from the group with each consumer\n    Map<String, StreamEntryID> streamQeury = singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    pipe.xreadGroup(\"xpending-group\", \"consumer1\", XReadGroupParams.xReadGroupParams().count(1), streamQeury);\n    pipe.xreadGroup(\"xpending-group\", \"consumer2\", XReadGroupParams.xReadGroupParams().count(1), streamQeury);\n\n    Response<List<StreamPendingEntry>> pending1 = pipe.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams(\"(0\", \"+\", 5));\n\n    Response<List<StreamPendingEntry>> pending2 = pipe.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams(StreamEntryID.MINIMUM_ID, StreamEntryID.MAXIMUM_ID, 5));\n\n    pipe.sync();\n\n    assertThat(pending1.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"consumer1\", \"consumer2\"));\n\n    assertThat(pending1.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1, id2));\n\n    assertThat(pending2.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"consumer1\", \"consumer2\"));\n\n    assertThat(pending2.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1, id2));\n  }\n\n  @Test\n  public void xclaimWithParams() throws InterruptedException {\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", (StreamEntryID) null, map1);\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // Read the event from Stream put it on pending\n    pipe.xreadGroup(\"xpending-group\", \"xpending-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending =\n        pipe.xpending(\"xpending-stream\", \"xpending-group\",\n            XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    // must sync before the sleep\n    pipe.sync();\n\n    // Sleep a bit so we can claim events pending for more than 50ms\n    Thread.sleep(100);\n\n    Response<List<StreamEntry>> claimed =\n        pipe.xclaim(\"xpending-stream\", \"xpending-group\", \"xpending-consumer2\", 50,\n            XClaimParams.xClaimParams().idle(0).retryCount(0), id1);\n\n    pipe.sync();\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(claimed.get().stream().map(StreamEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(claimed.get().stream().map(StreamEntry::getFields).collect(Collectors.toList()),\n        contains(map1));\n  }\n\n  @Test\n  public void xclaimJustId() throws InterruptedException {\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", (StreamEntryID) null, map1);\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // Read the event from Stream put it on pending\n    pipe.xreadGroup(\"xpending-group\", \"xpending-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending =\n        pipe.xpending(\"xpending-stream\", \"xpending-group\",\n            XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    // must sync before the sleep\n    pipe.sync();\n\n    // Sleep for 100ms so we can claim events pending for more than 50ms\n    Thread.sleep(100);\n\n    Response<List<StreamEntryID>> claimedIds =\n        pipe.xclaimJustId(\"xpending-stream\", \"xpending-group\", \"xpending-consumer2\", 50,\n            XClaimParams.xClaimParams().idle(0).retryCount(0), id1);\n\n    pipe.sync();\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(claimedIds.get(), contains(id1));\n  }\n\n  @Test\n  public void xautoclaim() throws InterruptedException {\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", (StreamEntryID) null, map1);\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // Read the event from Stream put it on pending\n    pipe.xreadGroup(\"xpending-group\", \"xpending-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending = pipe.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    pipe.sync();\n\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    Thread.sleep(100);\n\n    // Auto claim pending events to different consumer\n    Response<Entry<StreamEntryID, List<StreamEntry>>> autoclaimed = pipe.xautoclaim(\"xpending-stream\", \"xpending-group\",\n        \"xpending-consumer2\", 50, new StreamEntryID(), new XAutoClaimParams().count(1));\n\n    pipe.sync();\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(autoclaimed.get().getValue().stream().map(StreamEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(autoclaimed.get().getValue().stream().map(StreamEntry::getFields).collect(Collectors.toList()),\n        contains(map1));\n  }\n\n  @Test\n  public void xautoclaimBinary() throws InterruptedException {\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", XAddParams.xAddParams(), map1);\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // Read the event from Stream put it on pending\n    pipe.xreadGroup(\"xpending-group\", \"xpending-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending = pipe.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    pipe.sync();\n\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    Thread.sleep(100);\n\n    // Auto claim pending events to different consumer\n    Response<List<Object>> autoclaimed = pipe.xautoclaim(SafeEncoder.encode(\"xpending-stream\"),\n        SafeEncoder.encode(\"xpending-group\"), SafeEncoder.encode(\"xpending-consumer2\"),\n        50, SafeEncoder.encode(new StreamEntryID().toString()), new XAutoClaimParams().count(1));\n\n    pipe.sync();\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    Map.Entry<StreamEntryID, List<StreamEntry>> autoclaimedParsed =\n        BuilderFactory.STREAM_AUTO_CLAIM_RESPONSE.build(autoclaimed.get());\n\n    assertThat(autoclaimedParsed.getValue().stream().map(StreamEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(autoclaimedParsed.getValue().stream().map(StreamEntry::getFields).collect(Collectors.toList()),\n        contains(map1));\n  }\n\n  @Test\n  public void xautoclaimJustId() throws InterruptedException {\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", XAddParams.xAddParams(), map1);\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // Read the event from Stream put it on pending\n    pipe.xreadGroup(\"xpending-group\", \"xpending-consumer\", XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending = pipe.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    pipe.sync();\n\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    Thread.sleep(100);\n\n    // Auto claim pending events to different consumer\n    Response<Entry<StreamEntryID, List<StreamEntryID>>> claimedIds = pipe.xautoclaimJustId(\"xpending-stream\", \"xpending-group\",\n        \"xpending-consumer2\", 50, new StreamEntryID(), new XAutoClaimParams().count(1));\n\n    pipe.sync();\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    assertThat(claimedIds.get().getValue(), contains(id1));\n  }\n\n  @Test\n  public void xautoclaimJustIdBinary() throws InterruptedException {\n    Map<String, String> map1 = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"xpending-stream\", XAddParams.xAddParams(), map1);\n\n    pipe.xgroupCreate(\"xpending-stream\", \"xpending-group\", null, false);\n\n    // Read the event from Stream put it on pending\n    pipe.xreadGroup(\"xpending-group\", \"xpending-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1).block(1),\n        singletonMap(\"xpending-stream\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY));\n\n    // Get the pending event\n    Response<List<StreamPendingEntry>> pending = pipe.xpending(\"xpending-stream\", \"xpending-group\",\n        XPendingParams.xPendingParams().count(3).consumer(\"xpending-consumer\"));\n\n    pipe.sync();\n\n    // Sleep for 100ms so we can auto claim events pending for more than 50ms\n    Thread.sleep(100);\n\n    // Auto claim pending events to different consumer\n    Response<List<Object>> autoclaimed = pipe.xautoclaimJustId(SafeEncoder.encode(\"xpending-stream\"),\n        SafeEncoder.encode(\"xpending-group\"), SafeEncoder.encode(\"xpending-consumer2\"),\n        50, SafeEncoder.encode(new StreamEntryID().toString()), new XAutoClaimParams().count(1));\n\n    pipe.sync();\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getConsumerName).collect(Collectors.toList()),\n        contains(\"xpending-consumer\"));\n\n    assertThat(pending.get().stream().map(StreamPendingEntry::getID).collect(Collectors.toList()),\n        contains(id1));\n\n    Entry<StreamEntryID, List<StreamEntryID>> autoclaimedParsed =\n        BuilderFactory.STREAM_AUTO_CLAIM_JUSTID_RESPONSE.build(autoclaimed.get());\n\n    assertThat(autoclaimedParsed.getValue(), contains(id1));\n  }\n\n  @Test\n  public void xinfo() throws InterruptedException {\n    final String STREAM_NAME = \"xadd-stream1\";\n    final String F1 = \"f1\";\n    final String V1 = \"v1\";\n    final String V2 = \"v2\";\n    final String G1 = \"G1\";\n    final String G2 = \"G2\";\n    final String MY_CONSUMER = \"myConsumer\";\n    final String MY_CONSUMER2 = \"myConsumer2\";\n\n    Map<String, String> map1 = new HashMap<>();\n    map1.put(F1, V1);\n    StreamEntryID id1 = client.xadd(STREAM_NAME, (StreamEntryID) null, map1);\n    map1.put(F1, V2);\n    StreamEntryID id2 = client.xadd(STREAM_NAME, (StreamEntryID) null, map1);\n\n    Response<StreamInfo> streamInfoResponse = pipe.xinfoStream(STREAM_NAME);\n\n    pipe.xgroupCreate(STREAM_NAME, G1, StreamEntryID.XGROUP_LAST_ENTRY, false);\n\n    Map<String, StreamEntryID> streamQuery1 = singletonMap(STREAM_NAME, new StreamEntryID(\"0-0\"));\n\n    pipe.xreadGroup(G1, MY_CONSUMER, XReadGroupParams.xReadGroupParams().count(1), streamQuery1);\n\n    pipe.sync();\n\n    Thread.sleep(1);\n\n    Response<List<StreamGroupInfo>> groupInfoResponse = pipe.xinfoGroups(STREAM_NAME);\n    Response<List<StreamConsumersInfo>> consumersInfoResponse = pipe.xinfoConsumers(STREAM_NAME, G1);\n    Response<List<StreamConsumerInfo>> consumerInfoResponse = pipe.xinfoConsumers2(STREAM_NAME, G1);\n\n    pipe.sync();\n\n    // Stream info test\n    StreamInfo streamInfo = streamInfoResponse.get();\n\n    assertEquals(2L, streamInfo.getStreamInfo().get(StreamInfo.LENGTH));\n    assertEquals(1L, streamInfo.getStreamInfo().get(StreamInfo.RADIX_TREE_KEYS));\n    assertEquals(2L, streamInfo.getStreamInfo().get(StreamInfo.RADIX_TREE_NODES));\n    assertEquals(0L, streamInfo.getStreamInfo().get(StreamInfo.GROUPS));\n    assertEquals(V1, ((StreamEntry) streamInfo.getStreamInfo().get(StreamInfo.FIRST_ENTRY)).getFields().get(F1));\n    assertEquals(V2, ((StreamEntry) streamInfo.getStreamInfo().get(StreamInfo.LAST_ENTRY)).getFields().get(F1));\n    assertEquals(id2, streamInfo.getStreamInfo().get(StreamInfo.LAST_GENERATED_ID));\n\n    // Using getters\n    assertEquals(2, streamInfo.getLength());\n    assertEquals(1, streamInfo.getRadixTreeKeys());\n    assertEquals(2, streamInfo.getRadixTreeNodes());\n    assertEquals(0, streamInfo.getGroups());\n    assertEquals(V1, streamInfo.getFirstEntry().getFields().get(F1));\n    assertEquals(V2, streamInfo.getLastEntry().getFields().get(F1));\n    assertEquals(id2, streamInfo.getLastGeneratedId());\n\n    // Group info test\n    List<StreamGroupInfo> groupInfo = groupInfoResponse.get();\n\n    assertEquals(1, groupInfo.size());\n    assertEquals(G1, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.NAME));\n    assertEquals(1L, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.CONSUMERS));\n    assertEquals(0L, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.PENDING));\n    assertEquals(id2, groupInfo.get(0).getGroupInfo().get(StreamGroupInfo.LAST_DELIVERED));\n\n    // Using getters\n    assertEquals(1, groupInfo.size());\n    assertEquals(G1, groupInfo.get(0).getName());\n    assertEquals(1, groupInfo.get(0).getConsumers());\n    assertEquals(0, groupInfo.get(0).getPending());\n    assertEquals(id2, groupInfo.get(0).getLastDeliveredId());\n\n    // Consumers info test\n    List<StreamConsumersInfo> consumersInfo = consumersInfoResponse.get();\n\n    assertEquals(MY_CONSUMER,\n        consumersInfo.get(0).getConsumerInfo().get(StreamConsumersInfo.NAME));\n    assertEquals(0L, consumersInfo.get(0).getConsumerInfo().get(StreamConsumersInfo.PENDING));\n    assertTrue((Long) consumersInfo.get(0).getConsumerInfo().get(StreamConsumersInfo.IDLE) > 0);\n\n    // Using getters\n    assertEquals(MY_CONSUMER, consumersInfo.get(0).getName());\n    assertEquals(0L, consumersInfo.get(0).getPending());\n    assertThat(consumersInfo.get(0).getIdle(), Matchers.greaterThanOrEqualTo(0L));\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_0_0)) {\n      assertThat(consumersInfo.get(0).getInactive(), Matchers.any(Long.class));\n    }\n\n    // Consumer info test\n    List<StreamConsumerInfo> consumerInfo = consumerInfoResponse.get();\n\n    assertEquals(MY_CONSUMER,\n        consumerInfo.get(0).getConsumerInfo().get(StreamConsumerInfo.NAME));\n    assertEquals(0L, consumerInfo.get(0).getConsumerInfo().get(StreamConsumerInfo.PENDING));\n    assertTrue((Long) consumerInfo.get(0).getConsumerInfo().get(StreamConsumerInfo.IDLE) > 0);\n\n    // Using getters\n    assertEquals(MY_CONSUMER, consumerInfo.get(0).getName());\n    assertEquals(0L, consumerInfo.get(0).getPending());\n    assertThat(consumerInfo.get(0).getIdle(), Matchers.greaterThanOrEqualTo(0L));\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_0_0)) {\n      assertThat(consumerInfo.get(0).getInactive(), Matchers.any(Long.class));\n    }\n\n    // test with more groups and consumers\n    pipe.xgroupCreate(STREAM_NAME, G2, StreamEntryID.XGROUP_LAST_ENTRY, false);\n    pipe.xreadGroup(G1, MY_CONSUMER2, XReadGroupParams.xReadGroupParams().count(1), streamQuery1);\n    pipe.xreadGroup(G2, MY_CONSUMER, XReadGroupParams.xReadGroupParams().count(1), streamQuery1);\n    pipe.xreadGroup(G2, MY_CONSUMER2, XReadGroupParams.xReadGroupParams().count(1), streamQuery1);\n\n    Response<List<StreamGroupInfo>> manyGroupsInfoResponse = pipe.xinfoGroups(STREAM_NAME);\n    Response<List<StreamConsumersInfo>> manyConsumersInfoResponse = pipe.xinfoConsumers(STREAM_NAME, G2);\n    Response<List<StreamConsumerInfo>> manyConsumerInfoResponse = pipe.xinfoConsumers2(STREAM_NAME, G2);\n    Response<StreamFullInfo> streamInfoFullResponse = pipe.xinfoStreamFull(STREAM_NAME);\n    Response<StreamFullInfo> streamInfoFull10Response = pipe.xinfoStreamFull(STREAM_NAME, 10);\n\n    pipe.sync();\n\n    List<StreamGroupInfo> manyGroupsInfo = manyGroupsInfoResponse.get();\n    List<StreamConsumersInfo> manyConsumersInfo = manyConsumersInfoResponse.get();\n    List<StreamConsumerInfo> manyConsumerInfo = manyConsumerInfoResponse.get();\n    StreamFullInfo streamInfoFull = streamInfoFullResponse.get();\n    StreamFullInfo streamInfoFull10 = streamInfoFull10Response.get();\n\n    assertEquals(2, manyGroupsInfo.size());\n    assertEquals(2, manyConsumersInfo.size());\n    assertEquals(2, manyConsumerInfo.size());\n\n    assertEquals(2, streamInfoFull.getEntries().size());\n    assertEquals(2, streamInfoFull.getGroups().size());\n    assertEquals(2, streamInfoFull.getLength());\n    assertEquals(1, streamInfoFull.getRadixTreeKeys());\n    assertEquals(2, streamInfoFull.getRadixTreeNodes());\n    assertEquals(0, streamInfo.getGroups());\n    assertEquals(G1, streamInfoFull.getGroups().get(0).getName());\n    assertEquals(G2, streamInfoFull.getGroups().get(1).getName());\n    assertEquals(V1, streamInfoFull.getEntries().get(0).getFields().get(F1));\n    assertEquals(V2, streamInfoFull.getEntries().get(1).getFields().get(F1));\n    assertEquals(id2, streamInfoFull.getLastGeneratedId());\n\n    assertEquals(G1, streamInfoFull10.getGroups().get(0).getName());\n    assertEquals(G2, streamInfoFull10.getGroups().get(1).getName());\n    assertEquals(V1, streamInfoFull10.getEntries().get(0).getFields().get(F1));\n    assertEquals(V2, streamInfoFull10.getEntries().get(1).getFields().get(F1));\n    assertEquals(id2, streamInfoFull10.getLastGeneratedId());\n\n    // Not existing key - redis cli return error so we expect exception\n    pipe.xinfoStream(\"random\");\n\n    assertThat(pipe.syncAndReturnAll(), contains(\n        both(instanceOf(JedisDataException.class)).and(hasToString(containsString(\"ERR no such key\")))\n    ));\n  }\n\n  @Test\n  public void xinfoStreamFullWithPending() {\n    Map<String, String> map = singletonMap(\"f1\", \"v1\");\n    StreamEntryID id1 = client.xadd(\"streamfull2\", (StreamEntryID) null, map);\n    StreamEntryID id2 = client.xadd(\"streamfull2\", (StreamEntryID) null, map);\n    client.xgroupCreate(\"streamfull2\", \"xreadGroup-group\", null, false);\n\n    Map<String, StreamEntryID> streamQeury1 = singletonMap(\"streamfull2\", StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY);\n    Response<List<Entry<String, List<StreamEntry>>>> pending = pipe.xreadGroup(\"xreadGroup-group\", \"xreadGroup-consumer\",\n        XReadGroupParams.xReadGroupParams().count(1), streamQeury1);\n\n    Response<StreamFullInfo> fullResult = pipe.xinfoStreamFull(\"streamfull2\");\n\n    pipe.sync();\n\n    assertThat(pending.get(), hasSize(1));\n\n    StreamFullInfo full = fullResult.get();\n    assertEquals(1, full.getGroups().size());\n    StreamGroupFullInfo group = full.getGroups().get(0);\n    assertEquals(\"xreadGroup-group\", group.getName());\n\n    assertEquals(1, group.getPending().size());\n    List<Object> groupPendingEntry = group.getPending().get(0);\n    assertEquals(id1, groupPendingEntry.get(0));\n    assertEquals(\"xreadGroup-consumer\", groupPendingEntry.get(1));\n\n    assertEquals(1, group.getConsumers().size());\n    StreamConsumerFullInfo consumer = group.getConsumers().get(0);\n    assertEquals(\"xreadGroup-consumer\", consumer.getName());\n    assertThat(consumer.getSeenTime(), Matchers.greaterThanOrEqualTo(0L));\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_0_0)) {\n      assertThat(consumer.getActiveTime(), Matchers.greaterThanOrEqualTo(0L));\n    }\n    assertEquals(1, consumer.getPending().size());\n    List<Object> consumerPendingEntry = consumer.getPending().get(0);\n    assertEquals(id1, consumerPendingEntry.get(0));\n  }\n\n  @Test\n  @EnabledOnCommand(\"XCFGSET\")\n  public void xcfgset() {\n    // Add an entry to create the stream\n    client.xadd(\"xcfgset-stream\", StreamEntryID.NEW_ENTRY, singletonMap(\"field\", \"value\"));\n\n    // Configure idempotent producer settings via pipeline\n    Response<String> response = pipe.xcfgset(\"xcfgset-stream\",\n        XCfgSetParams.xCfgSetParams().idmpDuration(1000).idmpMaxsize(500));\n\n    pipe.sync();\n\n    assertEquals(\"OK\", response.get());\n\n    // Verify settings via XINFO STREAM\n    StreamInfo info = client.xinfoStream(\"xcfgset-stream\");\n    assertEquals(Long.valueOf(1000), info.getIdmpDuration());\n    assertEquals(Long.valueOf(500), info.getIdmpMaxsize());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/search/FTHybridCommandsTestBase.java",
    "content": "package redis.clients.jedis.commands.unified.search;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.closeTo;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\nimport static redis.clients.jedis.util.AssertUtil.assertOK;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.commands.unified.UnifiedJedisCommandsTestBase;\nimport redis.clients.jedis.search.Document;\nimport redis.clients.jedis.search.FTCreateParams;\nimport redis.clients.jedis.search.IndexDataType;\nimport redis.clients.jedis.search.Scorer;\nimport redis.clients.jedis.search.Scorers;\nimport redis.clients.jedis.search.aggr.Group;\nimport redis.clients.jedis.search.aggr.Reducers;\nimport redis.clients.jedis.search.aggr.SortedField;\nimport redis.clients.jedis.search.Apply;\nimport redis.clients.jedis.search.Filter;\nimport redis.clients.jedis.search.Combiners;\nimport redis.clients.jedis.search.hybrid.FTHybridParams;\nimport redis.clients.jedis.search.hybrid.FTHybridPostProcessingParams;\nimport redis.clients.jedis.search.hybrid.FTHybridSearchParams;\nimport redis.clients.jedis.search.hybrid.FTHybridVectorParams;\nimport redis.clients.jedis.search.hybrid.HybridResult;\nimport redis.clients.jedis.search.Limit;\nimport redis.clients.jedis.search.schemafields.NumericField;\nimport redis.clients.jedis.search.schemafields.TagField;\nimport redis.clients.jedis.search.schemafields.TextField;\nimport redis.clients.jedis.search.schemafields.VectorField;\n\n/**\n * Base test class for FT.HYBRID command using the UnifiedJedis pattern. Tests hybrid search\n * functionality combining text search and vector similarity.\n */\n@Tag(\"integration\")\n@Tag(\"search\")\n@SinceRedisVersion(\"8.4.0\")\npublic abstract class FTHybridCommandsTestBase extends UnifiedJedisCommandsTestBase {\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"modules-docker\");\n  }\n\n  private static final String INDEX_NAME = \"hybrid-test-idx\";\n  private static final String PREFIX = \"product:hybrid:\";\n  private static final int VECTOR_DIM = 10;\n\n  protected byte[] queryVector;\n\n  public FTHybridCommandsTestBase(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  public void setUpTestData() {\n    if (!jedis.ftList().contains(INDEX_NAME)) {\n      createHybridIndex();\n      createSampleProducts();\n    }\n\n    // Always initialize query vector (cheap operation)\n    queryVector = floatArrayToByteArray(\n      new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f });\n  }\n\n  // ========== Setup Helper Methods ==========\n\n  private void createHybridIndex() {\n    Map<String, Object> vectorAttrs = new HashMap<>();\n    vectorAttrs.put(\"TYPE\", \"FLOAT32\");\n    vectorAttrs.put(\"DIM\", VECTOR_DIM);\n    vectorAttrs.put(\"DISTANCE_METRIC\", \"COSINE\");\n\n    assertOK(jedis.ftCreate(INDEX_NAME,\n      FTCreateParams.createParams().on(IndexDataType.HASH).prefix(PREFIX), TextField.of(\"id\"),\n      TextField.of(\"title\"), TagField.of(\"category\"), TagField.of(\"brand\"),\n      NumericField.of(\"price\"), NumericField.of(\"rating\"),\n      VectorField.builder().fieldName(\"image_embedding\").algorithm(VectorField.VectorAlgorithm.HNSW)\n          .attributes(vectorAttrs).build()));\n  }\n\n  private void createSampleProducts() {\n    // Electronics - Apple products\n    createProduct(\"1\", \"Apple iPhone 15 Pro smartphone with advanced camera\", \"electronics\",\n      \"apple\", \"999\", \"4.8\",\n      new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f });\n    createProduct(\"4\", \"Apple iPhone 15 Pro smartphone camera\", \"electronics\", \"apple\", \"999\",\n      \"4.8\", new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f });\n    createProduct(\"10\", \"Apple iPhone 15 Pro smartphone camera\", \"electronics\", \"apple\", \"999\",\n      \"4.8\", new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f });\n\n    // Electronics - Samsung products\n    createProduct(\"2\", \"Samsung Galaxy S24 smartphone camera\", \"electronics\", \"samsung\", \"799\",\n      \"4.6\", new float[] { 0.15f, 0.25f, 0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.9f });\n    createProduct(\"5\", \"Samsung Galaxy S24\", \"electronics\", \"samsung\", \"799\", \"4.6\",\n      new float[] { 0.15f, 0.25f, 0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.9f });\n\n    // Electronics - Google products\n    createProduct(\"3\", \"Google Pixel 8 Pro camera smartphone\", \"electronics\", \"google\", \"699\",\n      \"4.5\", new float[] { 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 0.8f });\n    createProduct(\"6\", \"Google Pixel 8 Pro\", \"electronics\", \"google\", \"699\", \"4.5\",\n      new float[] { 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 0.8f });\n\n    // Other categories\n    createProduct(\"7\", \"Best T-shirt\", \"apparel\", \"denim\", \"255\", \"4.2\",\n      new float[] { 0.12f, 0.22f, 0.32f, 0.42f, 0.52f, 0.62f, 0.72f, 0.82f, 0.92f, 0.85f });\n    createProduct(\"8\", \"Best makeup\", \"beauty\", \"loreal\", \"155\", \"4.4\",\n      new float[] { 0.18f, 0.28f, 0.38f, 0.48f, 0.58f, 0.68f, 0.78f, 0.88f, 0.98f, 0.75f });\n    createProduct(\"9\", \"Best punching bag\", \"sports\", \"lonsdale\", \"733\", \"4.6\",\n      new float[] { 0.11f, 0.21f, 0.31f, 0.41f, 0.51f, 0.61f, 0.71f, 0.81f, 0.91f, 0.95f });\n  }\n\n  private void createProduct(String id, String title, String category, String brand, String price,\n      String rating, float[] embedding) {\n    Map<byte[], byte[]> fields = new HashMap<>();\n    fields.put(\"id\".getBytes(), id.getBytes());\n    fields.put(\"title\".getBytes(), title.getBytes());\n    fields.put(\"category\".getBytes(), category.getBytes());\n    fields.put(\"brand\".getBytes(), brand.getBytes());\n    fields.put(\"price\".getBytes(), price.getBytes());\n    fields.put(\"rating\".getBytes(), rating.getBytes());\n    fields.put(\"image_embedding\".getBytes(), floatArrayToByteArray(embedding));\n\n    jedis.hset((PREFIX + id).getBytes(), fields);\n  }\n\n  private byte[] floatArrayToByteArray(float[] floats) {\n    ByteBuffer buffer = ByteBuffer.allocate(floats.length * 4).order(ByteOrder.LITTLE_ENDIAN);\n    for (float f : floats) {\n      buffer.putFloat(f);\n    }\n    return buffer.array();\n  }\n\n  // ========== Test Methods ==========\n\n  @Test\n  public void testComprehensiveFtHybridWithAllFeatures() {\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .load(\"price\", \"brand\", \"@category\")\n        .groupBy(new Group(\"@brand\").reduce(Reducers.sum(\"@price\").as(\"sum\"))\n            .reduce(Reducers.count().as(\"count\")))\n        .apply(Apply.of(\"@sum * 0.9\", \"discounted_price\"))\n        .sortBy(SortedField.asc(\"@sum\"), SortedField.desc(\"@count\")).filter(Filter.of(\"@sum > 700\"))\n        .limit(Limit.of(0, 20)).build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"@category:{electronics} smartphone camera\")\n            .scorer(Scorers.bm25std()).scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(20).efRuntime(150))\n            .filter(\"(@brand:{apple|samsung|google}) (@price:[500 1500]) (@category:{electronics})\")\n            .scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.7).beta(0.3).window(25)).postProcessing(postProcessing)\n        .param(\"discount_rate\", \"0.9\").param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n    assertThat(reply.getTotalResults(), equalTo(3L));\n    assertThat(reply.getDocuments().size(), equalTo(3));\n    assertThat(reply.getWarnings().size(), greaterThanOrEqualTo(0));\n    assertThat(reply.getExecutionTime(), greaterThan(0.0));\n\n    // Verify first result (google) - exact field values\n    Document r1 = reply.getDocuments().get(0);\n    assertThat(r1.get(\"brand\"), equalTo(\"google\"));\n    assertThat(r1.get(\"count\"), equalTo(\"2\"));\n    assertThat(r1.get(\"sum\"), equalTo(\"1398\"));\n    assertThat(r1.get(\"discounted_price\"), equalTo(\"1258.2\"));\n\n    // Verify second result (samsung) - exact field values\n    Document r2 = reply.getDocuments().get(1);\n    assertThat(r2.get(\"brand\"), equalTo(\"samsung\"));\n    assertThat(r2.get(\"count\"), equalTo(\"2\"));\n    assertThat(r2.get(\"sum\"), equalTo(\"1598\"));\n    assertThat(r2.get(\"discounted_price\"), equalTo(\"1438.2\"));\n\n    // Verify third result (apple) - exact field values\n    Document r3 = reply.getDocuments().get(2);\n    assertThat(r3.get(\"brand\"), equalTo(\"apple\"));\n    assertThat(r3.get(\"count\"), equalTo(\"3\"));\n    assertThat(r3.get(\"sum\"), equalTo(\"2997\"));\n    assertThat(r3.get(\"discounted_price\"), equalTo(\"2697.3\"));\n  }\n\n  @Test\n  public void testLoadSpecificFields() {\n    // Test LOAD with specific fields\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .load(\"title\", \"@price\", \"brand\").build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"@category:{electronics} smartphone\")\n            .scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(5)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Field count and content assertions\n    Document firstResult = reply.getDocuments().get(0);\n\n    // Loaded fields should be present\n    assertThat(firstResult.hasProperty(\"title\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"price\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"brand\"), equalTo(true));\n\n    // Non-loaded document fields should NOT be present\n    assertThat(firstResult.hasProperty(\"category\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"rating\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"image_embedding\"), equalTo(false));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.6.0\")\n  public void testLoadAllFields() {\n    // Test LOAD * to load all fields\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder().loadAll()\n        .build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"@category:{electronics}\")\n            .scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(3)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Field count and content assertions\n    Document firstResult = reply.getDocuments().get(0);\n\n    // All document fields should be present\n    assertThat(firstResult.hasProperty(\"title\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"category\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"brand\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"price\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"rating\"), equalTo(true));\n\n    // Vector field should also be present with LOAD *\n    assertThat(firstResult.hasProperty(\"image_embedding\"), equalTo(true));\n\n    // Score fields should be present\n    assertThat(firstResult.hasProperty(\"text_score\"), equalTo(true));\n  }\n\n  @Test\n  public void testLoadWithGroupBy() {\n    // Test LOAD behavior with GROUPBY - loaded fields should be available for grouping\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .load(\"brand\", \"price\", \"category\").groupBy(new Group(\"@brand\")\n            .reduce(Reducers.count().as(\"count\")).reduce(Reducers.avg(\"@price\").as(\"avg_price\")))\n        .build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"@category:{electronics}\")\n            .scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(10)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Field count and content assertions\n    Document firstResult = reply.getDocuments().get(0);\n\n    // Grouping field should be present\n    assertThat(firstResult.hasProperty(\"brand\"), equalTo(true));\n\n    // Reducer results should be present\n    assertThat(firstResult.hasProperty(\"count\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"avg_price\"), equalTo(true));\n\n    // Original loaded fields (price, category) should NOT be present after GROUPBY\n    // GROUPBY transforms the results, only group keys and reducers remain\n    assertThat(firstResult.hasProperty(\"price\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"category\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"title\"), equalTo(false));\n  }\n\n  @Test\n  public void testLoadWithApply() {\n    // Test LOAD with APPLY - loaded fields should be available for expressions\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .load(\"price\", \"rating\")\n        // with alias\n        .apply(Apply.of(\"@price * @rating\", \"value_score\"))\n        // without alias returns field name as the expression itself\n        .apply(Apply.of(\"@price * 0.9\")).build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"smartphone\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(10)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5).window(25)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Field count and content assertions\n    Document firstResult = reply.getDocuments().get(0);\n\n    // Loaded fields should be present\n    assertThat(firstResult.hasProperty(\"price\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"rating\"), equalTo(true));\n\n    // Computed fields (from APPLY) should be present\n    assertThat(firstResult.hasProperty(\"value_score\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"@price * 0.9\"), equalTo(true));\n\n    // Non-loaded document fields should NOT be present\n    assertThat(firstResult.hasProperty(\"title\"), equalTo(false));\n  }\n\n  @Test\n  public void testLoadWithFilter() {\n    // Test LOAD with FILTER - loaded fields should be available for filtering\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .load(\"price\", \"brand\", \"title\").filter(Filter.of(\"@price > 700\"))\n        .sortBy(SortedField.asc(\"@price\")).build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"@category:{electronics}\")\n            .scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(10)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Verify loaded fields are present and FILTER condition is applied\n    for (Document result : reply.getDocuments()) {\n      // Loaded fields should be present\n      assertThat(result.hasProperty(\"price\"), equalTo(true));\n      assertThat(result.hasProperty(\"brand\"), equalTo(true));\n      assertThat(result.hasProperty(\"title\"), equalTo(true));\n\n      // FILTER condition: all results should have price > 700\n      double price = Double.parseDouble(result.getString(\"price\"));\n      assertThat(price, greaterThan(700.0));\n\n      // Non-loaded document fields should NOT be present\n      assertThat(result.hasProperty(\"category\"), equalTo(false));\n      assertThat(result.hasProperty(\"rating\"), equalTo(false));\n      assertThat(result.hasProperty(\"image_embedding\"), equalTo(false));\n    }\n  }\n\n  @Test\n  public void testLoadNoFields() {\n    // Test without LOAD - should return only document IDs and scores\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .limit(Limit.of(0, 5)).build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"smartphone\").scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(5)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Without LOAD, results should contain only keys and scores, NO document fields\n    Document firstResult = reply.getDocuments().get(0);\n\n    // Document id and score should be present (extracted from __key and __score)\n    assertThat(firstResult.getId(), notNullValue());\n    assertThat(firstResult.getScore(), notNullValue());\n\n    // Document fields should NOT be present when no LOAD is specified\n    assertThat(firstResult.hasProperty(\"title\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"category\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"brand\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"price\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"rating\"), equalTo(false));\n    assertThat(firstResult.hasProperty(\"image_embedding\"), equalTo(false));\n  }\n\n  @Test\n  public void testNoSort() {\n    // Test NOSORT - disables default sorting by score\n    FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder()\n        .load(\"title\", \"price\").noSort().limit(Limit.of(0, 10)).build();\n\n    FTHybridParams hybridArgs = FTHybridParams.builder()\n        .search(FTHybridSearchParams.builder().query(\"@category:{electronics}\")\n            .scoreAlias(\"text_score\").build())\n        .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n            .method(FTHybridVectorParams.Knn.of(10)).scoreAlias(\"vector_score\").build())\n        .combine(Combiners.linear().alpha(0.5).beta(0.5)).postProcessing(postProcessing)\n        .param(\"vector\", queryVector).build();\n\n    HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n    assertThat(reply, notNullValue());\n    assertThat(reply.getDocuments(), not(empty()));\n\n    // Result count assertions\n    assertThat(reply.getTotalResults(), greaterThan(0L));\n    assertThat(reply.getDocuments().size(), greaterThan(0));\n\n    // Loaded fields should be present\n    Document firstResult = reply.getDocuments().get(0);\n    assertThat(firstResult.hasProperty(\"title\"), equalTo(true));\n    assertThat(firstResult.hasProperty(\"price\"), equalTo(true));\n  }\n\n  // ========== Scorer Tests ==========\n\n  /**\n   * Nested test class to verify all supported scorers work correctly with FT.HYBRID command. Tests\n   * each scorer from {@link Scorers} to ensure proper integration.\n   */\n  @Nested\n  @Tag(\"integration\")\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  @SinceRedisVersion(\"8.4.0\")\n  class SupportedScorersTest {\n\n    /**\n     * Provides scorer instances and their expected scores for parameterized testing. Sccore values\n     * might differ between cluster and standalone modes. To perform basic verification use same\n     * values for both cluster/standalone with tolerance.\n     * @return Stream of Arguments containing (Scorer, expectedScore, tolerance)\n     */\n    Stream<Arguments> scorerProvider() {\n      return Stream.of(Arguments.of(Scorers.tfidf(), 2.5, 0.5),\n        Arguments.of(Scorers.tfidfDocnorm(), 0.2, 0.5), Arguments.of(Scorers.bm25stdNorm(), 1, 0.5),\n        Arguments.of(Scorers.bm25std(), 1.3, 0.5), Arguments.of(Scorers.dismax(), 1.0, 0.5),\n        Arguments.of(Scorers.docscore(), 1.0, 0.5), Arguments.of(Scorers.hamming(), 0.0, 0.5));\n    }\n\n    @ParameterizedTest(name = \"{index}: {0}\")\n    @MethodSource(\"scorerProvider\")\n    public void testScorer(Scorer scorer, double expectedScore, double tolerance) {\n      // Create hybrid search with the provided scorer\n      FTHybridParams hybridArgs = FTHybridParams.builder()\n          .search(FTHybridSearchParams.builder().query(\"@id:1\").scorer(scorer)\n              .scoreAlias(\"text_score\").build())\n          .vectorSearch(FTHybridVectorParams.builder().field(\"@image_embedding\").vector(\"vector\")\n              .filter(\"@id:1\").method(FTHybridVectorParams.Knn.of(5)).scoreAlias(\"vector_score\")\n              .build())\n          .param(\"vector\", queryVector).build();\n\n      // Execute hybrid search\n      HybridResult result = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n      // Verify results are returned\n      assertThat(result, notNullValue());\n      assertThat(result.getTotalResults(), equalTo(1L));\n\n      // Verify scorer is working - text_score should be present\n      Document firstDoc = result.getDocuments().get(0);\n      assertThat(firstDoc.hasProperty(\"text_score\"), equalTo(true));\n\n      // Verify score is valid and within expected range\n      double scoreValue = Double.parseDouble(firstDoc.getString(\"text_score\"));\n      assertThat(scoreValue, closeTo(expectedScore, tolerance));\n    }\n  }\n\n  // ========== HybridResult Population Tests ==========\n\n  /**\n   * Verify that HybridResult is properly populated from FT.HYBRID command responses. Tests all\n   * HybridResult fields and Document structure in various scenarios.\n   */\n  @Nested\n  @Tag(\"integration\")\n  @SinceRedisVersion(\"8.4.0\")\n  class HybridResultPopulationTest {\n\n    /**\n     * Verify : This command will only return document IDs (keyid) and scores to which the user has\n     * read access. To retrieve entire documents, use projections with LOAD * or LOAD <count> field\n     */\n    @Test\n    public void verifyHybridResultBasicFieldsNoLoad() {\n      FTHybridParams hybridArgs = FTHybridParams.builder()\n          .search(FTHybridSearchParams.builder().query(\"@id:1\").build())\n          .vectorSearch(FTHybridVectorParams.builder().filter(\"@id:1\").field(\"@image_embedding\")\n              .vector(\"vector\").method(FTHybridVectorParams.Knn.of(5)).build())\n          .param(\"vector\", queryVector).build();\n\n      HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n      // Verify HybridResult is not null\n      assertThat(reply, notNullValue());\n\n      // Verify totalResults is populated and > 0\n      assertThat(reply.getTotalResults(), equalTo(1L));\n\n      // Verify executionTime is populated and reasonable\n      assertThat(reply.getExecutionTime(), greaterThan(0.0));\n\n      // Verify documents list is populated\n      assertThat(reply.getDocuments(), notNullValue());\n      assertThat(reply.getDocuments(), not(empty()));\n\n      Document doc = reply.getDocuments().get(0);\n      assertThat(doc.getId(), equalTo(\"product:hybrid:1\"));\n      assertThat(doc.hasProperty(\"title\"), equalTo(false));\n      assertThat(doc.getScore(), closeTo(0.03, 0.01));\n\n      // Verify warnings list is not null (may be empty)\n      assertThat(reply.getWarnings(), notNullValue());\n      assertThat(reply.getWarnings(), empty());\n    }\n\n    /**\n     * Verify : This command will only return document IDs (keyid) and scores to which the user has\n     * read access. To retrieve entire documents, use projections with LOAD * or LOAD <count> field\n     */\n    @Test\n    @SinceRedisVersion(\"8.6.0\")\n    public void verifyHybridResultBasicFieldsWithLoadAll() {\n      // Execute a simple hybrid search with known results\n      FTHybridPostProcessingParams postProcessing = FTHybridPostProcessingParams.builder().loadAll()\n          .build();\n\n      FTHybridParams hybridArgs = FTHybridParams.builder()\n          .search(FTHybridSearchParams.builder().query(\"@id:1\").build())\n          .vectorSearch(FTHybridVectorParams.builder().filter(\"@id:1\").field(\"@image_embedding\")\n              .vector(\"vector\").method(FTHybridVectorParams.Knn.of(5)).build())\n          .combine(Combiners.linear().alpha(1).beta(0)).postProcessing(postProcessing)\n          .param(\"vector\", queryVector).build();\n\n      HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n      // Verify HybridResult is not null\n      assertThat(reply, notNullValue());\n\n      // Verify totalResults is populated and > 0\n      assertThat(reply.getTotalResults(), equalTo(1L));\n\n      // Verify executionTime is populated and reasonable\n      assertThat(reply.getExecutionTime(), greaterThan(0.0));\n\n      // Verify documents list is populated\n      assertThat(reply.getDocuments(), notNullValue());\n      assertThat(reply.getDocuments(), not(empty()));\n\n      Document doc = reply.getDocuments().get(0);\n      assertThat(doc.getId(), nullValue());\n      assertThat(doc.hasProperty(\"title\"), equalTo(true));\n      assertThat(doc.get(\"title\"), equalTo(\"Apple iPhone 15 Pro smartphone with advanced camera\"));\n      assertThat(doc.getScore(), closeTo(0.5, 0.5));\n\n      // Verify warnings list is not null (may be empty)\n      assertThat(reply.getWarnings(), notNullValue());\n      assertThat(reply.getWarnings(), empty());\n    }\n\n    @Test\n    public void verifyHybridResultWithEmptyResults() {\n      // Execute hybrid search with filter that matches no documents\n      FTHybridParams hybridArgs = FTHybridParams.builder()\n          .search(FTHybridSearchParams.builder().query(\"@category:{nonexistent}\").build())\n          .vectorSearch(\n            FTHybridVectorParams.builder().filter(\"@id:nonexistent\").field(\"@image_embedding\")\n                .vector(\"vector\").method(FTHybridVectorParams.Knn.of(5)).build())\n          .param(\"vector\", queryVector).build();\n\n      HybridResult reply = jedis.ftHybrid(INDEX_NAME, hybridArgs);\n\n      // Verify HybridResult is not null even with empty results\n      assertThat(reply, notNullValue());\n\n      // Verify totalResults is 0\n      assertThat(reply.getTotalResults(), equalTo(0L));\n\n      // Verify documents list is empty\n      assertThat(reply.getDocuments(), notNullValue());\n      assertThat(reply.getDocuments(), empty());\n\n      // Verify executionTime is still populated (>= 0.0)\n      assertThat(reply.getExecutionTime(), greaterThanOrEqualTo(0.0));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/commands/unified/sentinel/SentinelAllKindOfValuesCommandsIT.java",
    "content": "package redis.clients.jedis.commands.unified.sentinel;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.commands.unified.AllKindOfValuesCommandsTestBase;\nimport redis.clients.jedis.util.EnabledOnCommandCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class SentinelAllKindOfValuesCommandsIT extends AllKindOfValuesCommandsTestBase {\n\n  static HostAndPort sentinel1;\n\n  static HostAndPort sentinel2;\n\n  static Set<HostAndPort> sentinels;\n\n  static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder().build();\n\n  static EndpointConfig primary;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone2-primary\"));\n\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone2-primary\"));\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    sentinel1 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort();\n    sentinel2 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-3\").getHostAndPort();\n    sentinels = new HashSet<>(Arrays.asList(sentinel1, sentinel2));\n    primary = Endpoints.getRedisEndpoint(\"standalone2-primary\");\n  }\n\n  public SentinelAllKindOfValuesCommandsIT(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  protected UnifiedJedis createTestClient() {\n\n    return RedisSentinelClient.builder()\n        .clientConfig(primary.getClientConfigBuilder().protocol(protocol).build())\n        .sentinels(sentinels).sentinelClientConfig(sentinelClientConfig).masterName(\"mymaster\")\n        .build();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport static java.util.Collections.singleton;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.csc.util.AllowAndDenyListWithStringKeys;\n\npublic class AllowAndDenyListCacheableTest extends ClientSideCacheTestBase {\n\n  private static CacheConfig createConfig(Cacheable cacheable) {\n    return CacheConfig.builder().cacheable(cacheable).cacheClass(TestCache.class).build();\n  }\n\n  @Test\n  public void none() {\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(createConfig(new AllowAndDenyListWithStringKeys(null, null, null, null)))\n        .poolConfig(singleConnectionPoolConfig.get())\n        .build()) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getSize());\n    }\n  }\n\n  @Test\n  public void whiteListCommand() {\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(createConfig(new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)))\n        .poolConfig(singleConnectionPoolConfig.get())\n        .build()) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getSize());\n    }\n  }\n\n  @Test\n  public void blackListCommand() {\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(createConfig(new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)))\n        .poolConfig(singleConnectionPoolConfig.get())\n        .build()) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(0, cache.getSize());\n    }\n  }\n\n  @Test\n  public void whiteListKey() {\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(createConfig(new AllowAndDenyListWithStringKeys(null, null, singleton(\"foo\"), null)))\n        .poolConfig(singleConnectionPoolConfig.get())\n        .build()) {\n      control.set(\"foo\", \"bar\");\n      Cache cache = jedis.getCache();\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getSize());\n    }\n  }\n\n  @Test\n  public void blackListKey() {\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(createConfig(new AllowAndDenyListWithStringKeys(null, null, null, singleton(\"foo\"))))\n        .poolConfig(singleConnectionPoolConfig.get())\n        .build()) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(0, cache.getSize());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.aMapWithSize;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.*;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\nimport redis.clients.jedis.CommandObjects;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.util.TestEnvUtil;\n\npublic class ClientSideCacheFunctionalityTest extends ClientSideCacheTestBase {\n\n  @Test // T.5.1\n  public void flushAllTest() {\n    final int count = 100;\n    for (int i = 0; i < count; i++) {\n      control.set(\"k\" + i, \"v\" + i);\n    }\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      for (int i = 0; i < count; i++) {\n        jedis.get(\"k\" + i);\n      }\n\n      assertEquals(count, cache.getSize());\n      cache.flush();\n      assertEquals(0, cache.getSize());\n    }\n  }\n\n  @Test // T.4.1\n  public void lruEvictionTest() {\n    final int count = 100;\n    final int extra = 10;\n\n    // Add 100 + 10 keys to Redis\n    for (int i = 0; i < count + extra; i++) {\n      control.set(\"key:\" + i, \"value\" + i);\n    }\n\n    Map<CacheKey, CacheEntry> map = new LinkedHashMap<>(count);\n    Cache cache = new DefaultCache(count, map);\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cache(cache)\n        .build()) {\n\n      // Retrieve the 100 keys in the same order\n      for (int i = 0; i < count; i++) {\n        jedis.get(\"key:\" + i);\n      }\n      assertThat(map, aMapWithSize(count));\n\n      List<CacheKey> earlierKeys = new ArrayList<>(map.keySet()).subList(0, extra);\n      // earlier keys in map\n      earlierKeys.forEach(cacheKey -> assertThat(map, Matchers.hasKey(cacheKey)));\n\n      // Retrieve the 10 extra keys\n      for (int i = count; i < count + extra; i++) {\n        jedis.get(\"key:\" + i);\n      }\n\n      // earlier keys NOT in map\n      earlierKeys.forEach(cacheKey -> assertThat(map, Matchers.not(Matchers.hasKey(cacheKey))));\n      assertThat(map, aMapWithSize(count));\n    }\n  }\n\n  @Test // T.5.2\n  public void deleteByKeyUsingMGetTest() {\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      Cache clientSideCache = jedis.getCache();\n\n      jedis.set(\"1\", \"one\");\n      jedis.set(\"2\", \"two\");\n\n      assertEquals(Arrays.asList(\"one\", \"two\"), jedis.mget(\"1\", \"2\"));\n      assertEquals(1, clientSideCache.getSize());\n\n      assertThat(clientSideCache.deleteByRedisKey(\"1\"), hasSize(1));\n      assertEquals(0, clientSideCache.getSize());\n    }\n  }\n\n  @Test // T.5.2\n  public void deleteByKeyTest() {\n    final int count = 100;\n    for (int i = 0; i < count; i++) {\n      control.set(\"k\" + i, \"v\" + i);\n    }\n\n    // By using LinkedHashMap, we can get the hashes (map keys) at the same order of the actual keys.\n    LinkedHashMap<CacheKey, CacheEntry> map = new LinkedHashMap<>();\n    Cache clientSideCache = new TestCache(map);\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cache(clientSideCache)\n        .build()) {\n      for (int i = 0; i < count; i++) {\n        jedis.get(\"k\" + i);\n      }\n      assertThat(map, aMapWithSize(count));\n\n      ArrayList<CacheKey> cacheKeys = new ArrayList<>(map.keySet());\n      for (int i = 0; i < count; i++) {\n        String key = \"k\" + i;\n        CacheKey cacheKey = cacheKeys.get(i);\n        assertTrue(map.containsKey(cacheKey));\n        assertThat(clientSideCache.deleteByRedisKey(key), hasSize(1));\n        assertFalse(map.containsKey(cacheKey));\n        assertThat(map, aMapWithSize(count - i - 1));\n      }\n      assertThat(map, aMapWithSize(0));\n    }\n  }\n\n  @Test // T.5.2\n  public void deleteByKeysTest() {\n    final int count = 100;\n    final int delete = 10;\n\n    for (int i = 0; i < count; i++) {\n      control.set(\"k\" + i, \"v\" + i);\n    }\n\n    // By using LinkedHashMap, we can get the hashes (map keys) at the same order of the actual keys.\n    LinkedHashMap<CacheKey, CacheEntry> map = new LinkedHashMap<>();\n    Cache clientSideCache = new TestCache(map);\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cache(clientSideCache)\n        .build()) {\n      for (int i = 0; i < count; i++) {\n        jedis.get(\"k\" + i);\n      }\n      assertThat(map, aMapWithSize(count));\n\n      List<String> keysToDelete = new ArrayList<>(delete);\n      for (int i = 0; i < delete; i++) {\n        String key = \"k\" + i;\n        keysToDelete.add(key);\n      }\n      assertThat(clientSideCache.deleteByRedisKeys(keysToDelete), hasSize(delete));\n      assertThat(map, aMapWithSize(count - delete));\n    }\n  }\n\n  @Test // T.5.3\n  public void deleteByEntryTest() {\n    final int count = 100;\n    for (int i = 0; i < count; i++) {\n      control.set(\"k\" + i, \"v\" + i);\n    }\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      for (int i = 0; i < count; i++) {\n        jedis.get(\"k\" + i);\n      }\n      assertEquals(count, cache.getSize());\n\n      List<CacheEntry> cacheKeys = new ArrayList<>(cache.getCacheEntries());\n      for (int i = 0; i < count; i++) {\n        CacheKey cacheKey = cacheKeys.get(i).getCacheKey();\n        assertTrue(cache.delete(cacheKey));\n        assertFalse(cache.hasCacheKey(cacheKey));\n        assertEquals(count - i - 1, cache.getSize());\n      }\n    }\n  }\n\n  @Test // T.5.3\n  public void deleteByEntriesTest() {\n    final int count = 100;\n    final int delete = 10;\n    for (int i = 0; i < count; i++) {\n      control.set(\"k\" + i, \"v\" + i);\n    }\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      for (int i = 0; i < count; i++) {\n        jedis.get(\"k\" + i);\n      }\n      assertEquals(count, cache.getSize());\n\n      List<CacheKey> cacheKeysToDelete = new ArrayList<>(cache.getCacheEntries()).subList(0, delete).stream().map(e -> e.getCacheKey())\n          .collect(Collectors.toList());\n      List<Boolean> isDeleted = cache.delete(cacheKeysToDelete);\n      assertThat(isDeleted, hasSize(delete));\n      isDeleted.forEach(Assertions::assertTrue);\n      assertEquals(count - delete, cache.getSize());\n    }\n  }\n\n  @Test\n  public void multiKeyOperation() {\n    control.set(\"k1\", \"v1\");\n    control.set(\"k2\", \"v2\");\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      jedis.mget(\"k1\", \"k2\");\n      assertEquals(1, jedis.getCache().getSize());\n    }\n  }\n\n  @Test\n  public void maximumSizeExact() {\n    control.set(\"k1\", \"v1\");\n    control.set(\"k2\", \"v2\");\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().maxSize(1).build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      assertEquals(0, cache.getSize());\n      jedis.get(\"k1\");\n      assertEquals(1, cache.getSize());\n      assertEquals(0, cache.getStats().getEvictCount());\n      jedis.get(\"k2\");\n      assertEquals(1, cache.getSize());\n      assertEquals(1, cache.getStats().getEvictCount());\n    }\n  }\n\n  @Test\n  public void testInvalidationWithUnifiedJedis() {\n    Cache cache = new TestCache();\n    Cache mock = Mockito.spy(cache);\n    UnifiedJedis client = new UnifiedJedis(hnp, clientConfig.get(), mock);\n    UnifiedJedis controlClient = new UnifiedJedis(hnp, clientConfig.get());\n\n    try {\n      // \"foo\" is cached\n      client.set(\"foo\", \"bar\");\n      client.get(\"foo\"); // read from the server\n      assertEquals(\"bar\", client.get(\"foo\")); // cache hit\n\n      // Using another connection\n      controlClient.set(\"foo\", \"bar2\");\n      assertEquals(\"bar2\", controlClient.get(\"foo\"));\n\n      //invalidating the cache and read it back from server\n      assertEquals(\"bar2\", client.get(\"foo\"));\n\n      Mockito.verify(mock, Mockito.times(1)).deleteByRedisKeys(Mockito.anyList());\n      Mockito.verify(mock, Mockito.times(2)).set(Mockito.any(CacheKey.class), Mockito.any(CacheEntry.class));\n    } finally {\n      client.close();\n      controlClient.close();\n    }\n  }\n\n  @Test\n  public void differentInstanceOnEachCacheHit() {\n\n    // fill the cache for maxSize\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      jedis.sadd(\"foo\", \"a\");\n      jedis.sadd(\"foo\", \"b\");\n\n      Set<String> expected = new HashSet<>();\n      expected.add(\"a\");\n      expected.add(\"b\");\n\n      Set<String> members1 = jedis.smembers(\"foo\");\n      Set<String> members2 = jedis.smembers(\"foo\");\n\n      Set<String> fromMap = (Set<String>) cache.get(new CacheKey<>(new CommandObjects().smembers(\"foo\"))).getValue();\n      assertEquals(expected, members1);\n      assertEquals(expected, members2);\n      assertEquals(expected, fromMap);\n      assertTrue(members1 != members2);\n      assertTrue(members1 != fromMap);\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testSequentialAccess() throws InterruptedException {\n    int threadCount = 10;\n    int iterations = 10000;\n\n    control.set(\"foo\", \"0\");\n\n    ReentrantLock lock = new ReentrantLock(true);\n    ExecutorService executorService = Executors.newFixedThreadPool(threadCount);\n\n    CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build();\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(clientConfig.get())\n        .cacheConfig(cacheConfig)\n        .build()) {\n      // Submit multiple threads to perform concurrent operations\n      CountDownLatch latch = new CountDownLatch(threadCount);\n      for (int i = 0; i < threadCount; i++) {\n        executorService.submit(() -> {\n          try {\n            for (int j = 0; j < iterations; j++) {\n              lock.lock();\n              try {\n                // Simulate continious get and update operations and consume invalidation events meanwhile\n                assertEquals(control.get(\"foo\"), jedis.get(\"foo\"));\n                Integer value = new Integer(jedis.get(\"foo\"));\n                assertEquals(\"OK\", jedis.set(\"foo\", (++value).toString()));\n              } finally {\n                lock.unlock();\n              }\n            }\n          } finally {\n            latch.countDown();\n          }\n        });\n      }\n\n      // wait for all threads to complete\n      latch.await();\n    }\n\n    executorService.shutdownNow();\n\n    // Verify the final value of \"foo\" in Redis\n    String finalValue = control.get(\"foo\");\n    assertEquals(threadCount * iterations, Integer.parseInt(finalValue));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testConcurrentAccessWithStats() throws InterruptedException {\n    int threadCount = 10;\n    int iterations = 10000;\n\n    control.set(\"foo\", \"0\");\n\n    ExecutorService executorService = Executors.newFixedThreadPool(threadCount);\n\n    // Create the shared mock instance of cache\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      // Submit multiple threads to perform concurrent operations\n      CountDownLatch latch = new CountDownLatch(threadCount);\n      for (int i = 0; i < threadCount; i++) {\n        executorService.submit(() -> {\n          try {\n            for (int j = 0; j < iterations; j++) {\n              // Simulate continious get and update operations and consume invalidation events meanwhile\n              Integer value = new Integer(jedis.get(\"foo\")) + 1;\n              assertEquals(\"OK\", jedis.set(\"foo\", value.toString()));\n            }\n          } finally {\n            latch.countDown();\n          }\n        });\n      }\n\n      // wait for all threads to complete\n      latch.await();\n\n      executorService.shutdownNow();\n\n      CacheStats stats = cache.getStats();\n      assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount());\n      assertEquals(stats.getMissCount(), stats.getLoadCount());\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testMaxSize() throws InterruptedException {\n    int threadCount = 10;\n    int iterations = 11000;\n    int maxSize = 1000;\n\n    ExecutorService executorService = Executors.newFixedThreadPool(threadCount);\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().maxSize(maxSize).build())\n        .build()) {\n      Cache testCache = jedis.getCache();\n      // Submit multiple threads to perform concurrent operations\n      CountDownLatch latch = new CountDownLatch(threadCount);\n      for (int i = 0; i < threadCount; i++) {\n        executorService.submit(() -> {\n          try {\n            for (int j = 0; j < iterations; j++) {\n              // Simulate continious get and update operations and consume invalidation events meanwhile\n              assertEquals(\"OK\", jedis.set(\"foo\" + j, \"foo\" + j));\n              jedis.get(\"foo\" + j);\n            }\n          } finally {\n            latch.countDown();\n          }\n        });\n      }\n\n      // wait for all threads to complete\n      latch.await();\n\n      executorService.shutdownNow();\n\n      CacheStats stats = testCache.getStats();\n\n      assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount());\n      assertEquals(stats.getMissCount(), stats.getLoadCount());\n      assertEquals(threadCount * iterations, stats.getNonCacheableCount());\n      assertTrue(maxSize >= testCache.getSize());\n    }\n  }\n\n  @Test\n  public void testEvictionPolicy() throws InterruptedException {\n    int maxSize = 100;\n    int expectedEvictions = 20;\n    int touchOffset = 10;\n\n    // fill the cache for maxSize\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().maxSize(maxSize).build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      for (int i = 0; i < maxSize; i++) {\n        jedis.set(\"foo\" + i, \"bar\" + i);\n        assertEquals(\"bar\" + i, jedis.get(\"foo\" + i));\n      }\n\n      // touch a set of keys to prevent from eviction from index 10 to 29\n      for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) {\n        assertEquals(\"bar\" + i, jedis.get(\"foo\" + i));\n      }\n\n      // add more keys to trigger eviction, adding from 100 to 119\n      for (int i = maxSize; i < maxSize + expectedEvictions; i++) {\n        jedis.set(\"foo\" + i, \"bar\" + i);\n        assertEquals(\"bar\" + i, jedis.get(\"foo\" + i));\n      }\n\n      // check touched keys not evicted\n      for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) {\n        assertTrue(cache.hasCacheKey(new CacheKey(new CommandObjects().get(\"foo\" + i))));\n      }\n\n      // check expected evictions are done till the offset\n      for (int i = 0; i < touchOffset; i++) {\n        assertTrue(!cache.hasCacheKey(new CacheKey(new CommandObjects().get(\"foo\" + i))));\n      }\n\n      // check expected evictions are done after the touched keys\n      for (int i = touchOffset + expectedEvictions; i < (2 * expectedEvictions); i++) {\n        assertFalse(cache.hasCacheKey(new CacheKey(new CommandObjects().get(\"foo\" + i))));\n      }\n\n      assertEquals(maxSize, cache.getSize());\n    }\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testEvictionPolicyMultithreaded() throws InterruptedException {\n    int NUMBER_OF_THREADS = 100;\n    int TOTAL_OPERATIONS = 1000000;\n    int NUMBER_OF_DISTINCT_KEYS = 53;\n    int MAX_SIZE = 20;\n    List<Exception> exceptions = new ArrayList<>();\n\n    List<Thread> tds = new ArrayList<>();\n    final AtomicInteger ind = new AtomicInteger();\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().maxSize(MAX_SIZE).build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      for (int i = 0; i < NUMBER_OF_THREADS; i++) {\n        Thread hj = new Thread(new Runnable() {\n          @Override\n          public void run() {\n            for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) {\n              try {\n                final String key = \"foo\" + i % NUMBER_OF_DISTINCT_KEYS;\n                if (i < NUMBER_OF_DISTINCT_KEYS) {\n                  jedis.set(key, key);\n                }\n                jedis.get(key);\n              } catch (Exception e) {\n                exceptions.add(e);\n                throw e;\n              }\n            }\n          }\n        });\n        tds.add(hj);\n        hj.start();\n      }\n\n      for (Thread t : tds) {\n        t.join();\n      }\n\n      assertEquals(MAX_SIZE, cache.getSize());\n      assertEquals(0, exceptions.size());\n    }\n  }\n\n  @Test\n  public void testNullValue() throws InterruptedException {\n    int MAX_SIZE = 20;\n    String nonExisting = \"non-existing-key-\"+ UUID.randomUUID().toString();\n    control.del(nonExisting);\n\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().maxSize(MAX_SIZE).build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      CacheStats stats = cache.getStats();\n\n      String val = jedis.get(nonExisting);\n      assertNull(val);\n      assertEquals(1, cache.getSize());\n      assertEquals(0, stats.getHitCount());\n      assertEquals(1, stats.getMissCount());\n\n      val = jedis.get(nonExisting);\n      assertNull(val);\n      assertEquals(1, cache.getSize());\n      assertNull(cache.getCacheEntries().iterator().next().getValue());\n      assertEquals(1, stats.getHitCount());\n      assertEquals(1, stats.getMissCount());\n\n      control.set(nonExisting, \"bar\");\n      await()\n              .atMost(5, TimeUnit.SECONDS)\n              .pollInterval(10, TimeUnit.MILLISECONDS)\n              .untilAsserted(() -> assertEquals(\"bar\", jedis.get(nonExisting)));\n      assertEquals(1, cache.getSize());\n      assertEquals(\"bar\", cache.getCacheEntries().iterator().next().getValue());\n      assertEquals(1, stats.getHitCount());\n      assertEquals(2, stats.getMissCount());\n    }\n  }\n\n  @Test\n  public void testCacheFactory() throws InterruptedException {\n    // this checks the instantiation with parameters (int, EvictionPolicy, Cacheable)\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().cacheClass(TestCache.class).build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      CacheStats stats = cache.getStats();\n\n      String val = jedis.get(\"foo\");\n      val = jedis.get(\"foo\");\n      assertNull(val);\n      assertEquals(1, cache.getSize());\n      assertNull(cache.getCacheEntries().iterator().next().getValue());\n      assertEquals(1, stats.getHitCount());\n      assertEquals(1, stats.getMissCount());\n    }\n\n    // this checks the instantiation with parameters (int, EvictionPolicy)\n    try (RedisClient jedis = RedisClient.builder()\n        .hostAndPort(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(CacheConfig.builder().cacheClass(TestCache.class).cacheable(null).build())\n        .build()) {\n      Cache cache = jedis.getCache();\n      CacheStats stats = cache.getStats();\n\n      String val = jedis.get(\"foo\");\n      val = jedis.get(\"foo\");\n      assertNull(val);\n      assertEquals(1, cache.getSize());\n      assertNull(cache.getCacheEntries().iterator().next().getValue());\n      assertEquals(1, stats.getHitCount());\n      assertEquals(1, stats.getMissCount());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.function.Supplier;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@SinceRedisVersion(value = \"7.4.0\", message = \"Jedis client-side caching is only supported with Redis 7.4 or later.\")\n@Tag(\"integration\")\npublic abstract class ClientSideCacheTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  protected static HostAndPort hnp;\n\n  protected Jedis control;\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone1\"));\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone1\");\n    hnp = endpoint.getHostAndPort();\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    control = new Jedis(hnp, endpoint.getClientConfigBuilder().build());\n    control.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    control.close();\n  }\n\n  protected static final Supplier<JedisClientConfig> clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build();\n\n  protected static final Supplier<GenericObjectPoolConfig<Connection>> singleConnectionPoolConfig = () -> {\n    ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();\n    poolConfig.setMaxTotal(1);\n    return poolConfig;\n  };\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/RedisClientSideCacheTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.util.RedisVersionCondition;\n\n@SinceRedisVersion(value = \"7.4.0\", message = \"Jedis client-side caching is only supported with Redis 7.4 or later.\")\n@Tag(\"integration\")\npublic class RedisClientSideCacheTest extends RedisClientSideCacheTestBase {\n\n  @RegisterExtension\n  public static RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone0\"));\n\n  @BeforeAll\n  public static void prepare() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/RedisClientSideCacheTestBase.java",
    "content": "package redis.clients.jedis.csc;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.args.ClientType;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.params.ClientKillParams;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic abstract class RedisClientSideCacheTestBase extends UnifiedJedisClientSideCacheTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  @Override\n  protected RedisClient createRegularJedis() {\n    return RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(endpoint.getClientConfigBuilder().build())\n        .build();\n  }\n\n  @Override\n  protected RedisClient createCachedJedis(CacheConfig cacheConfig) {\n    return RedisClient.builder()\n        .hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(endpoint.getClientConfigBuilder().resp3().build())\n        .cacheConfig(cacheConfig)\n        .build();\n  }\n\n  @Test\n  public void clearIfOneDiesTest() {\n    try (RedisClient jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n      // Create 100 keys\n      for (int i = 0; i < 100; i++) {\n        jedis.set(\"key\" + i, \"value\" + i);\n      }\n      assertEquals(0, cache.getSize());\n\n      // Get 100 keys into the cache\n      for (int i = 0; i < 100; i++) {\n        jedis.get(\"key\" + i);\n      }\n      assertEquals(100, cache.getSize());\n\n      try (Jedis killer = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build())) {\n        killer.clientKill(ClientKillParams.clientKillParams().type(ClientType.NORMAL).skipMe(ClientKillParams.SkipMe.YES));\n      }\n\n      try {\n        jedis.get(\"foo\");\n      } catch (JedisConnectionException jce) {\n        // expected\n      }\n      assertEquals(0, cache.getSize());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/RedisClusterClientSideCacheTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@SinceRedisVersion(value = \"7.4.0\", message = \"Jedis client-side caching is only supported with Redis 7.4 or later.\")\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = false)\npublic class RedisClusterClientSideCacheTest extends UnifiedJedisClientSideCacheTestBase {\n\n  protected static EndpointConfig endpoint;\n\n  private static Set<HostAndPort> hnp;\n\n  private static final Supplier<JedisClientConfig> clientConfig\n      = () -> endpoint.getClientConfigBuilder().resp3().build();\n\n  private static final Supplier<GenericObjectPoolConfig<Connection>> singleConnectionPoolConfig\n      = () -> {\n        ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();\n        poolConfig.setMaxTotal(1);\n        return poolConfig;\n      };\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @RegisterExtension\n  public static RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"cluster-stable\"));\n\n  @BeforeAll\n  public static void prepare() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-stable\");\n    hnp = new HashSet<>(endpoint.getHostsAndPorts());\n  }\n\n  @Override\n  protected RedisClusterClient createRegularJedis() {\n    return RedisClusterClient.builder()\n        .nodes(hnp)\n        .clientConfig(clientConfig.get())\n        .build();\n  }\n\n  @Override\n  protected RedisClusterClient createCachedJedis(CacheConfig cacheConfig) {\n    return RedisClusterClient.builder()\n        .nodes(hnp)\n        .clientConfig(clientConfig.get())\n        .cacheConfig(cacheConfig)\n        .build();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/RedisSentinelClientSideCacheTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport io.redis.test.utils.RedisVersion;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.RedisVersionUtil;\n\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@Tag(\"integration\")\npublic class RedisSentinelClientSideCacheTest extends UnifiedJedisClientSideCacheTestBase {\n\n  private static final String MASTER_NAME = \"mymaster\";\n\n  protected static HostAndPort sentinel1;\n  protected static HostAndPort sentinel2;\n\n  private static Set<HostAndPort> sentinels;\n\n  private static final JedisClientConfig masterClientConfig = DefaultJedisClientConfig.builder()\n      .resp3().password(\"foobared\").build();\n\n  private static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder()\n      .resp3().build();\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    sentinel1 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort();\n    sentinel2 = Endpoints.getRedisEndpoint(\"sentinel-standalone2-3\").getHostAndPort();\n    sentinels = new HashSet<>(Arrays.asList(sentinel1, sentinel2));\n  }\n\n  @Override\n  protected RedisSentinelClient createRegularJedis() {\n    return RedisSentinelClient.builder().masterName(MASTER_NAME).clientConfig(masterClientConfig)\n        .sentinels(sentinels).sentinelClientConfig(sentinelClientConfig).build();\n  }\n\n  @Override\n  protected RedisSentinelClient createCachedJedis(CacheConfig cacheConfig) {\n    return RedisSentinelClient.builder().masterName(MASTER_NAME).clientConfig(masterClientConfig)\n        .sentinels(sentinels).sentinelClientConfig(sentinelClientConfig).cacheConfig(cacheConfig)\n        .build();\n  }\n\n  @BeforeAll\n  public static void prepare() {\n    try (\n        RedisSentinelClient sentinelClient = RedisSentinelClient.builder().masterName(MASTER_NAME)\n            .clientConfig(masterClientConfig).sentinels(sentinels)\n            .sentinelClientConfig(sentinelClientConfig).build();\n        Jedis master = new Jedis(sentinelClient.getCurrentMaster(), masterClientConfig)) {\n      assumeTrue(RedisVersionUtil.getRedisVersion(master).isGreaterThanOrEqualTo(RedisVersion.V7_4),\n        \"Jedis Client side caching is only supported with 'Redis 7.4' or later.\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/SSLRedisClientSideCacheTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport io.redis.test.utils.RedisVersion;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.TlsUtil;\n\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@Tag(\"integration\")\npublic class SSLRedisClientSideCacheTest extends RedisClientSideCacheTestBase {\n\n  private static final String trustStoreName = SSLRedisClientSideCacheTest.class.getSimpleName();\n\n  @BeforeAll\n  public static void prepare() {\n\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0-tls\");\n\n    List<Path> trustedCertLocation = Collections.singletonList(endpoint.getCertificatesLocation());\n    Path trustStorePath = TlsUtil.createAndSaveTestTruststore(trustStoreName, trustedCertLocation,\"changeit\");\n    TlsUtil.setCustomTrustStore(trustStorePath, \"changeit\");\n\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().build())) {\n      assumeTrue(RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4),\n          \"Jedis Client side caching is only supported with 'Redis 7.4' or later.\");\n    }\n  }\n\n  @AfterAll\n  public static void teardownTrustStore() {\n    TlsUtil.restoreOriginalTrustStore();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/TestCache.java",
    "content": "package redis.clients.jedis.csc;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TestCache extends DefaultCache {\n\n  public TestCache() {\n    this(new HashMap<CacheKey, CacheEntry>());\n  }\n\n  public TestCache(Map<CacheKey, CacheEntry> map) {\n    super(10000, map);\n  }\n\n  public TestCache(Map<CacheKey, CacheEntry> map, Cacheable cacheable) {\n    super(10000, map, cacheable, new LRUEviction(10000));\n  }\n\n  public TestCache(int maximumSize, EvictionPolicy evictionPolicy ) {\n    super(maximumSize, new HashMap<CacheKey, CacheEntry>(), DefaultCacheable.INSTANCE, evictionPolicy);\n  }\n\n  public TestCache(int maximumSize, EvictionPolicy evictionPolicy, Cacheable cacheable ) {\n    super(maximumSize, new HashMap<CacheKey, CacheEntry>(), cacheable, evictionPolicy);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java",
    "content": "package redis.clients.jedis.csc;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.UnifiedJedis;\n\npublic abstract class UnifiedJedisClientSideCacheTestBase {\n\n  protected UnifiedJedis control;\n\n  protected abstract UnifiedJedis createRegularJedis();\n\n  protected abstract UnifiedJedis createCachedJedis(CacheConfig cacheConfig);\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    control = createRegularJedis();\n    control.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    control.close();\n  }\n\n  @Test\n  public void simple() {\n    CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build();\n    try (UnifiedJedis jedis = createCachedJedis(cacheConfig)) {\n      control.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      control.del(\"foo\");\n      await().atMost(5, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n          .untilAsserted(() -> assertNull(jedis.get(\"foo\")));\n    }\n  }\n\n  @Test\n  public void simpleWithSimpleMap() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getSize());\n      control.del(\"foo\");\n      assertEquals(1, cache.getSize());\n      await().atMost(5, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n          .untilAsserted(() -> assertNull(jedis.get(\"foo\")));\n      assertEquals(1, cache.getSize());\n    }\n  }\n\n  @Test\n  public void flushAll() {\n    CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build();\n    try (UnifiedJedis jedis = createCachedJedis(cacheConfig)) {\n      control.set(\"foo\", \"bar\");\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      control.flushAll();\n      await().atMost(5, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n          .untilAsserted(() -> assertNull(jedis.get(\"foo\")));\n    }\n  }\n\n  @Test\n  public void flushAllWithSimpleMap() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getSize());\n      control.flushAll();\n      assertEquals(1, cache.getSize());\n      await().atMost(5, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n          .untilAsserted(() -> assertNull(jedis.get(\"foo\")));\n      assertEquals(1, cache.getSize());\n    }\n  }\n\n  @Test\n  public void cacheNotEmptyTest() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n      control.set(\"foo\", \"bar\");\n      assertEquals(0, cache.getSize());\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getSize());\n    }\n  }\n\n  @Test\n  public void cacheUsedTest() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n\n      control.set(\"foo\", \"bar\");\n\n      assertEquals(0, cache.getStats().getMissCount());\n      assertEquals(0, cache.getStats().getHitCount());\n\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getStats().getMissCount());\n      assertEquals(0, cache.getStats().getHitCount());\n\n      assertEquals(\"bar\", jedis.get(\"foo\"));\n      assertEquals(1, cache.getStats().getMissCount());\n      assertEquals(1, cache.getStats().getHitCount());\n    }\n  }\n\n  @Test\n  public void immutableCacheEntriesTest() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      jedis.set(\"{csc}a\", \"AA\");\n      jedis.set(\"{csc}b\", \"BB\");\n      jedis.set(\"{csc}c\", \"CC\");\n\n      List<String> expected = Arrays.asList(\"AA\", \"BB\", \"CC\");\n\n      List<String> reply1 = jedis.mget(\"{csc}a\", \"{csc}b\", \"{csc}c\");\n      List<String> reply2 = jedis.mget(\"{csc}a\", \"{csc}b\", \"{csc}c\");\n\n      assertEquals(expected, reply1);\n      assertEquals(expected, reply2);\n      assertEquals(reply1, reply2);\n      assertNotSame(reply1, reply2);\n    }\n  }\n\n  @Test\n  public void invalidationTest() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n      jedis.set(\"{csc}1\", \"one\");\n      jedis.set(\"{csc}2\", \"two\");\n      jedis.set(\"{csc}3\", \"three\");\n\n      assertEquals(0, cache.getSize());\n      assertEquals(0, cache.getStats().getInvalidationCount());\n\n      List<String> reply1 = jedis.mget(\"{csc}1\", \"{csc}2\", \"{csc}3\");\n      assertEquals(Arrays.asList(\"one\", \"two\", \"three\"), reply1);\n      assertEquals(1, cache.getSize());\n      assertEquals(0, cache.getStats().getInvalidationCount());\n\n      jedis.set(\"{csc}1\", \"new-one\");\n      List<String> reply2 = jedis.mget(\"{csc}1\", \"{csc}2\", \"{csc}3\");\n      assertEquals(Arrays.asList(\"new-one\", \"two\", \"three\"), reply2);\n\n      assertEquals(1, cache.getSize());\n      assertEquals(1, cache.getStats().getInvalidationCount());\n    }\n  }\n\n  @Test\n  public void getNumEntriesTest() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n\n      // Create 100 keys\n      for (int i = 0; i < 100; i++) {\n        jedis.set(\"key\" + i, \"value\" + i);\n      }\n      assertEquals(0, cache.getSize());\n\n      // Get 100 keys into the cache\n      for (int i = 0; i < 100; i++) {\n        jedis.get(\"key\" + i);\n      }\n      assertEquals(100, cache.getSize());\n    }\n  }\n\n  @Test\n  public void invalidationOnCacheHitTest() {\n    try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) {\n      Cache cache = jedis.getCache();\n      // Create 100 keys\n      for (int i = 0; i < 100; i++) {\n        jedis.set(\"key\" + i, \"value\" + i);\n      }\n      assertEquals(0, cache.getSize());\n\n      // Get 100 keys into the cache\n      for (int i = 0; i < 100; i++) {\n        jedis.get(\"key\" + i);\n      }\n      assertEquals(100, cache.getSize());\n\n      assertEquals(100, cache.getStats().getLoadCount());\n      assertEquals(0, cache.getStats().getInvalidationCount());\n\n      // Change 50 of the 100 keys\n      for (int i = 1; i < 100; i += 2) {\n        jedis.set(\"key\" + i, \"val\" + i);\n      }\n\n      assertEquals(100, cache.getStats().getLoadCount());\n      // invalidation count is anything between 0 and 50\n\n      // Get the 100 keys again\n      for (int i = 0; i < 100; i++) {\n        jedis.get(\"key\" + i);\n      }\n      assertEquals(100, cache.getSize());\n\n      assertEquals(150, cache.getStats().getLoadCount());\n      assertEquals(50, cache.getStats().getInvalidationCount());\n    }\n  }\n\n  @Test\n  public void simplePubsubWithClientCache() {\n    String test_channel = \"test_channel\";\n    String test_message = \"test message\";\n\n    UnifiedJedis publisher = createCachedJedis(CacheConfig.builder().build());\n    Runnable command = () -> publisher.publish(test_channel, test_message + System.currentTimeMillis());\n    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n    executor.scheduleAtFixedRate(command, 0, 100, java.util.concurrent.TimeUnit.MILLISECONDS);\n\n    List<String> receivedMessages = new ArrayList<>();\n    try (UnifiedJedis subscriber = createCachedJedis(CacheConfig.builder().build())) {\n      JedisPubSub jedisPubSub = new JedisPubSub() {\n        private int count = 0;\n\n        @Override\n        public void onMessage(String channel, String message) {\n          receivedMessages.add(message);\n          if (message.startsWith(test_message) && count++ > 1) {\n            this.unsubscribe(test_channel);\n          }\n        }\n      };\n      subscriber.subscribe(jedisPubSub, test_channel);\n    }\n\n    executor.shutdown();\n    publisher.close();\n\n    assertTrue(receivedMessages.size() > 1);\n    receivedMessages.forEach(message -> assertTrue(message.startsWith(test_message)));\n  }\n\n  @Test\n  public void advancedPubsubWithClientCache() {\n    String test_channel = \"test_channel\";\n    String test_message = \"test message\";\n    String test_key = \"test_key\";\n    String test_value = \"test_value\";\n\n    UnifiedJedis publisher = createCachedJedis(CacheConfig.builder().build());\n    Runnable command = () -> publisher.publish(test_channel, test_message + System.currentTimeMillis());\n    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n    executor.scheduleAtFixedRate(command, 0, 50, java.util.concurrent.TimeUnit.MILLISECONDS);\n\n    int iteration = 0;\n    int totalIteration = 10;\n    while (iteration++ < totalIteration) {\n\n      List<String> receivedMessages = new ArrayList<>();\n      try (UnifiedJedis subscriber = createCachedJedis(CacheConfig.builder().build())) {\n\n        subscriber.set(test_key, test_value);\n        assertEquals(test_value, subscriber.get(test_key));\n        JedisPubSub jedisPubSub = new JedisPubSub() {\n          private int count = 0;\n\n          @Override\n          public void onMessage(String channel, String message) {\n            receivedMessages.add(message);\n            if (message.startsWith(test_message) && count++ > 1) {\n              this.unsubscribe(test_channel);\n            }\n          }\n        };\n        subscriber.subscribe(jedisPubSub, test_channel);\n        subscriber.set(test_key, test_value);\n        assertEquals(test_value, subscriber.get(test_key));\n      }\n\n      assertTrue(receivedMessages.size() > 1);\n      receivedMessages.forEach(message -> assertTrue(message.startsWith(test_message)));\n    }\n\n    executor.shutdown();\n    publisher.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/csc/VersionTest.java",
    "content": "package redis.clients.jedis.csc;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n\npublic class VersionTest {\n\n    @Test\n    public void compareSameVersions() {\n        RedisVersion a = new RedisVersion(\"5.2.4\");\n        RedisVersion b = new RedisVersion(\"5.2.4\");\n        assertEquals(a, b);\n\n        RedisVersion c = new RedisVersion(\"5.2.0.0\");\n        RedisVersion d = new RedisVersion(\"5.2\");\n        assertEquals(a, b);\n    }\n\n    @Test\n    public void compareDifferentVersions() {\n        RedisVersion a = new RedisVersion(\"5.2.4\");\n        RedisVersion b = new RedisVersion(\"5.1.4\");\n        assertEquals(1, a.compareTo(b));\n\n        RedisVersion c = new RedisVersion(\"5.2.4\");\n        RedisVersion d = new RedisVersion(\"5.2.5\");\n        assertEquals(-1, c.compareTo(d));\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/examples/BroadcastCommandsToAllClusterNodes.java",
    "content": "package redis.clients.jedis.examples;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.RedisClusterClient;\n\n/**\n * When using the <a href=\"https://redis.io/docs/reference/cluster-spec/\">Open Source Redis Cluster\n * API</a>, some commands must be executed against all primary nodes. To simplify this task, Jedis\n * provides an easy way to broadcast commands.\n *\n * For example, to update the server configuration of all nodes in the Redis Cluster, we broadcast\n * the command [CONFIG SET](https://redis.io/commands/config-set/) to all nodes.\n */\npublic class BroadcastCommandsToAllClusterNodes {\n\n  public static void main(String[] args) {\n\n    HostAndPort clusterNode = new HostAndPort(\"127.0.0.1\", 7000);\n    RedisClusterClient client = RedisClusterClient.create(clusterNode);\n\n    String reply = client.configSet(\"maxmemory\", \"100mb\"); // reply is \"OK\"\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java",
    "content": "package redis.clients.jedis.examples;\n\nimport org.locationtech.jts.geom.Coordinate;\nimport org.locationtech.jts.geom.Geometry;\nimport org.locationtech.jts.geom.GeometryFactory;\nimport org.locationtech.jts.geom.Polygon;\nimport org.locationtech.jts.io.ParseException;\nimport org.locationtech.jts.io.WKTReader;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.search.FTSearchParams;\nimport redis.clients.jedis.search.SearchResult;\nimport redis.clients.jedis.search.schemafields.GeoShapeField;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * As of RediSearch 2.8.4, advanced GEO querying with GEOSHAPE fields is supported.\n * <p>\n * Notes:\n * <ul>\n *   <li>As of RediSearch 2.8.4, only POLYGON and POINT objects are supported.</li>\n *   <li>As of RediSearch 2.8.4, only WITHIN and CONTAINS conditions are supported.</li>\n *   <li>As of RedisStack 7.4.0, support for INTERSECTS and DISJOINT conditions are added.</li>\n * </ul>\n *\n * Any object/library producing a <a href=\"https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry\">\n * well-known text (WKT)</a> in {@code toString()} method can be used.\n *\n * This example uses the <a href=\"https://github.com/locationtech/jts\">JTS</a> library.\n * <pre>\n * {@code\n * <dependency>\n *   <groupId>org.locationtech.jts</groupId>\n *   <artifactId>jts-core</artifactId>\n *   <version>1.19.0</version>\n * </dependency>\n * }\n * </pre>\n */\npublic class GeoShapeFieldsUsageInRediSearch {\n\n  public static void main(String[] args) {\n\n    // We'll create geometry objects with GeometryFactory\n    final GeometryFactory factory = new GeometryFactory();\n\n    final String host = \"localhost\";\n    final int port = 6379;\n    final HostAndPort address = new HostAndPort(host, port);\n\n    RedisClient client = RedisClient.create(address);\n    // client.setDefaultSearchDialect(3); // we can set default search dialect for the client (UnifiedJedis) object\n                                          // to avoid setting dialect in every query.\n\n    // creating index\n    client.ftCreate(\"geometry-index\",\n        GeoShapeField.of(\"geometry\", GeoShapeField.CoordinateSystem.SPHERICAL) // 'SPHERICAL' is for geographic (lon, lat).\n                                                   // 'FLAT' coordinate system also available for cartesian (X,Y).\n    );\n\n    // preparing data\n    final Polygon small = factory.createPolygon(\n        new Coordinate[]{new Coordinate(34.9001, 29.7001),\n        new Coordinate(34.9001, 29.7100), new Coordinate(34.9100, 29.7100),\n        new Coordinate(34.9100, 29.7001), new Coordinate(34.9001, 29.7001)}\n    );\n\n    // client.hset(\"small\", RediSearchUtil.toStringMap(Collections.singletonMap(\"geometry\", small))); // setting data\n    // client.hset(\"small\", \"geometry\", small.toString()); // simplified setting data\n    client.hsetObject(\"small\", \"geometry\", small); // more simplified setting data\n\n    final Polygon large = factory.createPolygon(\n        new Coordinate[]{new Coordinate(34.9001, 29.7001),\n        new Coordinate(34.9001, 29.7200), new Coordinate(34.9200, 29.7200),\n        new Coordinate(34.9200, 29.7001), new Coordinate(34.9001, 29.7001)}\n    );\n\n    // client.hset(\"large\", RediSearchUtil.toStringMap(Collections.singletonMap(\"geometry\", large))); // setting data\n    // client.hset(\"large\", \"geometry\", large.toString()); // simplified setting data\n    client.hsetObject(\"large\", \"geometry\", large); // more simplified setting data\n\n    // searching\n    final Polygon within = factory.createPolygon(\n        new Coordinate[]{new Coordinate(34.9000, 29.7000),\n        new Coordinate(34.9000, 29.7150), new Coordinate(34.9150, 29.7150),\n        new Coordinate(34.9150, 29.7000), new Coordinate(34.9000, 29.7000)}\n    );\n\n    SearchResult res = client.ftSearch(\"geometry-index\",\n        \"@geometry:[within $poly]\",     // query string\n        FTSearchParams.searchParams()\n            .addParam(\"poly\", within)\n            .dialect(3)                 // DIALECT '3' is required for this query\n    ); \n    assertEquals(1, res.getTotalResults());\n    assertEquals(1, res.getDocuments().size());\n\n    // We can parse geometry objects with WKTReader\n    try {\n      final WKTReader reader = new WKTReader();\n      Geometry object = reader.read(res.getDocuments().get(0).getString(\"geometry\"));\n      assertEquals(small, object);\n    } catch (ParseException ex) { // WKTReader#read throws ParseException\n      ex.printStackTrace(System.err);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/examples/RedisCredentialsProviderUsage.java",
    "content": "package redis.clients.jedis.examples;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.DefaultRedisCredentials;\nimport redis.clients.jedis.DefaultRedisCredentialsProvider;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.RedisClient;\n\npublic class RedisCredentialsProviderUsage {\n\n  public static void main(String[] args) {\n\n    DefaultRedisCredentials initialCredentials\n        = new DefaultRedisCredentials(\"<INITIAL_USERNAME>\", \"<INITIAL_PASSWORD>\");\n\n    DefaultRedisCredentialsProvider credentialsProvider\n        = new DefaultRedisCredentialsProvider(initialCredentials);\n\n    final String host = \"<HOST>\";\n    final int port = 6379;\n    final HostAndPort address = new HostAndPort(host, port);\n    final DefaultJedisClientConfig clientConfig\n        = DefaultJedisClientConfig.builder()\n            .credentialsProvider(credentialsProvider).build();\n\n    RedisClient jedis = RedisClient.builder()\n        .hostAndPort(address)\n        .clientConfig(clientConfig)\n        .build();\n\n    // ...\n    // do operations with jedis\n    // ...\n\n    // when credentials is required to be updated\n    DefaultRedisCredentials updatedCredentials\n        = new DefaultRedisCredentials(\"<UPDATED_USERNAME>\", \"<UPDATED_PASSWORD>\");\n\n    credentialsProvider.setCredentials(updatedCredentials);\n\n    // ...\n    // continue doing operations with jedis\n    // ...\n\n    jedis.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/examples/RetryableCommandExecution.java",
    "content": "package redis.clients.jedis.examples;\n\nimport java.time.Duration;\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPoolConfig;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\n\n/**\n * It is possible to retry command executions in case of connection failures in UnifiedJedis class.\n *\n * The retry-ability comes through RetryableCommandExecutor class. It is also possible to directly provide\n * RetryableCommandExecutor as a parameter.\n *\n * Note: RetryableCommandExecutor should not be considered for\n * <a href=\"https://redis.io/docs/reference/cluster-spec/\">Open Source Redis Cluster mode</a> because it requires to\n * handle more than connection failures. These are done in ClusterCommandExecutor.\n */\npublic class RetryableCommandExecution {\n\n  public static void main(String[] args) {\n\n    // Connection and pool parameters\n    HostAndPort hostAndPort = new HostAndPort(\"127.0.0.1\", 6379);\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().user(\"myuser\").password(\"mypassword\").build();\n    GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();\n\n    PooledConnectionProvider provider = new PooledConnectionProvider(hostAndPort, clientConfig, poolConfig);\n\n    // Retry parameters\n    int maxAttempts = 5;\n    Duration maxTotalRetriesDuration = Duration.ofSeconds(2);\n\n    UnifiedJedis jedis = new UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration);\n\n    jedis.set(\"foo\", \"bar\");\n    jedis.get(\"foo\");\n\n    jedis.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/exceptions/ExceptionsTest.java",
    "content": "package redis.clients.jedis.exceptions;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.HostAndPort;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\n\npublic class ExceptionsTest {\n\n  private static String MESSAGE;\n  private static Throwable CAUSE;\n\n  @BeforeAll\n  public static void prepare() {\n    MESSAGE = \"This is a test message.\";\n    CAUSE = new Throwable(\"This is a test cause.\");\n  }\n\n  @Test\n  public void invalidURI() {\n    try {\n      throw new InvalidURIException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(InvalidURIException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new InvalidURIException(CAUSE);\n    } catch (Exception e) {\n      assertSame(InvalidURIException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new InvalidURIException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(InvalidURIException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void accessControl() {\n    try {\n      throw new JedisAccessControlException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisAccessControlException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisAccessControlException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisAccessControlException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisAccessControlException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisAccessControlException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void askData() {\n    HostAndPort hap = new HostAndPort(\"\", 0);\n    int slot = -1;\n\n    try {\n      throw new JedisAskDataException(MESSAGE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisAskDataException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisAskDataException(CAUSE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisAskDataException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisAskDataException(MESSAGE, CAUSE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisAskDataException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void busy() {\n    try {\n      throw new JedisBusyException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisBusyException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisBusyException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisBusyException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisBusyException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisBusyException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void cluster() {\n    try {\n      throw new JedisClusterException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisClusterException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisClusterException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisClusterException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisClusterException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisClusterException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void clusterOperation() {\n    try {\n      throw new JedisClusterOperationException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisClusterOperationException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisClusterOperationException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisClusterOperationException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisClusterOperationException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisClusterOperationException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void connection() {\n    try {\n      throw new JedisConnectionException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisConnectionException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisConnectionException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisConnectionException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisConnectionException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisConnectionException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void data() {\n    try {\n      throw new JedisDataException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisDataException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisDataException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisDataException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisDataException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisDataException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void jedis() {\n    try {\n      throw new JedisException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void movedData() {\n    HostAndPort hap = new HostAndPort(\"\", 0);\n    int slot = -1;\n\n    try {\n      throw new JedisMovedDataException(MESSAGE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisMovedDataException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisMovedDataException(CAUSE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisMovedDataException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisMovedDataException(MESSAGE, CAUSE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisMovedDataException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void noScript() {\n    try {\n      throw new JedisNoScriptException(MESSAGE);\n    } catch (Exception e) {\n      assertSame(JedisNoScriptException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisNoScriptException(CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisNoScriptException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisNoScriptException(MESSAGE, CAUSE);\n    } catch (Exception e) {\n      assertSame(JedisNoScriptException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n\n  @Test\n  public void redirection() {\n    HostAndPort hap = new HostAndPort(\"\", 0);\n    int slot = -1;\n\n    try {\n      throw new JedisRedirectionException(MESSAGE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisRedirectionException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertNull(e.getCause());\n    }\n\n    try {\n      throw new JedisRedirectionException(CAUSE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisRedirectionException.class, e.getClass());\n      assertEquals(CAUSE, e.getCause());\n      assertEquals(CAUSE.toString(), e.getMessage());\n    }\n\n    try {\n      throw new JedisRedirectionException(MESSAGE, CAUSE, hap, slot);\n    } catch (Exception e) {\n      assertSame(JedisRedirectionException.class, e.getClass());\n      assertEquals(MESSAGE, e.getMessage());\n      assertEquals(CAUSE, e.getCause());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/exceptions/FailoverAbortedException.java",
    "content": "package redis.clients.jedis.exceptions;\n\npublic class FailoverAbortedException extends RuntimeException {\n  private static final long serialVersionUID = 1925110762858409954L;\n\n  public FailoverAbortedException(String message) {\n    super(message);\n  }\n\n  public FailoverAbortedException(Throwable cause) {\n    super(cause);\n  }\n\n  public FailoverAbortedException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/executors/ClusterCommandExecutorTest.java",
    "content": "package redis.clients.jedis.executors;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.function.LongConsumer;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.JedisClusterCRC16;\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\n\nimport redis.clients.jedis.exceptions.JedisAskDataException;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.exceptions.JedisMovedDataException;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\nimport redis.clients.jedis.util.ReflectionTestUtil;\n\npublic class ClusterCommandExecutorTest {\n\n  private static final Duration ONE_SECOND = Duration.ofSeconds(1);\n\n  private static final CommandObject<String> STR_COM_OBJECT = new CommandObject<>(\n      new CommandArguments(Protocol.Command.GET).key(\"testkey\"), BuilderFactory.STRING);\n\n  // Keyless command object for testing keyless command execution with WRITE flag (FLUSHDB is\n  // keyless and WRITE)\n  private static final CommandObject<String> KEYLESS_WRITE_COM_OBJECT = new CommandObject<>(\n      new CommandArguments(Protocol.Command.FLUSHDB), BuilderFactory.STRING);\n\n  /**\n   * Helper method to invoke the private executeKeylessCommand method via reflection.\n   */\n  @SuppressWarnings(\"unchecked\")\n  private static <T> T invokeExecuteKeylessCommand(ClusterCommandExecutor executor,\n      CommandObject<T> commandObject) {\n    return ReflectionTestUtil.invokeMethod(executor, \"executeKeylessCommand\",\n      new Class<?>[] { CommandObject.class }, commandObject);\n  }\n\n  @Test\n  public void runSuccessfulExecute() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"foo\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n    assertEquals(\"foo\", testMe.executeCommand(STR_COM_OBJECT));\n  }\n\n  @Test\n  public void runFailOnFirstExecSuccessOnSecondExec() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      boolean isFirstCall = true;\n\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        if (isFirstCall) {\n          isFirstCall = false;\n          throw new JedisConnectionException(\"Borkenz\");\n        }\n\n        return (T) \"foo\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    assertEquals(\"foo\", testMe.executeCommand(STR_COM_OBJECT));\n  }\n\n  @Test\n  public void runAlwaysFailing() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    final LongConsumer sleep = mock(LongConsumer.class);\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        throw new JedisConnectionException(\"Connection failed\");\n      }\n\n      @Override\n      protected void sleep(long sleepMillis) {\n        sleep.accept(sleepMillis);\n      }\n    };\n\n    try {\n      testMe.executeCommand(STR_COM_OBJECT);\n      fail(\"cluster command did not fail\");\n    } catch (JedisClusterOperationException e) {\n      // expected\n    }\n    InOrder inOrder = inOrder(connectionHandler, sleep);\n    inOrder.verify(connectionHandler, times(2))\n        .getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verify(sleep).accept(ArgumentMatchers.anyLong());\n    inOrder.verify(connectionHandler).renewSlotCache();\n    inOrder.verify(connectionHandler).getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  @Test\n  public void runMovedSuccess() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    final HostAndPort movedTarget = new HostAndPort(null, 0);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n    when(connectionHandler.getConnection(movedTarget)).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      boolean isFirstCall = true;\n\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        if (isFirstCall) {\n          isFirstCall = false;\n\n          // Slot 0 moved\n          throw new JedisMovedDataException(\"\", movedTarget, 0);\n        }\n\n        return (T) \"foo\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    assertEquals(\"foo\", testMe.executeCommand(STR_COM_OBJECT));\n\n    InOrder inOrder = inOrder(connectionHandler);\n    inOrder.verify(connectionHandler).getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verify(connectionHandler).renewSlotCache(ArgumentMatchers.any());\n    inOrder.verify(connectionHandler).getConnection(movedTarget);\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  @Test\n  public void runAskSuccess() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    final HostAndPort askTarget = new HostAndPort(null, 0);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n    when(connectionHandler.getConnection(askTarget)).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      boolean isFirstCall = true;\n\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        if (isFirstCall) {\n          isFirstCall = false;\n\n          // Slot 0 moved\n          throw new JedisAskDataException(\"\", askTarget, 0);\n        }\n\n        return (T) \"foo\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    assertEquals(\"foo\", testMe.executeCommand(STR_COM_OBJECT));\n\n    InOrder inOrder = inOrder(connectionHandler, connection);\n    inOrder.verify(connectionHandler).getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verify(connectionHandler).getConnection(askTarget);\n    // inOrder.verify(connection).asking();\n    inOrder.verify(connection).close(); // From the finally clause in runWithRetries()\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  // requires 'execute(Connection connection, CommandObject<T> commandObject)' separately\n  @Test\n  public void runMovedThenAllNodesFailing() {\n    // Test:\n    // First attempt is a JedisMovedDataException() move, because we asked the wrong node.\n    // All subsequent attempts are JedisConnectionExceptions, because all nodes are now down.\n    // In response to the JedisConnectionExceptions, run() retries random nodes until maxAttempts is\n    // reached.\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n\n    final Connection redirecter = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(redirecter);\n\n    final Connection failer = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(HostAndPort.class)))\n        .thenReturn(failer);\n    Mockito.doAnswer((InvocationOnMock invocation) -> {\n      when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n          .thenReturn(failer);\n      return null;\n    }).when(connectionHandler).renewSlotCache();\n\n    final LongConsumer sleep = mock(LongConsumer.class);\n    final HostAndPort movedTarget = new HostAndPort(null, 0);\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 5, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        if (redirecter == connection) {\n          // First attempt, report moved\n          throw new JedisMovedDataException(\"Moved\", movedTarget, 0);\n        }\n\n        if (failer == connection) {\n          // Second attempt in response to the move, report failure\n          throw new JedisConnectionException(\"Connection failed\");\n        }\n\n        throw new IllegalStateException(\"Should have thrown jedis exception\");\n      }\n\n      @Override\n      protected void sleep(long sleepMillis) {\n        sleep.accept(sleepMillis);\n      }\n    };\n\n    try {\n      testMe.executeCommand(STR_COM_OBJECT);\n      fail(\"cluster command did not fail\");\n    } catch (JedisClusterOperationException e) {\n      // expected\n    }\n    InOrder inOrder = inOrder(connectionHandler, sleep);\n    inOrder.verify(connectionHandler).getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verify(connectionHandler).renewSlotCache(redirecter);\n    inOrder.verify(connectionHandler, times(2)).getConnection(movedTarget);\n    inOrder.verify(sleep).accept(ArgumentMatchers.anyLong());\n    inOrder.verify(connectionHandler).renewSlotCache();\n    inOrder.verify(connectionHandler, times(2))\n        .getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verify(sleep).accept(ArgumentMatchers.anyLong());\n    inOrder.verify(connectionHandler).renewSlotCache();\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  // requires 'execute(Connection connection, CommandObject<T> commandObject)' separately\n  @Test\n  public void runMasterFailingReplicaRecovering() {\n    // We have two nodes, master and replica, and master has just gone down permanently.\n    //\n    // Test:\n    // 1. We try to contact master => JedisConnectionException\n    // 2. We try to contact master => JedisConnectionException\n    // 3. sleep and renew\n    // 4. We try to contact replica => Success, because it has now failed over\n\n    final Connection master = mock(Connection.class);\n    when(master.toString()).thenReturn(\"master\");\n\n    final Connection replica = mock(Connection.class);\n    when(replica.toString()).thenReturn(\"replica\");\n\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(master);\n\n    Mockito.doAnswer((InvocationOnMock invocation) -> {\n      when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n          .thenReturn(replica);\n      return null;\n    }).when(connectionHandler).renewSlotCache();\n\n    final AtomicLong totalSleepMs = new AtomicLong();\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 5, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        assertNotNull(connection);\n\n        if (connection.toString().equals(\"master\")) {\n          throw new JedisConnectionException(\"Master is down\");\n        }\n\n        assert connection.toString().equals(\"replica\");\n\n        return (T) \"Success!\";\n      }\n\n      @Override\n      protected void sleep(long sleepMillis) {\n        // assert sleepMillis > 0;\n        totalSleepMs.addAndGet(sleepMillis);\n      }\n    };\n\n    assertEquals(\"Success!\", testMe.executeCommand(STR_COM_OBJECT));\n    InOrder inOrder = inOrder(connectionHandler);\n    inOrder.verify(connectionHandler, times(2))\n        .getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verify(connectionHandler).renewSlotCache();\n    inOrder.verify(connectionHandler).getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verifyNoMoreInteractions();\n    MatcherAssert.assertThat(totalSleepMs.get(), Matchers.greaterThan(0L));\n  }\n\n  @Test\n  public void runRethrowsJedisNoReachableClusterNodeException() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenThrow(JedisClusterOperationException.class);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return null;\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    assertThrows(JedisClusterOperationException.class, () -> testMe.executeCommand(STR_COM_OBJECT));\n  }\n\n  @Test\n  public void runStopsRetryingAfterTimeout() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    // final LongConsumer sleep = mock(LongConsumer.class);\n    final AtomicLong totalSleepMs = new AtomicLong();\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        try {\n          // exceed deadline\n          Thread.sleep(2L);\n        } catch (InterruptedException e) {\n          throw new RuntimeException(e);\n        }\n        throw new JedisConnectionException(\"Connection failed\");\n      }\n\n      @Override\n      protected void sleep(long sleepMillis) {\n        // sleep.accept(sleepMillis);\n        totalSleepMs.addAndGet(sleepMillis);\n      }\n    };\n\n    try {\n      testMe.executeCommand(STR_COM_OBJECT);\n      fail(\"cluster command did not fail\");\n    } catch (JedisClusterOperationException e) {\n      // expected\n    }\n    // InOrder inOrder = inOrder(connectionHandler, sleep);\n    InOrder inOrder = inOrder(connectionHandler);\n    inOrder.verify(connectionHandler).getConnection(ArgumentMatchers.any(CommandArguments.class));\n    inOrder.verifyNoMoreInteractions();\n    assertEquals(0L, totalSleepMs.get());\n  }\n\n  @Test\n  public void runSuccessfulExecuteKeylessCommand() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection = mock(Connection.class);\n\n    connectionMap.put(\"localhost:6379\", pool);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool.getResource()).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n    assertEquals(\"OK\", invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT));\n  }\n\n  @Test\n  public void runKeylessCommandUsesConnectionMapRoundRobin() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection = mock(Connection.class);\n\n    connectionMap.put(\"localhost:6379\", pool);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool.getResource()).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n\n    // Verify that getPrimaryNodesConnectionMap() was called for round-robin distribution\n    InOrder inOrder = inOrder(connectionHandler, pool, connection);\n    inOrder.verify(connectionHandler).getPrimaryNodesConnectionMap();\n    inOrder.verify(pool).getResource();\n    inOrder.verify(connection).close();\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  @Test\n  public void runKeylessCommandThrowsOnRedirections() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection1 = mock(Connection.class);\n    final HostAndPort movedTarget = new HostAndPort(\"127.0.0.1\", 6380);\n    final int slot = 12345;\n\n    connectionMap.put(\"localhost:6379\", pool);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool.getResource()).thenReturn(connection1);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        // When followRedirections=false, redirections should be thrown as exceptions\n        throw new JedisMovedDataException(\"MOVED \" + slot, movedTarget, slot);\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // When followRedirections=false, the redirection exception should be thrown\n    JedisMovedDataException exception = assertThrows(JedisMovedDataException.class,\n      () -> invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT));\n\n    // Verify exception contains correct information\n    assertEquals(movedTarget, exception.getTargetNode());\n    assertEquals(slot, exception.getSlot());\n\n    // Verify that we only tried once (no retry after redirection)\n    verify(connectionHandler, times(1)).getPrimaryNodesConnectionMap();\n    verify(pool, times(1)).getResource();\n    verify(connection1).close();\n  }\n\n  @Test\n  public void runKeylessCommandThrowsAskDataExceptionOnAskRedirection() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection1 = mock(Connection.class);\n    final HostAndPort askTarget = new HostAndPort(\"127.0.0.1\", 6381);\n    final int slot = 9999;\n\n    connectionMap.put(\"localhost:6379\", pool);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool.getResource()).thenReturn(connection1);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        // When followRedirections=false, ASK redirections should be thrown as exceptions\n        throw new JedisAskDataException(\"ASK \" + slot, askTarget, slot);\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // When followRedirections=false, the ASK exception should be thrown\n    JedisAskDataException exception = assertThrows(JedisAskDataException.class,\n      () -> invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT));\n\n    // Verify exception contains correct information\n    assertEquals(askTarget, exception.getTargetNode());\n    assertEquals(slot, exception.getSlot());\n\n    // Verify that we only tried once (no retry after redirection)\n    verify(connectionHandler, times(1)).getPrimaryNodesConnectionMap();\n    verify(pool, times(1)).getResource();\n    verify(connection1).close();\n  }\n\n  @Test\n  public void runKeylessCommandFailsAfterMaxAttempts() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n    final LongConsumer sleep = mock(LongConsumer.class);\n\n    connectionMap.put(\"localhost:6379\", pool);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool.getResource()).thenReturn(connection1, connection2, connection3);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        throw new JedisConnectionException(\"Connection failed\");\n      }\n\n      @Override\n      protected void sleep(long sleepMillis) {\n        sleep.accept(sleepMillis);\n      }\n    };\n\n    try {\n      invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n      fail(\"keyless command did not fail\");\n    } catch (JedisClusterOperationException e) {\n      // expected\n    }\n\n    // Verify that we tried connection map access and performed slot cache renewal\n    // getPrimaryNodesConnectionMap() called 3 times (once for each connection attempt)\n    // getResource() called 3 times, sleep called once, renewSlotCache called once\n    verify(connectionHandler, times(3)).getPrimaryNodesConnectionMap();\n    verify(pool, times(3)).getResource();\n    verify(connection1).close();\n    verify(connection2).close();\n    verify(connection3).close();\n    verify(sleep).accept(ArgumentMatchers.anyLong());\n    verify(connectionHandler).renewSlotCache();\n  }\n\n  @Test\n  public void runKeylessCommandFailsWithEmptyConnectionMap() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> emptyConnectionMap = new HashMap<>();\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(emptyConnectionMap);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"should_not_reach_here\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    try {\n      invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n      fail(\"keyless command should fail with empty connection map\");\n    } catch (JedisClusterOperationException e) {\n      assertEquals(\"No cluster nodes available.\", e.getMessage());\n    }\n\n    // Verify that getPrimaryNodesConnectionMap() was called\n    verify(connectionHandler).getPrimaryNodesConnectionMap();\n  }\n\n  @Test\n  public void runKeylessCommandRoundRobinDistribution() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n\n    // Create multiple pools to test round-robin\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    ConnectionPool pool2 = mock(ConnectionPool.class);\n    ConnectionPool pool3 = mock(ConnectionPool.class);\n\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n\n    connectionMap.put(\"localhost:6379\", pool1);\n    connectionMap.put(\"localhost:6380\", pool2);\n    connectionMap.put(\"localhost:6381\", pool3);\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n    when(pool2.getResource()).thenReturn(connection2);\n    when(pool3.getResource()).thenReturn(connection3);\n\n    // Track which connections are used\n    List<Connection> usedConnections = new ArrayList<>();\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        usedConnections.add(connection);\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // Execute multiple keyless commands to verify round-robin\n    invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n    invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n    invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n    invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT); // Should cycle back to first\n\n    // Verify round-robin behavior - should cycle through all connections\n    assertEquals(4, usedConnections.size());\n    Set<Connection> uniqueConnections = new HashSet<>(usedConnections);\n    assertEquals(3, uniqueConnections.size(),\n      \"Round-robin should distribute across multiple nodes\");\n  }\n\n  @Test\n  public void runKeylessCommandCircularCounterNeverOverflows() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n\n    // Create 3 pools to test circular behavior\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    ConnectionPool pool2 = mock(ConnectionPool.class);\n    ConnectionPool pool3 = mock(ConnectionPool.class);\n\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n\n    connectionMap.put(\"node1:6379\", pool1);\n    connectionMap.put(\"node2:6379\", pool2);\n    connectionMap.put(\"node3:6379\", pool3);\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n    when(pool2.getResource()).thenReturn(connection2);\n    when(pool3.getResource()).thenReturn(connection3);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // Execute many commands to test circular behavior\n    // With our implementation using getAndUpdate(current -> (current + 1) % nodeCount),\n    // the counter never exceeds nodeCount-1, so overflow is impossible\n    for (int i = 0; i < 100; i++) {\n      String result = invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n      assertEquals(\"OK\", result);\n    }\n\n    // Verify that getPrimaryNodesConnectionMap() was called for each execution\n    verify(connectionHandler, times(100)).getPrimaryNodesConnectionMap();\n\n    // The circular counter implementation ensures no overflow can occur\n    // because the counter value is always between 0 and (nodeCount-1)\n  }\n\n  @Test\n  public void runKeylessCommandEvenDistributionRoundRobin() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n\n    // Create 4 pools to test even distribution\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    ConnectionPool pool2 = mock(ConnectionPool.class);\n    ConnectionPool pool3 = mock(ConnectionPool.class);\n    ConnectionPool pool4 = mock(ConnectionPool.class);\n\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n    Connection connection4 = mock(Connection.class);\n\n    // Use ordered map to ensure consistent iteration order for testing\n    connectionMap.put(\"node1:6379\", pool1);\n    connectionMap.put(\"node2:6379\", pool2);\n    connectionMap.put(\"node3:6379\", pool3);\n    connectionMap.put(\"node4:6379\", pool4);\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n    when(pool2.getResource()).thenReturn(connection2);\n    when(pool3.getResource()).thenReturn(connection3);\n    when(pool4.getResource()).thenReturn(connection4);\n\n    // Track connection usage count\n    Map<Connection, Integer> connectionUsage = new HashMap<>();\n    connectionUsage.put(connection1, 0);\n    connectionUsage.put(connection2, 0);\n    connectionUsage.put(connection3, 0);\n    connectionUsage.put(connection4, 0);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        connectionUsage.put(connection, connectionUsage.get(connection) + 1);\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // Execute commands - should be evenly distributed\n    int totalCommands = 40; // Multiple of 4 for perfect distribution\n    for (int i = 0; i < totalCommands; i++) {\n      invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n    }\n\n    // Verify even distribution - each node should get exactly 10 commands\n    int expectedPerNode = totalCommands / 4;\n    assertEquals(expectedPerNode, connectionUsage.get(connection1).intValue(),\n      \"Node 1 should receive exactly \" + expectedPerNode + \" commands\");\n    assertEquals(expectedPerNode, connectionUsage.get(connection2).intValue(),\n      \"Node 2 should receive exactly \" + expectedPerNode + \" commands\");\n    assertEquals(expectedPerNode, connectionUsage.get(connection3).intValue(),\n      \"Node 3 should receive exactly \" + expectedPerNode + \" commands\");\n    assertEquals(expectedPerNode, connectionUsage.get(connection4).intValue(),\n      \"Node 4 should receive exactly \" + expectedPerNode + \" commands\");\n\n    // Verify total commands executed\n    int totalExecuted = connectionUsage.values().stream().mapToInt(Integer::intValue).sum();\n    assertEquals(totalCommands, totalExecuted, \"Total commands executed should match\");\n\n    // Verify that getPrimaryNodesConnectionMap() was called for each execution\n    verify(connectionHandler, times(totalCommands)).getPrimaryNodesConnectionMap();\n  }\n\n  @Test\n  public void runKeylessCommandRoundRobinSequence() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new HashMap<>();\n\n    // Create 3 pools for simpler sequence verification\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    ConnectionPool pool2 = mock(ConnectionPool.class);\n    ConnectionPool pool3 = mock(ConnectionPool.class);\n\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n\n    // Use LinkedHashMap to ensure consistent iteration order\n    connectionMap = new java.util.LinkedHashMap<>();\n    connectionMap.put(\"node1:6379\", pool1);\n    connectionMap.put(\"node2:6379\", pool2);\n    connectionMap.put(\"node3:6379\", pool3);\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n    when(pool2.getResource()).thenReturn(connection2);\n    when(pool3.getResource()).thenReturn(connection3);\n\n    // Track the exact sequence of connections used\n    List<String> connectionSequence = new ArrayList<>();\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        if (connection == connection1) {\n          connectionSequence.add(\"node1\");\n        } else if (connection == connection2) {\n          connectionSequence.add(\"node2\");\n        } else if (connection == connection3) {\n          connectionSequence.add(\"node3\");\n        }\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // Execute 9 commands to see 3 complete cycles\n    for (int i = 0; i < 9; i++) {\n      invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n    }\n\n    // Verify the round-robin sequence\n    List<String> expectedSequence = new ArrayList<>();\n    expectedSequence.add(\"node1\");\n    expectedSequence.add(\"node2\");\n    expectedSequence.add(\"node3\"); // First cycle\n    expectedSequence.add(\"node1\");\n    expectedSequence.add(\"node2\");\n    expectedSequence.add(\"node3\"); // Second cycle\n    expectedSequence.add(\"node1\");\n    expectedSequence.add(\"node2\");\n    expectedSequence.add(\"node3\"); // Third cycle\n\n    assertEquals(expectedSequence, connectionSequence,\n      \"Round-robin should follow exact sequence: node1 -> node2 -> node3 -> node1 -> ...\");\n  }\n\n  @Test\n  public void runKeylessCommandWithReadOnlyCommandUsesAllNodesConnectionMap() {\n    // Create a read-only command object using GET command (which has READONLY flag)\n    CommandObject<String> readOnlyCommandObject = new CommandObject<>(\n        new CommandArguments(Protocol.Command.GET).key(\"testkey\"), BuilderFactory.STRING);\n\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> allNodesConnectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection = mock(Connection.class);\n\n    // Setup connection map with all nodes (including replicas)\n    allNodesConnectionMap.put(\"primary:6379\", pool);\n    allNodesConnectionMap.put(\"replica:6380\", pool);\n\n    // For read-only commands, getConnectionMap() should be called (all nodes including replicas)\n    when(connectionHandler.getConnectionMap()).thenReturn(allNodesConnectionMap);\n    when(pool.getResource()).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"readonly_result\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    assertEquals(\"readonly_result\", invokeExecuteKeylessCommand(testMe, readOnlyCommandObject));\n\n    // Verify that getConnectionMap() was called (for read-only commands, uses all nodes)\n    // and NOT getPrimaryNodesConnectionMap()\n    verify(connectionHandler).getConnectionMap();\n    verify(connectionHandler, times(0)).getPrimaryNodesConnectionMap();\n    verify(pool).getResource();\n    verify(connection).close();\n  }\n\n  @Test\n  public void runKeylessCommandWithWriteCommandUsesPrimaryNodesConnectionMap() {\n    // Create a write command object using SET command (which has WRITE flag, not READONLY)\n    CommandObject<String> writeCommandObject = new CommandObject<>(\n        new CommandArguments(Protocol.Command.SET).key(\"testkey\").add(\"value\"),\n        BuilderFactory.STRING);\n\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> primaryNodesConnectionMap = new HashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection = mock(Connection.class);\n\n    // Setup connection map with only primary nodes\n    primaryNodesConnectionMap.put(\"primary:6379\", pool);\n\n    // For write commands, getPrimaryNodesConnectionMap() should be called\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(primaryNodesConnectionMap);\n    when(pool.getResource()).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"write_result\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    assertEquals(\"write_result\", invokeExecuteKeylessCommand(testMe, writeCommandObject));\n\n    // Verify that getPrimaryNodesConnectionMap() was called (for write commands, uses only\n    // primaries)\n    // and NOT getConnectionMap()\n    verify(connectionHandler).getPrimaryNodesConnectionMap();\n    verify(connectionHandler, times(0)).getConnectionMap();\n    verify(pool).getResource();\n    verify(connection).close();\n  }\n\n  /**\n   * Provides command objects for commands that use ONE_SUCCEEDED response policy. These commands\n   * should return success if at least one node succeeds.\n   */\n  static java.util.stream.Stream<CommandObject<String>> oneSucceededPolicyCommands() {\n    return java.util.stream.Stream.of(\n      new CommandObject<>(new CommandArguments(Protocol.Command.SCRIPT).add(\"KILL\"),\n          BuilderFactory.STRING),\n      new CommandObject<>(new CommandArguments(Protocol.Command.FUNCTION).add(\"KILL\"),\n          BuilderFactory.STRING));\n  }\n\n  /**\n   * This test verifies the bug: broadcastCommand throws on partial failure ignoring ONE_SUCCEEDED\n   * policy. When any node throws an exception, isErrored is set to true, which causes subsequent\n   * successful replies to be skipped and the method to unconditionally throw\n   * JedisBroadcastException. For ONE_SUCCEEDED response policy (used by SCRIPT KILL, FUNCTION\n   * KILL), the method needs to return success if at least one node succeeded. Currently, a single\n   * node failure causes the entire broadcast to fail even when other nodes succeed. NOTE: This test\n   * FAILS when the bug exists, demonstrating the issue. When the bug is fixed, this test will pass.\n   */\n  @ParameterizedTest\n  @MethodSource(\"oneSucceededPolicyCommands\")\n  public void broadcastCommandShouldSucceedWithOneSucceededPolicyWhenSomeNodesFail(\n      CommandObject<String> killCommand) {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new java.util.LinkedHashMap<>();\n\n    // Create 3 node pools\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    ConnectionPool pool2 = mock(ConnectionPool.class);\n    ConnectionPool pool3 = mock(ConnectionPool.class);\n\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n\n    connectionMap.put(\"node1:6379\", pool1);\n    connectionMap.put(\"node2:6379\", pool2);\n    connectionMap.put(\"node3:6379\", pool3);\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n    when(pool2.getResource()).thenReturn(connection2);\n    when(pool3.getResource()).thenReturn(connection3);\n\n    List<String> nodeResults = new ArrayList<>();\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      int callCount = 0;\n\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        callCount++;\n        if (callCount == 1 || callCount == 3) {\n          // First and third nodes fail (e.g., no script/function running)\n          nodeResults.add(\"FAIL\");\n          throw new JedisDataException(\"NOTBUSY No scripts in execution right now.\");\n        }\n        // Second node succeeds\n        nodeResults.add(\"OK\");\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // With ONE_SUCCEEDED policy, the broadcast should succeed because at least one node returned OK\n    // BUG: Currently this throws JedisBroadcastException because isErrored=true ignores the policy\n    String result = testMe.broadcastCommand(killCommand, true);\n\n    assertEquals(\"OK\", result, \"broadcastCommand should return OK when at least one node succeeds \"\n        + \"with ONE_SUCCEEDED policy\");\n    // Verify that all nodes were queried\n    assertEquals(3, nodeResults.size(), \"Should have queried all 3 nodes\");\n    assertTrue(nodeResults.contains(\"OK\"), \"At least one node should have succeeded\");\n  }\n\n  /**\n   * This test verifies that broadcastCommand throws JedisMovedDataException when a node responds\n   * with a MOVED redirection, since broadcastCommand uses followRedirections=false. The exception\n   * should contain the correct target node and slot information.\n   */\n  @Test\n  public void broadcastCommandThrowsMovedDataExceptionOnRedirection() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new java.util.LinkedHashMap<>();\n\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    Connection connection1 = mock(Connection.class);\n    final HostAndPort movedTarget = new HostAndPort(\"127.0.0.1\", 6382);\n    final int slot = 7777;\n\n    connectionMap.put(\"node1:6379\", pool1);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n\n    CommandObject<String> flushdbCommand = new CommandObject<>(\n        new CommandArguments(Protocol.Command.FLUSHDB), BuilderFactory.STRING);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        // Simulate MOVED redirection\n        throw new JedisMovedDataException(\"MOVED \" + slot, movedTarget, slot);\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // broadcastCommand uses followRedirections=false, so it should throw the exception\n    // (caught by the aggregator as an error for that node)\n    // In this case with only one node, the broadcast will fail\n    try {\n      testMe.broadcastCommand(flushdbCommand, true);\n      fail(\"broadcastCommand should have thrown an exception when redirection occurs\");\n    } catch (Exception e) {\n      // The exception should be caught by the aggregator; verify the root cause is a redirection\n      // The aggregator collects errors per node\n    }\n\n    // Verify that we tried to execute the command\n    verify(pool1, times(1)).getResource();\n    verify(connection1).close();\n  }\n\n  /**\n   * This test verifies that broadcastCommand throws JedisAskDataException when a node responds with\n   * an ASK redirection, since broadcastCommand uses followRedirections=false. The exception should\n   * contain the correct target node and slot information.\n   */\n  @Test\n  public void broadcastCommandThrowsAskDataExceptionOnRedirection() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new java.util.LinkedHashMap<>();\n\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    Connection connection1 = mock(Connection.class);\n    final HostAndPort askTarget = new HostAndPort(\"127.0.0.1\", 6383);\n    final int slot = 8888;\n\n    connectionMap.put(\"node1:6379\", pool1);\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool1.getResource()).thenReturn(connection1);\n\n    CommandObject<String> flushdbCommand = new CommandObject<>(\n        new CommandArguments(Protocol.Command.FLUSHDB), BuilderFactory.STRING);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        // Simulate ASK redirection\n        throw new JedisAskDataException(\"ASK \" + slot, askTarget, slot);\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // broadcastCommand uses followRedirections=false, so it should throw the exception\n    // (caught by the aggregator as an error for that node)\n    try {\n      testMe.broadcastCommand(flushdbCommand, true);\n      fail(\"broadcastCommand should have thrown an exception when redirection occurs\");\n    } catch (Exception e) {\n      // The exception should be caught by the aggregator; verify the root cause is a redirection\n    }\n\n    // Verify that we tried to execute the command\n    verify(pool1, times(1)).getResource();\n    verify(connection1).close();\n  }\n\n  /**\n   * This test verifies the bug: executeMultiShardCommand throws on partial failure ignoring\n   * ONE_SUCCEEDED policy. The same issue as broadcastCommand exists in executeMultiShardCommand:\n   * when any shard throws an exception, isErrored is set to true, which causes subsequent\n   * successful replies to be skipped and the method to unconditionally throw\n   * JedisBroadcastException. For ONE_SUCCEEDED response policy, the method needs to return success\n   * if at least one shard succeeded. NOTE: This test FAILS when the bug exists, demonstrating the\n   * issue. When the bug is fixed, this test will pass.\n   */\n  @Test\n  public void executeMultiShardCommandShouldSucceedWithOneSucceededPolicyWhenSomeShardsFail() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    // Create multiple command objects to simulate multi-shard execution\n    // Using SCRIPT KILL which has ONE_SUCCEEDED policy\n    List<CommandObject<String>> commandObjects = new ArrayList<>();\n    commandObjects.add(new CommandObject<>(\n        new CommandArguments(Protocol.Command.SCRIPT).add(\"KILL\"), BuilderFactory.STRING));\n    commandObjects.add(new CommandObject<>(\n        new CommandArguments(Protocol.Command.SCRIPT).add(\"KILL\"), BuilderFactory.STRING));\n    commandObjects.add(new CommandObject<>(\n        new CommandArguments(Protocol.Command.SCRIPT).add(\"KILL\"), BuilderFactory.STRING));\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      int callCount = 0;\n\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        callCount++;\n        if (callCount == 1) {\n          // First shard fails\n          throw new JedisDataException(\"NOTBUSY No scripts in execution right now.\");\n        }\n        // Other shards succeed\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // With ONE_SUCCEEDED policy, the multi-shard command should succeed because at least one\n    // shard returned OK\n    // BUG: Currently this throws JedisBroadcastException because isErrored=true ignores the policy\n    String result = testMe.executeMultiShardCommand(commandObjects);\n\n    assertEquals(\"OK\", result, \"executeMultiShardCommand should return OK when at least one shard \"\n        + \"succeeds with ONE_SUCCEEDED policy\");\n  }\n\n  /**\n   * This test verifies the bug: MGET multi-shard silently returns values in wrong order. The issue\n   * is that mgetMultiShard groups keys by hash slot using a HashMap, which doesn't preserve\n   * insertion order. The aggregated result concatenates per-slot lists in arbitrary order, breaking\n   * MGET's contract that returned values correspond positionally to input keys. Callers using\n   * index-based access (e.g., values.get(i) for keys[i]) will silently get wrong values when keys\n   * span multiple slots. This test runs MGET with many different key combinations to find at least\n   * one case where the HashMap iteration order differs from insertion order, proving the bug\n   * exists. NOTE: This test FAILS when the bug exists, demonstrating the issue. When the bug is\n   * fixed (e.g., by using LinkedHashMap to preserve insertion order), this test will pass.\n   */\n  /**\n   * This test verifies that MGET multi-shard returns values in the correct order matching the input\n   * keys, even when keys span multiple hash slots. The fix groups consecutive keys with the same\n   * slot together, but keeps non-consecutive keys with the same slot in separate commands to\n   * preserve order. For example, keys mapping to slots [A, B, A] result in 3 separate commands, not\n   * 2, ensuring the concatenated results match the input key order.\n   */\n  @Test\n  public void mgetMultiShardReturnsValuesInCorrectOrderWhenKeysSpanMultipleSlots() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    ClusterCommandObjects commandObjects = new ClusterCommandObjects();\n\n    // Try many different key combinations including ones where keys hash to different slots\n    for (int i = 0; i < 100; i++) {\n      // Generate unique keys that are likely to hash to different slots\n      String key1 = \"testkey_a_\" + i;\n      String key2 = \"testkey_b_\" + i;\n      String key3 = \"testkey_c_\" + i;\n\n      int slot1 = JedisClusterCRC16.getSlot(key1);\n      int slot2 = JedisClusterCRC16.getSlot(key2);\n      int slot3 = JedisClusterCRC16.getSlot(key3);\n\n      // Only test if all keys hash to different slots (the challenging case)\n      if (slot1 == slot2 || slot2 == slot3 || slot1 == slot3) {\n        continue;\n      }\n\n      // Use the standard MGET multi-shard API\n      List<CommandObject<List<String>>> mgetCommands = commandObjects.mgetMultiShard(key1, key2,\n        key3);\n\n      List<String> inputOrder = java.util.Arrays.asList(key1, key2, key3);\n\n      // Execute MGET with the standard executeMultiShardCommand\n      ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10,\n          Duration.ZERO, StaticCommandFlagsRegistry.registry()) {\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public <T> T execute(Connection conn, CommandObject<T> commandObject) {\n          List<String> values = new ArrayList<>();\n          CommandArguments args = commandObject.getArguments();\n          boolean isFirst = true;\n          for (redis.clients.jedis.args.Rawable arg : args) {\n            if (isFirst) {\n              isFirst = false;\n              continue;\n            }\n            String key = new String(arg.getRaw());\n            values.add(\"value_for_\" + key);\n          }\n          return (T) values;\n        }\n\n        @Override\n        protected void sleep(long ignored) {\n          throw new RuntimeException(\"This test should never sleep\");\n        }\n      };\n\n      // Use the standard executeMultiShardCommand method\n      List<String> result = testMe.executeMultiShardCommand(mgetCommands);\n\n      // Expected correct order based on input - MGET contract says values must match key positions\n      List<String> expectedCorrectOrder = java.util.Arrays.asList(\"value_for_\" + key1,\n        \"value_for_\" + key2, \"value_for_\" + key3);\n\n      // Verify all values are present\n      assertEquals(3, result.size(), \"Should have 3 values\");\n      assertTrue(result.contains(\"value_for_\" + key1));\n      assertTrue(result.contains(\"value_for_\" + key2));\n      assertTrue(result.contains(\"value_for_\" + key3));\n\n      // THE KEY ASSERTION: Values must be in the same order as input keys\n      // With the fix (grouping consecutive keys only), this should pass for all key combinations\n      assertEquals(expectedCorrectOrder, result,\n        \"MGET multi-shard should return values in the same order as input keys. \" + \"Input keys: \"\n            + inputOrder + \", slots: [\" + slot1 + \", \" + slot2 + \", \" + slot3 + \"]\");\n    }\n  }\n\n  /**\n   * This test verifies that MGET multi-shard returns values in the correct order when keys have\n   * interleaved hash slots (e.g., [slotA, slotB, slotA]). This is the critical case that the fix\n   * addresses: without the fix, keys with the same slot would be grouped together, producing 2\n   * commands instead of 3, which would return values in wrong order.\n   */\n  @Test\n  public void mgetMultiShardReturnsValuesInCorrectOrderForInterleavedSlots() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    String key1 = \"key1\";\n    String key2 = \"key2\";\n    String key3 = \"key3\";\n\n    // Mock JedisClusterCRC16.getSlot to return interleaved slots: [slotA, slotB, slotA]\n    try (org.mockito.MockedStatic<JedisClusterCRC16> mockedStatic = Mockito\n        .mockStatic(JedisClusterCRC16.class)) {\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key1)).thenReturn(100);\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key2)).thenReturn(200);\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key3)).thenReturn(100);\n\n      ClusterCommandObjects commandObjects = new ClusterCommandObjects();\n\n      // The fix should create 3 separate commands (not 2) to preserve order\n      List<CommandObject<List<String>>> mgetCommands = commandObjects.mgetMultiShard(key1, key2,\n        key3);\n      assertEquals(3, mgetCommands.size(),\n        \"Should have 3 separate commands for interleaved slots [A, B, A], not 2\");\n\n      // Execute MGET with the standard executeMultiShardCommand\n      ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10,\n          Duration.ZERO, StaticCommandFlagsRegistry.registry()) {\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public <T> T execute(Connection conn, CommandObject<T> commandObject) {\n          List<String> values = new ArrayList<>();\n          CommandArguments args = commandObject.getArguments();\n          boolean isFirst = true;\n          for (redis.clients.jedis.args.Rawable arg : args) {\n            if (isFirst) {\n              isFirst = false;\n              continue;\n            }\n            String key = new String(arg.getRaw());\n            values.add(\"value_for_\" + key);\n          }\n          return (T) values;\n        }\n\n        @Override\n        protected void sleep(long ignored) {\n          throw new RuntimeException(\"This test should never sleep\");\n        }\n      };\n\n      // Use the standard executeMultiShardCommand method\n      List<String> result = testMe.executeMultiShardCommand(mgetCommands);\n\n      // Expected correct order based on input\n      List<String> expectedCorrectOrder = java.util.Arrays.asList(\"value_for_\" + key1,\n        \"value_for_\" + key2, \"value_for_\" + key3);\n\n      // THE KEY ASSERTION: Values must be in the same order as input keys\n      assertEquals(expectedCorrectOrder, result,\n        \"MGET multi-shard should return values in the same order as input keys. \" + \"Input keys: [\"\n            + key1 + \", \" + key2 + \", \" + key3 + \"], slots: [100, 200, 100]\");\n    }\n  }\n\n  /**\n   * This test verifies that when keys are sorted by hash slot (consecutive keys belong to the same\n   * slot), they are combined into a single command for optimal batching. For example, keys mapping\n   * to slots [A, A, B, B] should result in 2 commands (not 4), with keys grouped as:\n   * command1=[key1, key2], command2=[key3, key4].\n   */\n  @Test\n  public void mgetMultiShardCombinesConsecutiveKeysWithSameSlotIntoOneCommand() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Connection connection = mock(Connection.class);\n    when(connectionHandler.getConnection(ArgumentMatchers.any(CommandArguments.class)))\n        .thenReturn(connection);\n\n    String key1 = \"key1\";\n    String key2 = \"key2\";\n    String key3 = \"key3\";\n    String key4 = \"key4\";\n\n    // Mock JedisClusterCRC16.getSlot to return sorted/grouped slots: [A, A, B, B]\n    try (org.mockito.MockedStatic<JedisClusterCRC16> mockedStatic = Mockito\n        .mockStatic(JedisClusterCRC16.class)) {\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key1)).thenReturn(100);\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key2)).thenReturn(100);\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key3)).thenReturn(200);\n      mockedStatic.when(() -> JedisClusterCRC16.getSlot(key4)).thenReturn(200);\n\n      ClusterCommandObjects commandObjects = new ClusterCommandObjects();\n\n      // Consecutive keys with the same slot should be combined into one command\n      List<CommandObject<List<String>>> mgetCommands = commandObjects.mgetMultiShard(key1, key2,\n        key3, key4);\n      assertEquals(2, mgetCommands.size(),\n        \"Should have 2 commands for sorted slots [A, A, B, B], not 4\");\n\n      // Execute MGET with the standard executeMultiShardCommand\n      ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10,\n          Duration.ZERO, StaticCommandFlagsRegistry.registry()) {\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public <T> T execute(Connection conn, CommandObject<T> commandObject) {\n          List<String> values = new ArrayList<>();\n          CommandArguments args = commandObject.getArguments();\n          boolean isFirst = true;\n          for (redis.clients.jedis.args.Rawable arg : args) {\n            if (isFirst) {\n              isFirst = false;\n              continue;\n            }\n            String key = new String(arg.getRaw());\n            values.add(\"value_for_\" + key);\n          }\n          return (T) values;\n        }\n\n        @Override\n        protected void sleep(long ignored) {\n          throw new RuntimeException(\"This test should never sleep\");\n        }\n      };\n\n      // Use the standard executeMultiShardCommand method\n      List<String> result = testMe.executeMultiShardCommand(mgetCommands);\n\n      // Expected correct order based on input\n      List<String> expectedCorrectOrder = java.util.Arrays.asList(\"value_for_\" + key1,\n        \"value_for_\" + key2, \"value_for_\" + key3, \"value_for_\" + key4);\n\n      // Verify values are in the correct order\n      assertEquals(expectedCorrectOrder, result,\n        \"MGET multi-shard should return values in the same order as input keys\");\n    }\n  }\n\n  /**\n   * This test verifies the fix for the race condition in RoundRobinConnectionResolver where the\n   * round-robin counter could cause IndexOutOfBoundsException on topology change.\n   * <p>\n   * The bug occurred because: 1. Thread A with a 5-node list sets counter to 4 (valid for its list)\n   * 2. Thread B with a 3-node list (after topology change) reads counter value 4 3. Thread B uses 4\n   * as index into a 3-element list -> IndexOutOfBoundsException\n   * <p>\n   * The fix applies modulo with the current list size after reading the counter, ensuring the index\n   * is always valid for the current thread's node list.\n   */\n  @Test\n  public void runKeylessCommandDoesNotThrowIndexOutOfBoundsOnTopologyChange() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n\n    // Create initial larger connection map (5 nodes)\n    Map<String, ConnectionPool> largeConnectionMap = new java.util.LinkedHashMap<>();\n    ConnectionPool pool1 = mock(ConnectionPool.class);\n    ConnectionPool pool2 = mock(ConnectionPool.class);\n    ConnectionPool pool3 = mock(ConnectionPool.class);\n    ConnectionPool pool4 = mock(ConnectionPool.class);\n    ConnectionPool pool5 = mock(ConnectionPool.class);\n\n    Connection connection1 = mock(Connection.class);\n    Connection connection2 = mock(Connection.class);\n    Connection connection3 = mock(Connection.class);\n    Connection connection4 = mock(Connection.class);\n    Connection connection5 = mock(Connection.class);\n\n    largeConnectionMap.put(\"node1:6379\", pool1);\n    largeConnectionMap.put(\"node2:6379\", pool2);\n    largeConnectionMap.put(\"node3:6379\", pool3);\n    largeConnectionMap.put(\"node4:6379\", pool4);\n    largeConnectionMap.put(\"node5:6379\", pool5);\n\n    when(pool1.getResource()).thenReturn(connection1);\n    when(pool2.getResource()).thenReturn(connection2);\n    when(pool3.getResource()).thenReturn(connection3);\n    when(pool4.getResource()).thenReturn(connection4);\n    when(pool5.getResource()).thenReturn(connection5);\n\n    // Create smaller connection map (2 nodes) - simulates topology change\n    Map<String, ConnectionPool> smallConnectionMap = new java.util.LinkedHashMap<>();\n    smallConnectionMap.put(\"node1:6379\", pool1);\n    smallConnectionMap.put(\"node2:6379\", pool2);\n\n    // Start with large map, then switch to small map\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(largeConnectionMap)\n        .thenReturn(largeConnectionMap).thenReturn(largeConnectionMap)\n        .thenReturn(largeConnectionMap)\n        // After 4 calls, switch to smaller topology\n        .thenReturn(smallConnectionMap).thenReturn(smallConnectionMap)\n        .thenReturn(smallConnectionMap).thenReturn(smallConnectionMap)\n        .thenReturn(smallConnectionMap);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // Execute with large topology (5 nodes) - this advances the round-robin counter\n    for (int i = 0; i < 4; i++) {\n      String result = invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n      assertEquals(\"OK\", result);\n    }\n\n    // At this point, the internal counter could be at value 4 (pointing to 5th node)\n    // Now topology changes to only 2 nodes\n\n    // Execute with small topology (2 nodes) - should NOT throw IndexOutOfBoundsException\n    // This is the key assertion: the fix ensures modulo is applied with current list size\n    for (int i = 0; i < 5; i++) {\n      String result = invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n      assertEquals(\"OK\", result, \"Should succeed even when counter value exceeds new list size\");\n    }\n\n    // Verify both maps were used\n    verify(connectionHandler, times(9)).getPrimaryNodesConnectionMap();\n  }\n\n  /**\n   * This test verifies the round-robin counter handles integer overflow gracefully. When the\n   * counter reaches Integer.MAX_VALUE, the next increment wraps to Integer.MIN_VALUE. The fix uses\n   * Math.abs to ensure the modulo result is always non-negative.\n   */\n  @Test\n  public void runKeylessCommandHandlesCounterOverflowGracefully() {\n    ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class);\n    Map<String, ConnectionPool> connectionMap = new java.util.LinkedHashMap<>();\n    ConnectionPool pool = mock(ConnectionPool.class);\n    Connection connection = mock(Connection.class);\n\n    connectionMap.put(\"node1:6379\", pool);\n    connectionMap.put(\"node2:6379\", pool);\n    connectionMap.put(\"node3:6379\", pool);\n\n    when(connectionHandler.getPrimaryNodesConnectionMap()).thenReturn(connectionMap);\n    when(pool.getResource()).thenReturn(connection);\n\n    ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO,\n        StaticCommandFlagsRegistry.registry()) {\n      @Override\n      public <T> T execute(Connection connection, CommandObject<T> commandObject) {\n        return (T) \"OK\";\n      }\n\n      @Override\n      protected void sleep(long ignored) {\n        throw new RuntimeException(\"This test should never sleep\");\n      }\n    };\n\n    // Get the roundRobinConnectionResolver field and set its counter to near MAX_VALUE\n    ConnectionResolver roundRobinResolver = ReflectionTestUtil.getField(testMe,\n      \"roundRobinConnectionResolver\");\n    java.util.concurrent.atomic.AtomicInteger counter = ReflectionTestUtil\n        .getField(roundRobinResolver, \"roundRobinCounter\");\n\n    // Set counter to MAX_VALUE - 1, so next calls will overflow\n    counter.set(Integer.MAX_VALUE - 1);\n\n    // Execute several commands - should NOT throw any exception even when counter overflows\n    for (int i = 0; i < 5; i++) {\n      String result = invokeExecuteKeylessCommand(testMe, KEYLESS_WRITE_COM_OBJECT);\n      assertEquals(\"OK\", result, \"Should handle counter overflow gracefully\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/executors/RetryableCommandExecutorTest.java",
    "content": "package redis.clients.jedis.executors;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n@ExtendWith(MockitoExtension.class)\npublic class RetryableCommandExecutorTest {\n\n  @Mock\n  private ConnectionProvider mockProvider;\n  \n  @Mock\n  private Connection mockConnection;\n  \n  @Mock\n  private CommandObject<String> mockCommandObject;\n\n  @Test\n  public void testConstructorWithNullProvider() {\n    IllegalArgumentException exception = assertThrows(\n        IllegalArgumentException.class,\n        () -> new RetryableCommandExecutor(null, 3, Duration.ofSeconds(1)),\n        \"Should throw IllegalArgumentException when provider is null\"\n    );\n    \n    assertTrue(exception.getMessage().contains(\"provider\"),\n        \"Exception message should mention 'provider'\");\n  }\n  \n  @Test\n  public void testConstructorWithInvalidMaxAttempts() {\n    // Test with zero\n    IllegalArgumentException exceptionZero = assertThrows(\n        IllegalArgumentException.class,\n        () -> new RetryableCommandExecutor(mockProvider, 0, Duration.ofSeconds(1)),\n        \"Should throw IllegalArgumentException when maxAttempts is zero\"\n    );\n    \n    assertTrue(exceptionZero.getMessage().contains(\"maxAttempts\"),\n        \"Exception message should mention 'maxAttempts'\");\n    \n    // Test with negative value\n    IllegalArgumentException exceptionNegative = assertThrows(\n        IllegalArgumentException.class,\n        () -> new RetryableCommandExecutor(mockProvider, -1, Duration.ofSeconds(1)),\n        \"Should throw IllegalArgumentException when maxAttempts is negative\"\n    );\n    \n    assertTrue(exceptionNegative.getMessage().contains(\"maxAttempts\"),\n        \"Exception message should mention 'maxAttempts'\");\n  }\n\n  @Test\n  public void testValidConstruction() {\n    // Should not throw any exceptions\n    assertDoesNotThrow(() -> new RetryableCommandExecutor(mockProvider, 1, Duration.ofSeconds(1)));\n    assertDoesNotThrow(() -> new RetryableCommandExecutor(mockProvider, 3, Duration.ZERO));\n    assertDoesNotThrow(() -> new RetryableCommandExecutor(mockProvider, 10, Duration.ofMinutes(5)));\n  }\n  \n  @Test\n  public void testMaxAttemptsIsRespected() throws Exception {\n    // Set up the mock to return a connection but throw an exception when executing\n    when(mockProvider.getConnection(any())).thenReturn(mockConnection);\n    when(mockConnection.executeCommand(any(CommandObject.class))).thenThrow(new JedisConnectionException(\"Connection failed\"));\n    \n    // Create the executor with exactly 3 attempts\n    final int maxAttempts = 3;\n    RetryableCommandExecutor executor = spy(new RetryableCommandExecutor(mockProvider, maxAttempts, Duration.ofSeconds(10)));\n    \n    // Mock the sleep method to avoid actual sleeping\n    doNothing().when(executor).sleep(anyLong());\n    \n    // Execute the command and expect an exception\n    assertThrows(JedisException.class, () -> executor.executeCommand(mockCommandObject));\n    \n    // Verify that we tried exactly maxAttempts times\n    verify(mockProvider, times(maxAttempts)).getConnection(any());\n    verify(mockConnection, times(maxAttempts)).close();\n  }\n  \n  @Test\n  public void testExecuteCommandWithNoRetries() throws Exception {\n    // Set up the mock to return a connection and have it execute the command successfully\n    when(mockProvider.getConnection(any())).thenReturn(mockConnection);\n    when(mockConnection.executeCommand(mockCommandObject)).thenReturn(\"success\");\n    \n    // Create the executor with just 1 attempt (no retries)\n    RetryableCommandExecutor executor = new RetryableCommandExecutor(mockProvider, 1, Duration.ofSeconds(1));\n    \n    // Execute the command\n    String result = executor.executeCommand(mockCommandObject);\n    \n    // Verify the result and that the connection was closed\n    assertEquals(\"success\", result);\n    verify(mockConnection, times(1)).close();\n    verify(mockProvider, times(1)).getConnection(any());\n  }\n  \n  @Test\n  public void testMaxAttemptsExceeded() throws Exception {\n    // Set up the mock to return a connection but throw an exception when executing\n    when(mockProvider.getConnection(any())).thenReturn(mockConnection);\n    when(mockConnection.executeCommand(any(CommandObject.class))).thenThrow(new JedisConnectionException(\"Connection failed\"));\n    \n    // Create the executor with 3 attempts\n    RetryableCommandExecutor executor = spy(new RetryableCommandExecutor(mockProvider, 3, Duration.ofSeconds(1)));\n    \n    // Mock the sleep method to avoid actual sleeping\n    doNothing().when(executor).sleep(anyLong());\n    \n    // Execute the command and expect an exception\n    JedisException exception = assertThrows(\n        JedisException.class,\n        () -> executor.executeCommand(mockCommandObject),\n        \"Should throw JedisException when max attempts are exceeded\"\n    );\n    \n    // Verify the exception and that we tried the correct number of times\n    assertEquals(\"No more attempts left.\", exception.getMessage());\n    assertEquals(1, exception.getSuppressed().length);\n    verify(mockProvider, times(3)).getConnection(any());\n    verify(mockConnection, times(3)).close();\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/executors/aggregators/ClusterReplyAggregatorTest.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.CommandFlagsRegistry;\nimport redis.clients.jedis.exceptions.UnsupportedAggregationException;\nimport redis.clients.jedis.util.ByteArrayMapMatcher;\nimport redis.clients.jedis.util.JedisByteHashMap;\nimport redis.clients.jedis.util.JedisByteMap;\nimport redis.clients.jedis.util.JedisByteMapMatcher;\nimport redis.clients.jedis.util.KeyValue;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.arrayContainingInAnyOrder;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class ClusterReplyAggregatorTest {\n\n  // ==================== aggregateAllSucceeded Tests ====================\n  // Per Redis ALL_SUCCEEDED spec: returns successfully only if there are no error replies.\n  // Error handling is done separately by the caller (MultiNodeResultAggregator.addError()),\n  // so aggregateAllSucceeded simply returns the first reply when aggregating successful responses.\n  @Nested\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class AggregateAllSucceededTests {\n    /**\n     * Provides test cases for ALL_SUCCEEDED aggregator. Each Object[] contains {firstValue,\n     * secondValue, thirdValue}. Includes Long, Integer, Double, String, Boolean.\n     */\n    Stream<Object[]> valuesProvider() {\n      return Stream.of(new Object[] { 42L, 42L, 100L }, // Long\n        new Object[] { 123, 123, 456 }, // Integer\n        new Object[] { 3.14159, 3.14159, 2.71828 }, // Double\n        new Object[] { \"OK\", \"OK\", \"DIFFERENT\" }, // String\n        new Object[] { true, true, false }, // Boolean\n        new Object[] { false, false, true }, // Boolean\n        new Object[] { new byte[] { 1, 2 }, new byte[] { 1, 2 }, new byte[] { 3, 4 } } // byte[]\n      );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"valuesProvider\")\n    void testAggregateAllSucceeded_returnsFirstValue(Object first, Object second, Object third) {\n      @SuppressWarnings(\"unchecked\")\n      ClusterReplyAggregator<Object> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.ALL_SUCCEEDED);\n\n      // first addition\n      aggregator.add(first);\n      assertThat(\"First value should be result\", aggregator.getResult(), equalTo(first));\n\n      // add same value again\n      aggregator.add(second);\n      assertThat(\"Result should remain first value\", aggregator.getResult(), equalTo(first));\n\n      // add a different value\n      aggregator.add(third);\n      assertThat(\"Result should still remain first value\", aggregator.getResult(), equalTo(first));\n    }\n  }\n  // ==================== aggregateDefault - List<String> Tests ====================\n\n  @Nested\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  public class AggregateDefaultTests {\n\n    // ==================== aggregateDefault - Unsupported Types Throw Exception\n    // ====================\n\n    @Test\n    public void testAggregateDefault_nonListTypes_throwsUnsupportedAggregationException() {\n      String first = \"existing\";\n\n      ClusterReplyAggregator<String> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n      UnsupportedAggregationException exception = assertThrows(\n        UnsupportedAggregationException.class, () -> aggregator.add(first));\n\n      assertTrue(exception.getMessage().contains(\"DEFAULT policy requires\"),\n        \"Exception message should describe the policy requirement\");\n      assertTrue(exception.getMessage().contains(\"String\"),\n        \"Exception message should mention the unsupported type\");\n    }\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class AggregateDefaultListTests {\n\n      /**\n       * Provides test cases: {firstList, secondList, expectedResult}.\n       */\n      Stream<Object[]> listProvider() {\n        return Stream.of(\n          // aggregate non empty lists\n          new Object[] { Arrays.asList(\"key1\", \"key2\"), Arrays.asList(\"key3\", \"key4\"),\n              Arrays.asList(\"key1\", \"key2\", \"key3\", \"key4\") },\n          // aggregate null and non empty list\n          new Object[] { null, Arrays.asList(\"key1\", \"key2\"), Arrays.asList(\"key1\", \"key2\") },\n          // aggregate empty and non empty list\n          new Object[] { Collections.emptyList(), Arrays.asList(\"key1\", \"key2\"),\n              Arrays.asList(\"key1\", \"key2\") },\n          // aggregate empty and non empty list\n          new Object[] { new ArrayList<>(), Arrays.asList(\"key1\", \"key2\"),\n              Arrays.asList(\"key1\", \"key2\") },\n          // aggregate non empty and empty list\n          new Object[] { Arrays.asList(\"key1\", \"key2\"), new ArrayList<>(),\n              Arrays.asList(\"key1\", \"key2\") },\n          // aggregate two empty lists\n          new Object[] { new ArrayList<>(), new ArrayList<>(), new ArrayList<>() }, // both empty →\n                                                                                    // empty\n          // aggregate two null lists\n          new Object[] { null, null, null });\n      }\n\n      @ParameterizedTest\n      @MethodSource(\"listProvider\")\n      void testAggregateDefault_lists(List<String> first, List<String> second,\n          List<String> expected) {\n        ClusterReplyAggregator<List<String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n\n        aggregator.add(first);\n        aggregator.add(second);\n\n        List<String> result = aggregator.getResult();\n        assertThat(\"Aggregated list should match expected\", result, equalTo(expected));\n      }\n\n      // ==================== aggregateDefault - List<byte[]> Tests ====================\n\n      @Test\n      public void testAggregateDefault_twoByteArrayLists_concatenatesThem() {\n        List<byte[]> first = new ArrayList<>(\n            Arrays.asList(new byte[] { 1, 2 }, new byte[] { 3, 4 }));\n        List<byte[]> second = new ArrayList<>(\n            Arrays.asList(new byte[] { 5, 6 }, new byte[] { 7, 8 }));\n\n        ClusterReplyAggregator<List<byte[]>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n        aggregator.add(first);\n        aggregator.add(second);\n        List<byte[]> result = aggregator.getResult();\n        assertEquals(4, result.size(), \"Should contain all byte arrays from both lists\");\n        assertThat(result, contains(new byte[] { 1, 2 }, new byte[] { 3, 4 }, new byte[] { 5, 6 },\n          new byte[] { 7, 8 }));\n      }\n\n      // ==================== aggregateDefault - Different List Implementations ====================\n\n      @Test\n      public void testAggregateDefault_linkedListAndArrayList() {\n        List<String> first = new LinkedList<>(Arrays.asList(\"a\", \"b\"));\n        List<String> second = new ArrayList<>(Arrays.asList(\"c\", \"d\"));\n\n        ClusterReplyAggregator<List<String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n        aggregator.add(first);\n        aggregator.add(second);\n\n        List<String> result = aggregator.getResult();\n        assertEquals(4, result.size(), \"Should concatenate different list implementations\");\n        assertEquals(Arrays.asList(\"a\", \"b\", \"c\", \"d\"), result);\n      }\n      // ==================== aggregateDefault - Mutates Existing ArrayList In Place\n      // ====================\n\n      @Test\n      public void testAggregateDefault_singleReplyDoesNotCreateNewList() {\n        List<String> first = null;\n        List<String> second = new ArrayList<>(Arrays.asList(\"c\", \"d\"));\n\n        ClusterReplyAggregator<List<String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n        aggregator.add(first);\n        aggregator.add(second);\n\n        List<String> result = aggregator.getResult();\n\n        // If single non null reply, Result should be the same instance\n        assertSame(second, result, \"Result should be the same instance as first non null reply\");\n        assertThat(result, contains(\"c\", \"d\"));\n        assertThat(result, sameInstance(second));\n      }\n    }\n\n    // ==================== aggregateDefault - Map Tests ====================\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class AggregateDefaultMapTests {\n\n      /**\n       * Provides test cases: {firstMap, secondMap, expectedResult}.\n       */\n      Stream<Object[]> mapProvider() {\n        Map<String, Integer> firstMap = new HashMap<>();\n        firstMap.put(\"key1\", 1);\n        firstMap.put(\"key2\", 2);\n\n        Map<String, Integer> secondMap = new HashMap<>();\n        secondMap.put(\"key3\", 3);\n        secondMap.put(\"key4\", 4);\n\n        Map<String, Integer> expectedFirstOnly = new HashMap<>();\n        expectedFirstOnly.put(\"key1\", 1);\n        expectedFirstOnly.put(\"key2\", 2);\n\n        Map<String, Integer> expectedMergedMap = new HashMap<>();\n        expectedMergedMap.put(\"key1\", 1);\n        expectedMergedMap.put(\"key2\", 2);\n        expectedMergedMap.put(\"key3\", 3);\n        expectedMergedMap.put(\"key4\", 4);\n\n        Map<String, Integer> overlappingMap = new HashMap<>();\n        overlappingMap.put(\"key1\", 1);\n        overlappingMap.put(\"key3\", 3);\n\n        Map<String, Integer> expectedOverlappingMap = new HashMap<>();\n        expectedOverlappingMap.put(\"key1\", 1);\n        expectedOverlappingMap.put(\"key2\", 2);\n        expectedOverlappingMap.put(\"key3\", 3);\n\n        return Stream.of(\n          // empty + non-empty → non-empty\n          new Object[] { new HashMap<String, Integer>(), firstMap, expectedFirstOnly },\n          // non-empty + empty → non-empty\n          new Object[] { firstMap, new HashMap<String, Integer>(), expectedFirstOnly },\n          // empty + empty → empty\n          new Object[] { new HashMap<String, Integer>(), new HashMap<String, Integer>(),\n              new HashMap<String, Integer>() },\n          // null + null → null\n          new Object[] { null, null, null },\n          // null + empty → empty\n          new Object[] { null, new HashMap<String, Integer>(), new HashMap<String, Integer>() },\n          // unmodifiableMap + non-empty → non-empty\n          new Object[] { Collections.emptyMap(), firstMap, expectedFirstOnly },\n          // non-empty + unmodifiableMap → non-empty\n          new Object[] { firstMap, Collections.emptyMap(), expectedFirstOnly },\n          // maps with different keys\n          new Object[] { firstMap, secondMap, expectedMergedMap },\n          // maps with overlapping keys, second map takes precedence\n          new Object[] { firstMap, overlappingMap, expectedOverlappingMap }\n\n        );\n\n      }\n\n      @ParameterizedTest\n      @MethodSource(\"mapProvider\")\n      void testAggregateDefault_maps(Map<String, Integer> first, Map<String, Integer> second,\n          Map<String, Integer> expected) {\n        ClusterReplyAggregator<Map<String, Integer>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Map<String, Integer> result = aggregator.getResult();\n        assertThat(\"Aggregated map should match expected\", result, equalTo(expected));\n      }\n\n      @Test\n      public void testAggregateDefault_differentMapImplementations_mergesThem() {\n        Map<String, String> first = new LinkedHashMap<>();\n        first.put(\"a\", \"1\");\n        first.put(\"b\", \"2\");\n\n        Map<String, String> second = new HashMap<>();\n        second.put(\"c\", \"3\");\n        second.put(\"d\", \"4\");\n\n        ClusterReplyAggregator<Map<String, String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Map<String, String> result = aggregator.getResult();\n        assertEquals(4, result.size(), \"Should merge different map implementations\");\n        assertEquals(\"1\", result.get(\"a\"));\n        assertEquals(\"2\", result.get(\"b\"));\n        assertEquals(\"3\", result.get(\"c\"));\n        assertEquals(\"4\", result.get(\"d\"));\n        assertTrue(result instanceof HashMap, \"Result should be a HashMap\");\n      }\n    }\n\n    // ==================== aggregateDefault - Set Tests ====================\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class AggregateDefaultSetTests {\n\n      /**\n       * Provides test cases: {firstSet, secondSet, expectedResult}.\n       */\n      Stream<Object[]> setProvider() {\n        Set<String> nonEmptySet = new HashSet<>(Arrays.asList(\"a\", \"b\"));\n        Set<String> expectedSet = new HashSet<>(Arrays.asList(\"a\", \"b\"));\n\n        return Stream.of(\n          // empty + non-empty → non-empty\n          new Object[] { new HashSet<String>(), nonEmptySet, expectedSet },\n          // non-empty + empty → non-empty\n          new Object[] { nonEmptySet, new HashSet<String>(), expectedSet },\n          // empty + empty → empty\n          new Object[] { new HashSet<String>(), new HashSet<String>(), new HashSet<String>() },\n          // unmodifiableSet + non-empty → non-empty\n          new Object[] { Collections.emptySet(), nonEmptySet, expectedSet },\n          // non-empty + unmodifiableSet → non-empty\n          new Object[] { nonEmptySet, Collections.emptySet(), expectedSet },\n          // sets with overlapping elements, merges without duplicates\n          new Object[] { new HashSet<String>(Arrays.asList(\"a\", \"b\", \"c\")),\n              new HashSet<String>(Arrays.asList(\"b\", \"c\", \"d\")),\n              new HashSet<String>(Arrays.asList(\"a\", \"b\", \"c\", \"d\")) },\n          // sets with different elements, merges all elements\n          new Object[] { new HashSet<String>(Arrays.asList(\"a\", \"b\")),\n              new HashSet<String>(Arrays.asList(\"c\", \"d\")),\n              new HashSet<String>(Arrays.asList(\"a\", \"b\", \"c\", \"d\")) },\n          // different set implementations, merges all elements\n          new Object[] { new LinkedHashSet<String>(Arrays.asList(\"a\", \"b\")),\n              new HashSet<String>(Arrays.asList(\"c\", \"d\")),\n              new HashSet<String>(Arrays.asList(\"a\", \"b\", \"c\", \"d\")) });\n\n      }\n\n      @ParameterizedTest\n      @MethodSource(\"setProvider\")\n      void testAggregateDefault_sets(Set<String> first, Set<String> second, Set<String> expected) {\n        ClusterReplyAggregator<Set<String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Set<String> result = aggregator.getResult();\n        assertThat(\"Aggregated set should match expected\", result, instanceOf(HashSet.class));\n        assertThat(\"Aggregated set should match expected\", result,\n          containsInAnyOrder(expected.toArray(new String[0])));\n      }\n\n      /**\n       * Provides test cases: {firstSet, secondSet, expectedResult}.\n       */\n      Stream<Object[]> setByteArrayProvider() {\n        Set<byte[]> nonEmptySet1 = new HashSet<>(Arrays.asList(\"a\".getBytes(), \"b\".getBytes()));\n        Set<byte[]> nonEmptySet2 = new HashSet<>(Arrays.asList(\"c\".getBytes(), \"d\".getBytes()));\n\n        Set<byte[]> expectedSet = new HashSet<>(\n            Arrays.asList(\"a\".getBytes(), \"b\".getBytes(), \"c\".getBytes(), \"d\".getBytes()));\n        return Stream.of(\n          // set of byte arrays\n          new Object[] { nonEmptySet1, nonEmptySet2, expectedSet },\n          // overlapping elements\n          new Object[] { new HashSet<>(Arrays.asList(\"a\".getBytes(), \"b\".getBytes())),\n              new HashSet<>(Arrays.asList(\"b\".getBytes(), \"c\".getBytes())),\n              new HashSet<>(Arrays.asList(\"a\".getBytes(), \"b\".getBytes(), \"c\".getBytes())) },\n          // empty + non-empty → non-empty\n          new Object[] { new HashSet<byte[]>(), nonEmptySet1, nonEmptySet1 },\n          // non-empty + empty → non-empty\n          new Object[] { nonEmptySet1, new HashSet<byte[]>(), nonEmptySet1 });\n\n      }\n\n      @ParameterizedTest\n      @MethodSource(\"setByteArrayProvider\")\n      void testAggregateDefault_sets_byteArrays(Set<byte[]> first, Set<byte[]> second,\n          Set<byte[]> expected) {\n        ClusterReplyAggregator<Set<byte[]>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Set<byte[]> result = aggregator.getResult();\n        assertThat(\"Aggregated set should match expected\", result, instanceOf(HashSet.class));\n        assertThat(result.toArray(new byte[0][]),\n          arrayContainingInAnyOrder(expected.toArray(new byte[0][])));\n      }\n\n      @Test\n      public void testAggregateDefault_singleSet_returnsSameInstance() {\n        Set<String> first = null;\n        Set<String> second = new HashSet<>(Arrays.asList(\"c\", \"d\"));\n\n        ClusterReplyAggregator<Set<String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Set<String> result = aggregator.getResult();\n\n        // ClusterReplyAggregator mutates the first set in place\n        assertThat(result, sameInstance(second));\n        assertThat(result, contains(\"c\", \"d\"));\n      }\n    }\n\n    // ==================== aggregateDefault - JedisByteHashMap Tests ====================\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class AggregateDefaultJedisByteHashMapTests {\n\n      /**\n       * Provides test cases: {firstMap, secondMap, expectedResult}.\n       */\n      Stream<Object[]> jedisByteHashMapProvider() {\n\n        JedisByteHashMap first = new JedisByteHashMap();\n        first.put(new byte[] { 'k', '1' }, new byte[] { 'v', '1' });\n        first.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });\n\n        JedisByteHashMap second = new JedisByteHashMap();\n        second.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });\n        second.put(new byte[] { 'k', '4' }, new byte[] { 'v', '4' });\n\n        JedisByteHashMap expectedFirstOnly = new JedisByteHashMap();\n        expectedFirstOnly.put(new byte[] { 'k', '1' }, new byte[] { 'v', '1' });\n        expectedFirstOnly.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });\n\n        JedisByteHashMap expectedFirstSecondMerged = new JedisByteHashMap();\n        expectedFirstSecondMerged.put(new byte[] { 'k', '1' }, new byte[] { 'v', '1' });\n        expectedFirstSecondMerged.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });\n        expectedFirstSecondMerged.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });\n        expectedFirstSecondMerged.put(new byte[] { 'k', '4' }, new byte[] { 'v', '4' });\n\n        JedisByteHashMap overlapFirstKeys = new JedisByteHashMap();\n        overlapFirstKeys.put(new byte[] { 'k', '1' }, new byte[] { 'v', 'A' });\n        overlapFirstKeys.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });\n        overlapFirstKeys.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });\n\n        JedisByteHashMap overlapKeysMerged = new JedisByteHashMap();\n        overlapKeysMerged.put(new byte[] { 'k', '1' }, new byte[] { 'v', 'A' });\n        overlapKeysMerged.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });\n        overlapKeysMerged.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });\n\n        return Stream.of(\n          // empty + non-empty → non-empty\n          new Object[] { new JedisByteHashMap(), first, expectedFirstOnly },\n          // non-empty + empty → non-empty\n          new Object[] { first, new JedisByteHashMap(), expectedFirstOnly },\n          // empty + empty → empty\n          new Object[] { new JedisByteHashMap(), new JedisByteHashMap(), new JedisByteHashMap() },\n          // null + null → null\n          new Object[] { null, null, null },\n          // null + empty → empty\n          new Object[] { null, new JedisByteHashMap(), new JedisByteHashMap() },\n          // maps with no overlapping keys\n          new Object[] { first, second, expectedFirstSecondMerged },\n          // maps with overlapping keys, second map takes precedence\n          new Object[] { first, overlapFirstKeys, overlapKeysMerged });\n      }\n\n      @ParameterizedTest\n      @MethodSource(\"jedisByteHashMapProvider\")\n      void testAggregateDefault_jedisByteHashMap(JedisByteHashMap first, JedisByteHashMap second,\n          JedisByteHashMap expected) {\n        ClusterReplyAggregator<Map<byte[], byte[]>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Map<byte[], byte[]> result = aggregator.getResult();\n\n        if (expected == null) {\n          assertNull(result);\n        } else {\n          assertThat(result, instanceOf(JedisByteHashMap.class));\n          assertThat(result, ByteArrayMapMatcher.contentEquals(expected));\n        }\n      }\n\n      @Test\n      public void testAggregateDefault_twoJedisByteHashMapsWithOverlappingKeys_secondMapTakesPrecedence() {\n        JedisByteHashMap first = new JedisByteHashMap();\n        first.put(new byte[] { 's', 'h', 'a', 'r', 'e', 'd' },\n          new byte[] { 'f', 'i', 'r', 's', 't' });\n        first.put(new byte[] { 'u', 'n', 'i', 'q', '1' }, new byte[] { 'v', 'a', 'l', '1' });\n\n        JedisByteHashMap second = new JedisByteHashMap();\n        second.put(new byte[] { 's', 'h', 'a', 'r', 'e', 'd' },\n          new byte[] { 's', 'e', 'c', 'o', 'n', 'd' });\n        second.put(new byte[] { 'u', 'n', 'i', 'q', '2' }, new byte[] { 'v', 'a', 'l', '2' });\n\n        ClusterReplyAggregator<JedisByteHashMap> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n        aggregator.add(first);\n        aggregator.add(second);\n\n        JedisByteHashMap result = aggregator.getResult();\n\n        assertEquals(3, result.size(), \"Should contain merged entries\");\n        assertArrayEquals(new byte[] { 's', 'e', 'c', 'o', 'n', 'd' },\n          result.get(new byte[] { 's', 'h', 'a', 'r', 'e', 'd' }),\n          \"Second map's value should overwrite first\");\n        assertArrayEquals(new byte[] { 'v', 'a', 'l', '1' },\n          result.get(new byte[] { 'u', 'n', 'i', 'q', '1' }));\n        assertArrayEquals(new byte[] { 'v', 'a', 'l', '2' },\n          result.get(new byte[] { 'u', 'n', 'i', 'q', '2' }));\n      }\n    }\n\n    // ==================== aggregateDefault - JedisByteMap Tests ====================\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class AggregateDefaultJedisByteMapTests {\n\n      /**\n       * Provides test cases: {firstMap, secondMap, expectedResult}.\n       */\n      Stream<Object[]> jedisByteMapProvider() {\n\n        JedisByteMap<String> first = new JedisByteMap<>();\n        first.put(new byte[] { 'k', '1' }, \"v1\");\n        first.put(new byte[] { 'k', '2' }, \"v2\");\n\n        JedisByteMap<String> second = new JedisByteMap<>();\n        second.put(new byte[] { 'k', '3' }, \"v3\");\n        second.put(new byte[] { 'k', '4' }, \"v4\");\n\n        JedisByteMap<String> expectedFirstOnly = new JedisByteMap<>();\n        expectedFirstOnly.put(new byte[] { 'k', '1' }, \"v1\");\n        expectedFirstOnly.put(new byte[] { 'k', '2' }, \"v2\");\n\n        JedisByteMap<String> expectedFirstSecondMerged = new JedisByteMap<>();\n        expectedFirstSecondMerged.put(new byte[] { 'k', '1' }, \"v1\");\n        expectedFirstSecondMerged.put(new byte[] { 'k', '2' }, \"v2\");\n        expectedFirstSecondMerged.put(new byte[] { 'k', '3' }, \"v3\");\n        expectedFirstSecondMerged.put(new byte[] { 'k', '4' }, \"v4\");\n\n        JedisByteMap<String> overlapFirstKeys = new JedisByteMap<>();\n        overlapFirstKeys.put(new byte[] { 'k', '1' }, \"vA\");\n        overlapFirstKeys.put(new byte[] { 'k', '2' }, \"v2\");\n        overlapFirstKeys.put(new byte[] { 'k', '3' }, \"v3\");\n\n        JedisByteMap<String> overlapKeysMerged = new JedisByteMap<>();\n        overlapKeysMerged.put(new byte[] { 'k', '1' }, \"vA\");\n        overlapKeysMerged.put(new byte[] { 'k', '2' }, \"v2\");\n        overlapKeysMerged.put(new byte[] { 'k', '3' }, \"v3\");\n\n        return Stream.of(\n          // empty + non-empty → non-empty\n          new Object[] { new JedisByteMap<>(), first, expectedFirstOnly },\n          // non-empty + empty → non-empty\n          new Object[] { first, new JedisByteMap<>(), expectedFirstOnly },\n          // empty + empty → empty\n          new Object[] { new JedisByteMap<>(), new JedisByteMap<>(), new JedisByteMap<>() },\n          // null + null → null\n          new Object[] { null, null, null },\n          // null + empty → empty\n          new Object[] { null, new JedisByteMap<>(), new JedisByteMap<>() },\n          // maps with no overlapping keys\n          new Object[] { first, second, expectedFirstSecondMerged },\n          // maps with overlapping keys, second map takes precedence\n          new Object[] { first, overlapFirstKeys, overlapKeysMerged });\n      }\n\n      @ParameterizedTest\n      @MethodSource(\"jedisByteMapProvider\")\n      void testAggregateDefault_jedisByteHashMap(Map<byte[], String> first,\n          Map<byte[], String> second, Map<byte[], String> expected) {\n        ClusterReplyAggregator<Map<byte[], String>> aggregator = new ClusterReplyAggregator<>(\n            CommandFlagsRegistry.ResponsePolicy.DEFAULT);\n\n        aggregator.add(first);\n        aggregator.add(second);\n\n        Map<byte[], String> result = aggregator.getResult();\n\n        if (expected == null) {\n          assertNull(result);\n        } else {\n          assertThat(result, instanceOf(JedisByteMap.class));\n          assertThat(result, JedisByteMapMatcher.contentEquals(expected));\n        }\n      }\n    }\n  }\n\n  // ==================== aggregateMin - KeyValue Tests ====================\n\n  @Nested\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class AggregateMinTests {\n\n    @Test\n    public void testAggregateMin_keyValueLongLong_returnsMinOfEachComponent() {\n      KeyValue<Long, Long> first = KeyValue.of(10L, 20L);\n      KeyValue<Long, Long> second = KeyValue.of(5L, 25L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MIN);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(5L, result.getKey(), \"Should return minimum key\");\n      assertEquals(20L, result.getValue(), \"Should return minimum value\");\n    }\n\n    @Test\n    public void testAggregateMin_keyValueLongLong_firstSmaller() {\n      KeyValue<Long, Long> first = KeyValue.of(1L, 2L);\n      KeyValue<Long, Long> second = KeyValue.of(10L, 20L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MIN);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(1L, result.getKey(), \"Should return minimum key from first\");\n      assertEquals(2L, result.getValue(), \"Should return minimum value from first\");\n    }\n\n    @Test\n    public void testAggregateMin_keyValueLongLong_secondSmaller() {\n      KeyValue<Long, Long> first = KeyValue.of(10L, 20L);\n      KeyValue<Long, Long> second = KeyValue.of(1L, 2L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MIN);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(1L, result.getKey(), \"Should return minimum key from second\");\n      assertEquals(2L, result.getValue(), \"Should return minimum value from second\");\n    }\n\n    @Test\n    public void testAggregateMin_keyValueLongLong_equalValues() {\n      KeyValue<Long, Long> first = KeyValue.of(5L, 5L);\n      KeyValue<Long, Long> second = KeyValue.of(5L, 5L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MIN);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(5L, result.getKey(), \"Should return equal key\");\n      assertEquals(5L, result.getValue(), \"Should return equal value\");\n    }\n\n    @Test\n    public void testAggregateMin_keyValueStringString_returnsMinOfEachComponent() {\n      KeyValue<String, String> first = KeyValue.of(\"b\", \"y\");\n      KeyValue<String, String> second = KeyValue.of(\"a\", \"z\");\n\n      ClusterReplyAggregator<KeyValue<String, String>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MIN);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<String, String> result = aggregator.getResult();\n\n      assertEquals(\"a\", result.getKey(), \"Should return minimum key\");\n      assertEquals(\"y\", result.getValue(), \"Should return minimum value\");\n    }\n  }\n\n  // ==================== aggregateMax - KeyValue Tests ====================\n\n  @Nested\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class AggregateMaxTests {\n\n    @Test\n    public void testAggregateMax_keyValueLongLong_returnsMaxOfEachComponent() {\n      KeyValue<Long, Long> first = KeyValue.of(10L, 20L);\n      KeyValue<Long, Long> second = KeyValue.of(5L, 25L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MAX);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(10L, result.getKey(), \"Should return maximum key\");\n      assertEquals(25L, result.getValue(), \"Should return maximum value\");\n    }\n\n    @Test\n    public void testAggregateMax_keyValueLongLong_firstLarger() {\n      KeyValue<Long, Long> first = KeyValue.of(10L, 20L);\n      KeyValue<Long, Long> second = KeyValue.of(1L, 2L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MAX);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(10L, result.getKey(), \"Should return maximum key from first\");\n      assertEquals(20L, result.getValue(), \"Should return maximum value from first\");\n    }\n\n    @Test\n    public void testAggregateMax_keyValueLongLong_secondLarger() {\n      KeyValue<Long, Long> first = KeyValue.of(1L, 2L);\n      KeyValue<Long, Long> second = KeyValue.of(10L, 20L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MAX);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(10L, result.getKey(), \"Should return maximum key from second\");\n      assertEquals(20L, result.getValue(), \"Should return maximum value from second\");\n    }\n\n    @Test\n    public void testAggregateMax_keyValueLongLong_equalValues() {\n      KeyValue<Long, Long> first = KeyValue.of(5L, 5L);\n      KeyValue<Long, Long> second = KeyValue.of(5L, 5L);\n\n      ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MAX);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<Long, Long> result = aggregator.getResult();\n\n      assertEquals(5L, result.getKey(), \"Should return equal key\");\n      assertEquals(5L, result.getValue(), \"Should return equal value\");\n    }\n\n    @Test\n    public void testAggregateMax_keyValueStringString_returnsMaxOfEachComponent() {\n      KeyValue<String, String> first = KeyValue.of(\"b\", \"y\");\n      KeyValue<String, String> second = KeyValue.of(\"a\", \"z\");\n\n      ClusterReplyAggregator<KeyValue<String, String>> aggregator = new ClusterReplyAggregator<>(\n          CommandFlagsRegistry.ResponsePolicy.AGG_MAX);\n      aggregator.add(first);\n      aggregator.add(second);\n\n      KeyValue<String, String> result = aggregator.getResult();\n\n      assertEquals(\"b\", result.getKey(), \"Should return maximum key\");\n      assertEquals(\"z\", result.getValue(), \"Should return maximum value\");\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/executors/aggregators/MultiNodeResultAggregatorTest.java",
    "content": "package redis.clients.jedis.executors.aggregators;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.CommandFlagsRegistry.ResponsePolicy;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.exceptions.JedisBroadcastException;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\n\npublic class MultiNodeResultAggregatorTest {\n\n  private static final HostAndPort NODE_1 = HostAndPort.from(\"127.0.0.1:7001\");\n  private static final HostAndPort NODE_2 = HostAndPort.from(\"127.0.0.1:7002\");\n  private static final HostAndPort NODE_3 = HostAndPort.from(\"127.0.0.1:7003\");\n  private static final HostAndPort UNKNOWN_NODE = HostAndPort.from(\"unknown:0\");\n\n  // ==================== Constructor Tests ====================\n\n  @Test\n  public void testConstructor_initializesWithResponsePolicy() {\n    MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n        ResponsePolicy.ALL_SUCCEEDED);\n\n    assertEquals(ResponsePolicy.ALL_SUCCEEDED, aggregator.getResponsePolicy(),\n      \"Should store the provided response policy\");\n  }\n\n  @Test\n  public void testConstructor_initializesWithDifferentPolicies() {\n    for (ResponsePolicy policy : ResponsePolicy.values()) {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(policy);\n      assertEquals(policy, aggregator.getResponsePolicy(),\n        \"Should store the response policy: \" + policy);\n    }\n  }\n\n  @Nested\n  class BasicTests {\n    // ==================== getResponsePolicy Tests ====================\n\n    @Test\n    public void testGetResponsePolicy_returnsCorrectPolicy() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_SUM);\n\n      assertEquals(ResponsePolicy.AGG_SUM, aggregator.getResponsePolicy(),\n        \"getResponsePolicy should return the policy passed to constructor\");\n    }\n\n    // ==================== addSuccess(HostAndPort, T) Tests ====================\n    @Test\n    public void testAddSuccess_withNode_addsToRepliesMap() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addSuccess(NODE_1, \"OK\");\n      aggregator.addError(NODE_2, new RuntimeException(\"error\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(2, replies.size(), \"Should have entries for both nodes\");\n      assertEquals(\"OK\", replies.get(NODE_1), \"Should contain the success reply for NODE_1\");\n      assertTrue(replies.get(NODE_2) instanceof RuntimeException,\n        \"Should contain the error for NODE_2\");\n    }\n\n    @Test\n    public void testAddSuccess_withNullNode_aggregatesResult() {\n      MultiNodeResultAggregator<List<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(null, Collections.singletonList(\"S1\"));\n      aggregator.addSuccess(null, Collections.singletonList(\"S2\"));\n      List<String> result = aggregator.getResult();\n\n      assertThat(result, contains(\"S1\", \"S2\"));\n    }\n\n    @Test\n    public void testAddSuccess_withNullNode_doesNotAddToRepliesMap() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addSuccess(null, \"result\");\n      aggregator.addError(NODE_1, new RuntimeException(\"error\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(1, replies.size(), \"Should only have entry for NODE_1, not null node\");\n      assertFalse(replies.containsKey(null), \"Should not contain null key\");\n    }\n\n    // ==================== addSuccess(T) Tests ====================\n\n    @Test\n    public void testAddSuccess_withoutNode_recordsResult() {\n      MultiNodeResultAggregator<List<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(Collections.singletonList(\"S1\"));\n\n      assertThat(aggregator.getResult(), contains(\"S1\"));\n    }\n\n    @Test\n    public void testAddSuccess_withoutNode_aggregatesMultipleResults() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_SUM);\n\n      aggregator.addSuccess(10L);\n      aggregator.addSuccess(20L);\n      aggregator.addSuccess(30L);\n      Long result = aggregator.getResult();\n\n      assertEquals(60L, result, \"Should aggregate results using AGG_SUM policy\");\n    }\n\n    // ==================== addError(HostAndPort, Exception) Tests ====================\n\n    @Test\n    public void testAddError_withNode_recordsErrorAndNode() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n      RuntimeException error = new RuntimeException(\"Connection failed\");\n\n      aggregator.addError(NODE_1, error);\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(1, replies.size(), \"Should have one error entry\");\n      assertSame(error, replies.get(NODE_1), \"Should contain the exception for NODE_1\");\n    }\n\n    @Test\n    public void testAddError_multipleNodes_recordsAllErrors() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n      RuntimeException error1 = new RuntimeException(\"Error 1\");\n      RuntimeException error2 = new RuntimeException(\"Error 2\");\n\n      aggregator.addError(NODE_1, error1);\n      aggregator.addError(NODE_2, error2);\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(2, replies.size(), \"Should have two error entries\");\n      assertSame(error1, replies.get(NODE_1));\n      assertSame(error2, replies.get(NODE_2));\n    }\n\n    // ==================== addError(Exception) Tests ====================\n\n    @Test\n    public void testAddError_withJedisClusterOperationException_extractsNode() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n      JedisClusterOperationException error = new JedisClusterOperationException(\"Cluster error\",\n          NODE_1);\n\n      aggregator.addError(error);\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(1, replies.size(), \"Should have one error entry\");\n      assertTrue(replies.containsKey(NODE_1),\n        \"Should extract node from JedisClusterOperationException\");\n      assertSame(error, replies.get(NODE_1), \"Should contain the original exception\");\n    }\n\n    @Test\n    public void testAddError_withJedisClusterOperationException_nullNode_usesUnknown() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n      // JedisClusterOperationException without node (null)\n      JedisClusterOperationException error = new JedisClusterOperationException(\"Cluster error\");\n\n      aggregator.addError(error);\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(1, replies.size(), \"Should have one error entry\");\n      assertTrue(replies.containsKey(UNKNOWN_NODE), \"Should use unknown:0 when node is null\");\n    }\n\n    @Test\n    public void testAddError_withRegularException_usesUnknownNode() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n      RuntimeException error = new RuntimeException(\"Generic error\");\n\n      aggregator.addError(error);\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(1, replies.size(), \"Should have one error entry\");\n      assertTrue(replies.containsKey(UNKNOWN_NODE),\n        \"Should use unknown:0 for non-JedisClusterOperationException\");\n      assertSame(error, replies.get(UNKNOWN_NODE));\n    }\n\n    // ==================== getResult() - ONE_SUCCEEDED Policy Tests ====================\n\n    @Test\n    public void testGetResult_oneSucceeded_allNodesSucceed_returnsAggregatedResult() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      aggregator.addSuccess(NODE_1, \"OK\");\n      aggregator.addSuccess(NODE_2, \"OK\");\n      aggregator.addSuccess(NODE_3, \"OK\");\n\n      String result = aggregator.getResult();\n      assertEquals(\"OK\", result, \"Should return successful result when all nodes succeed\");\n    }\n\n    @Test\n    public void testGetResult_oneSucceeded_oneNodeSucceedsOthersFail_returnsSuccess() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Error 1\"));\n      aggregator.addSuccess(NODE_2, \"OK\");\n      aggregator.addError(NODE_3, new RuntimeException(\"Error 3\"));\n\n      // ONE_SUCCEEDED should return success if at least one node succeeded\n      String result = aggregator.getResult();\n      assertEquals(\"OK\", result, \"Should return success if at least one node succeeded\");\n    }\n\n    @Test\n    public void testGetResult_oneSucceeded_allNodesFail_throwsException() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Error 1\"));\n      aggregator.addError(NODE_2, new RuntimeException(\"Error 2\"));\n      aggregator.addError(NODE_3, new RuntimeException(\"Error 3\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult(), \"Should throw JedisBroadcastException when all nodes fail\");\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(3, replies.size(), \"Should contain all error replies\");\n    }\n\n    @Test\n    public void testGetResult_oneSucceeded_mixedResults_returnsFirstSuccess() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Error\"));\n      aggregator.addSuccess(NODE_2, 42L);\n      aggregator.addSuccess(NODE_3, 100L);\n\n      Long result = aggregator.getResult();\n      // ONE_SUCCEEDED returns the first successful result (existing value)\n      assertEquals(42L, result, \"Should return the first successful result\");\n    }\n\n  }\n\n  // ==================== getResult() - ALL_SUCCEEDED Policy Tests ====================\n  @Nested\n  class AllSucceededPolicyTests {\n    @Test\n    public void testGetResult_allSucceeded_allNodesSucceed_returnsResult() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addSuccess(NODE_1, \"OK\");\n      aggregator.addSuccess(NODE_2, \"OK\");\n\n      String result = aggregator.getResult();\n      assertEquals(\"OK\", result, \"Should return result when all nodes succeed with equal values\");\n    }\n\n    @Test\n    public void testGetResult_allSucceeded_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addSuccess(NODE_1, \"OK\");\n      aggregator.addError(NODE_2, new RuntimeException(\"Connection failed\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult(), \"Should throw JedisBroadcastException when any node fails\");\n\n      assertTrue(ex.getMessage().contains(\"failed\"),\n        \"Exception message should indicate broadcast failure\");\n    }\n\n    @Test\n    public void testGetResult_allSucceeded_allNodesFail_throwsException() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Error 1\"));\n      aggregator.addError(NODE_2, new RuntimeException(\"Error 2\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"Should throw JedisBroadcastException when all nodes fail\");\n    }\n  }\n\n  // ==================== getResult() - DEFAULT Policy Tests ====================\n  // Nested class for comprehensive DEFAULT policy testing\n\n  @Nested\n  class DefaultPolicyTests {\n\n    @Test\n    public void testGetResult_default_allNodesSucceed_returnsResult() {\n      MultiNodeResultAggregator<ArrayList<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(NODE_1, new ArrayList<>(Collections.singletonList(\"a\")));\n      aggregator.addSuccess(NODE_2, new ArrayList<>(Collections.singletonList(\"b\")));\n\n      ArrayList<String> result = aggregator.getResult();\n      assertEquals(new ArrayList<>(Arrays.asList(\"a\", \"b\")), result,\n        \"Should return result when all nodes succeed\");\n    }\n\n    @Test\n    public void testGetResult_default_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<ArrayList<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(NODE_1, new ArrayList<>(Collections.singletonList(\"a\")));\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"DEFAULT policy should throw when any node fails\");\n    }\n  }\n\n  // ==================== getResult() - AGG_SUM Policy Tests ====================\n  @Nested\n  class AggSumPolicyTests {\n\n    @Test\n    public void testGetResult_aggSum_sumsLongResults() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_SUM);\n\n      aggregator.addSuccess(NODE_1, 5L);\n      aggregator.addSuccess(NODE_2, 10L);\n      aggregator.addSuccess(NODE_3, 15L);\n\n      Long result = aggregator.getResult();\n      assertEquals(30L, result, \"Should sum all Long results\");\n    }\n\n    @Test\n    public void testGetResult_aggSum_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_SUM);\n\n      aggregator.addSuccess(NODE_1, 5L);\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"AGG_SUM should throw when any node fails\");\n    }\n\n  }\n  // ==================== getResult() - AGG_MIN Policy Tests ====================\n\n  @Nested\n  class AggMinPolicyTests {\n\n    @Test\n    public void testGetResult_aggMin_returnsMinimumValue() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_MIN);\n\n      aggregator.addSuccess(NODE_1, 100L);\n      aggregator.addSuccess(NODE_2, 50L);\n      aggregator.addSuccess(NODE_3, 75L);\n\n      Long result = aggregator.getResult();\n      assertEquals(50L, result, \"Should return minimum Long value\");\n    }\n\n    @Test\n    public void testGetResult_aggMin_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_MIN);\n\n      aggregator.addSuccess(NODE_1, 100L);\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"AGG_MIN should throw when any node fails\");\n    }\n  }\n\n  // ==================== getResult() - AGG_MAX Policy Tests ====================\n\n  @Nested\n  class AggMaxPolicyTests {\n\n    @Test\n    public void testGetResult_aggMax_returnsMaximumValue() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_MAX);\n\n      aggregator.addSuccess(NODE_1, 25L);\n      aggregator.addSuccess(NODE_2, 100L);\n      aggregator.addSuccess(NODE_3, 75L);\n\n      Long result = aggregator.getResult();\n      assertEquals(100L, result, \"Should return maximum Long value\");\n    }\n\n    @Test\n    public void testGetResult_aggMax_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_MAX);\n\n      aggregator.addSuccess(NODE_1, 100L);\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"AGG_MAX should throw when any node fails\");\n    }\n  }\n\n  // ==================== getResult() - AGG_LOGICAL_AND Policy Tests ====================\n\n  @Nested\n  class AggLogicalAndPolicyTests {\n\n    @Test\n    public void testGetResult_aggLogicalAnd_allTrue_returnsTrue() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_LOGICAL_AND);\n\n      aggregator.addSuccess(NODE_1, 1L);\n      aggregator.addSuccess(NODE_2, 1L);\n      aggregator.addSuccess(NODE_3, 1L);\n\n      Long result = aggregator.getResult();\n      assertEquals(1L, result, \"Logical AND of all true (1L) should be 1L\");\n    }\n\n    @Test\n    public void testGetResult_aggLogicalAnd_oneFalse_returnsFalse() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_LOGICAL_AND);\n\n      aggregator.addSuccess(NODE_1, 1L);\n      aggregator.addSuccess(NODE_2, 0L);\n      aggregator.addSuccess(NODE_3, 1L);\n\n      Long result = aggregator.getResult();\n      assertEquals(0L, result, \"Logical AND with one false (0L) should be 0L\");\n    }\n\n    @Test\n    public void testGetResult_aggLogicalAnd_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_LOGICAL_AND);\n\n      aggregator.addSuccess(NODE_1, 1L);\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"AGG_LOGICAL_AND should throw when any node fails\");\n    }\n  }\n\n  // ==================== getResult() - AGG_LOGICAL_OR Policy Tests ====================\n\n  @Nested\n  class AggLogicalOrPolicyTests {\n\n    @Test\n    public void testGetResult_aggLogicalOr_allFalse_returnsFalse() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_LOGICAL_OR);\n\n      aggregator.addSuccess(NODE_1, 0L);\n      aggregator.addSuccess(NODE_2, 0L);\n      aggregator.addSuccess(NODE_3, 0L);\n\n      Long result = aggregator.getResult();\n      assertEquals(0L, result, \"Logical OR of all false (0L) should be 0L\");\n    }\n\n    @Test\n    public void testGetResult_aggLogicalOr_oneTrue_returnsTrue() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_LOGICAL_OR);\n\n      aggregator.addSuccess(NODE_1, 0L);\n      aggregator.addSuccess(NODE_2, 1L);\n      aggregator.addSuccess(NODE_3, 0L);\n\n      Long result = aggregator.getResult();\n      assertEquals(1L, result, \"Logical OR with one true (1L) should be 1L\");\n    }\n\n    @Test\n    public void testGetResult_aggLogicalOr_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_LOGICAL_OR);\n\n      aggregator.addSuccess(NODE_1, 1L);\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"AGG_LOGICAL_OR should throw when any node fails\");\n    }\n  }\n\n  // ==================== getResult() - SPECIAL Policy Tests ====================\n\n  @Nested\n  class SpecialPolicyTests {\n\n    @Test\n    public void testGetResult_special_returnsFirstResult() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.SPECIAL);\n\n      aggregator.addSuccess(NODE_1, \"first\");\n      aggregator.addSuccess(NODE_2, \"second\");\n\n      String result = aggregator.getResult();\n      assertEquals(\"first\", result, \"SPECIAL policy should return existing/first result\");\n    }\n\n    @Test\n    public void testGetResult_special_oneNodeFails_throwsException() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.SPECIAL);\n\n      aggregator.addSuccess(NODE_1, \"first\");\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"SPECIAL policy should throw when any node fails\");\n    }\n  }\n\n  // ==================== Edge Case Tests ====================\n\n  @Nested\n  class EdgeCaseTests {\n\n    @Test\n    public void testGetResult_noResultsAdded_returnsNull() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      String result = aggregator.getResult();\n      assertNull(result, \"Should return null when no results have been added\");\n    }\n\n    @Test\n    public void testGetResult_singleSuccess_returnsResult() {\n      MultiNodeResultAggregator<List<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(NODE_1, Collections.singletonList(\"S1\"));\n\n      List<String> result = aggregator.getResult();\n      assertThat(result, contains(\"S1\"));\n    }\n\n    @Test\n    public void testGetResult_singleError_throwsException() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Single error\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      assertEquals(1, ex.getReplies().size(), \"Should have one error reply\");\n    }\n\n    @Test\n    public void testAddSuccess_nullResult_handledCorrectly() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(NODE_1, null);\n\n      String result = aggregator.getResult();\n      assertNull(result, \"Should handle null result correctly\");\n    }\n\n    @Test\n    public void testAddSuccess_nullResult_followedByRealResult() {\n      MultiNodeResultAggregator<List<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(NODE_1, null);\n      aggregator.addSuccess(NODE_2, Collections.singletonList(\"S1\"));\n\n      assertThat(aggregator.getResult(), contains(\"S1\"));\n    }\n\n    @Test\n    public void testAddSuccess_realResult_followedByNull() {\n      MultiNodeResultAggregator<List<String>> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.DEFAULT);\n\n      aggregator.addSuccess(NODE_1, Collections.singletonList(\"S1\"));\n      aggregator.addSuccess(NODE_2, null);\n\n      List<String> result = aggregator.getResult();\n      assertThat(result, contains(\"S1\"));\n    }\n\n    @Test\n    public void testOneSucceeded_errorThenSuccess_returnsSuccess() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      // First add error, then success\n      aggregator.addError(NODE_1, new RuntimeException(\"Error\"));\n      aggregator.addSuccess(NODE_2, \"success\");\n\n      String result = aggregator.getResult();\n      assertEquals(\"success\", result,\n        \"ONE_SUCCEEDED should return success even if error came first\");\n    }\n\n    @Test\n    public void testOneSucceeded_successThenError_returnsSuccess() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      // First add success, then error\n      aggregator.addSuccess(NODE_1, \"success\");\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n\n      String result = aggregator.getResult();\n      assertEquals(\"success\", result,\n        \"ONE_SUCCEEDED should return success even if error came after\");\n    }\n\n    @Test\n    public void testBroadcastException_containsCorrectMessage() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Connection timeout\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      assertTrue(ex.getMessage().contains(\"failed\"),\n        \"JedisBroadcastException should have meaningful message\");\n    }\n\n    @Test\n    public void testBroadcastException_containsMixedReplies() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addSuccess(NODE_1, \"OK\");\n      aggregator.addSuccess(NODE_2, \"OK\");\n      aggregator.addError(NODE_3, new RuntimeException(\"Failed\"));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(3, replies.size(), \"Should contain all replies (successes and errors)\");\n      assertEquals(\"OK\", replies.get(NODE_1));\n      assertEquals(\"OK\", replies.get(NODE_2));\n      assertTrue(replies.get(NODE_3) instanceof RuntimeException);\n    }\n\n    @Test\n    public void testAggregation_multipleSuccessesBeforeError() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_SUM);\n\n      aggregator.addSuccess(NODE_1, 10L);\n      aggregator.addSuccess(NODE_2, 20L);\n      aggregator.addError(NODE_3, new RuntimeException(\"Error\"));\n\n      // Should throw because AGG_SUM is not ONE_SUCCEEDED\n      assertThrows(JedisBroadcastException.class, () -> aggregator.getResult(),\n        \"AGG_SUM should throw on any error, even if successes were aggregated\");\n    }\n\n    @Test\n    public void testOneSucceeded_aggregatesMultipleSuccesses() {\n      // With ONE_SUCCEEDED, aggregation still happens using the policy's aggregation behavior\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ONE_SUCCEEDED);\n\n      aggregator.addSuccess(NODE_1, \"first\");\n      aggregator.addError(NODE_2, new RuntimeException(\"Error\"));\n      aggregator.addSuccess(NODE_3, \"second\");\n\n      String result = aggregator.getResult();\n      // ONE_SUCCEEDED returns existing value (first successful result)\n      assertEquals(\"first\", result, \"ONE_SUCCEEDED should return the first successful result\");\n    }\n\n    @Test\n    public void testMixedAddSuccessMethods() {\n      MultiNodeResultAggregator<Long> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.AGG_SUM);\n\n      // Mix both addSuccess methods\n      aggregator.addSuccess(NODE_1, 10L);\n      aggregator.addSuccess(20L); // Without node\n      aggregator.addSuccess(NODE_2, 30L);\n\n      Long result = aggregator.getResult();\n      assertEquals(60L, result, \"Should aggregate results from both addSuccess methods\");\n    }\n\n    @Test\n    public void testMixedAddErrorMethods() {\n      MultiNodeResultAggregator<String> aggregator = new MultiNodeResultAggregator<>(\n          ResponsePolicy.ALL_SUCCEEDED);\n\n      aggregator.addError(NODE_1, new RuntimeException(\"Error 1\"));\n      aggregator.addError(new RuntimeException(\"Error 2\")); // Will use unknown:0\n      aggregator.addError(new JedisClusterOperationException(\"Error 3\", NODE_2));\n\n      JedisBroadcastException ex = assertThrows(JedisBroadcastException.class,\n        () -> aggregator.getResult());\n\n      Map<HostAndPort, Object> replies = ex.getReplies();\n      assertEquals(3, replies.size(), \"Should have three error entries\");\n      assertTrue(replies.containsKey(NODE_1));\n      assertTrue(replies.containsKey(UNKNOWN_NODE));\n      assertTrue(replies.containsKey(NODE_2));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/failover/FailoverIntegrationTest.java",
    "content": "package redis.clients.jedis.failover;\n\nimport eu.rekawek.toxiproxy.Proxy;\nimport eu.rekawek.toxiproxy.ToxiproxyClient;\nimport eu.rekawek.toxiproxy.model.Toxic;\nimport eu.rekawek.toxiproxy.model.ToxicDirection;\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbClient;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.scenario.RecommendedSettings;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.core.IsEqual.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@Tag(\"failover\")\npublic class FailoverIntegrationTest {\n\n  private static EndpointConfig endpoint1;\n  private static EndpointConfig endpoint2;\n\n  private static final ToxiproxyClient tp = new ToxiproxyClient(\"localhost\", 8474);\n  public static ExecutorService executor;\n  public static Pattern pattern = Pattern.compile(\"run_id:([a-f0-9]+)\");\n  private static Proxy redisProxy1;\n  private static Proxy redisProxy2;\n  private UnifiedJedis jedis1;\n  private UnifiedJedis jedis2;\n  private static String JEDIS1_ID = \"\";\n  private static String JEDIS2_ID = \"\";\n  private MultiDbConnectionProvider provider;\n  private MultiDbClient failoverClient;\n\n  @BeforeAll\n  public static void setupAdminClients() throws IOException {\n    endpoint1 = Endpoints.getRedisEndpoint(\"redis-failover-1\");\n    endpoint2 = Endpoints.getRedisEndpoint(\"redis-failover-2\");\n    if (tp.getProxyOrNull(\"redis-1\") != null) {\n      tp.getProxy(\"redis-1\").delete();\n    }\n    if (tp.getProxyOrNull(\"redis-2\") != null) {\n      tp.getProxy(\"redis-2\").delete();\n    }\n\n    executor = Executors.newCachedThreadPool();\n\n    redisProxy1 = tp.createProxy(\"redis-1\", \"0.0.0.0:29379\", \"redis-failover-1:9379\");\n    redisProxy2 = tp.createProxy(\"redis-2\", \"0.0.0.0:29380\", \"redis-failover-2:9380\");\n  }\n\n  @AfterAll\n  public static void cleanupAdminClients() throws IOException {\n\n    if (redisProxy1 != null) redisProxy1.delete();\n    if (redisProxy2 != null) redisProxy2.delete();\n\n    if (executor != null) executor.shutdown();\n  }\n\n  @BeforeEach\n  public void setup() throws IOException {\n    tp.getProxies().forEach(proxy -> {\n      try {\n        proxy.enable();\n        for (Toxic toxic : proxy.toxics().getAll()) {\n          toxic.remove();\n        }\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      }\n    });\n\n    jedis1 = new UnifiedJedis(endpoint1.getHostAndPort(),\n        DefaultJedisClientConfig.builder().build());\n    jedis2 = new UnifiedJedis(endpoint2.getHostAndPort(),\n        DefaultJedisClientConfig.builder().build());\n\n    jedis1.flushAll();\n    jedis2.flushAll();\n\n    JEDIS1_ID = getNodeId(jedis1);\n    JEDIS2_ID = getNodeId(jedis2);\n\n    // Create default provider and client for most tests\n    provider = createProvider();\n    failoverClient = MultiDbClient.builder().connectionProvider(provider).build();\n  }\n\n  @AfterEach\n  public void cleanup() throws IOException {\n    if (failoverClient != null) failoverClient.close();\n    if (jedis1 != null) jedis1.close();\n    if (jedis2 != null) jedis2.close();\n  }\n\n  /**\n   * Tests the automatic failover behavior when a Redis server becomes unavailable. This test\n   * verifies:\n   * <ol>\n   * <li>Initial connection to the first Redis server works correctly</li>\n   * <li>Disable access, the first command throws</li>\n   * <li>Command failure is propagated to the caller</li>\n   * <li>CB transitions to OPEN, failover is initiated and following commands are sent to the next\n   * endpoint</li>\n   * <li>Second server is also disabled, all commands fail with JedisConnectionException and error\n   * is propagated to the caller</li>\n   * </ol>\n   */\n  @Test\n  public void testAutomaticFailoverWhenServerBecomesUnavailable() throws Exception {\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS1_ID));\n\n    await().atMost(1, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n        .until(() -> provider.getDatabase(endpoint2.getHostAndPort()).isHealthy());\n\n    // Disable redisProxy1\n    redisProxy1.disable();\n\n    // Endpoint 1 not available\n    // 1. First call should throw JedisConnectionException and trigger failover\n    // 2. Endpoint 1 CB transitions to OPEN\n    // 3. Subsequent calls should be routed to Endpoint 2\n    assertThrows(JedisConnectionException.class, () -> failoverClient.info(\"server\"));\n\n    assertThat(provider.getDatabase(endpoint1.getHostAndPort()).getCircuitBreaker().getState(),\n      equalTo(CircuitBreaker.State.FORCED_OPEN));\n\n    // Check that the failoverClient is now using Endpoint 2\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS2_ID));\n\n    // Disable also second proxy\n    redisProxy2.disable();\n\n    // Endpoint1 and Endpoint2 are NOT available,\n    assertThrows(JedisConnectionException.class, () -> failoverClient.info(\"server\"));\n    assertThat(provider.getDatabase(endpoint2.getHostAndPort()).getCircuitBreaker().getState(),\n      equalTo(CircuitBreaker.State.FORCED_OPEN));\n\n    // and since no other nodes are available, it should propagate the errors to the caller\n    // subsequent calls\n    assertThrows(JedisConnectionException.class, () -> failoverClient.info(\"server\"));\n  }\n\n  @Test\n  public void testManualFailoverNewCommandsAreSentToActiveDatabase() throws InterruptedException {\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS1_ID));\n\n    await().atMost(1, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n        .until(() -> provider.getDatabase(endpoint2.getHostAndPort()).isHealthy());\n\n    provider.setActiveDatabase(endpoint2.getHostAndPort());\n\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS2_ID));\n  }\n\n  private List<MultiDbConfig.DatabaseConfig> getDatabaseConfigs(JedisClientConfig clientConfig,\n      EndpointConfig... endpoints) {\n\n    int weight = endpoints.length;\n    AtomicInteger weightCounter = new AtomicInteger(weight);\n    return Arrays.stream(endpoints)\n        .map(e -> MultiDbConfig.DatabaseConfig.builder(e.getHostAndPort(), clientConfig)\n            .weight(1.0f / weightCounter.getAndIncrement()).healthCheckEnabled(false).build())\n        .collect(Collectors.toList());\n  }\n\n  @Test\n  @Timeout(5)\n  public void testManualFailoverInflightCommandsCompleteGracefully()\n      throws ExecutionException, InterruptedException {\n\n    await().atMost(1, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n        .until(() -> provider.getDatabase(endpoint2.getHostAndPort()).isHealthy());\n\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS1_ID));\n\n    // We will trigger failover while this command is in-flight\n    Future<List<String>> blpop = executor.submit(() -> failoverClient.blpop(1000, \"test-list\"));\n\n    provider.setActiveDatabase(endpoint2.getHostAndPort());\n\n    // After the manual failover, commands should be executed against Endpoint 2\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS2_ID));\n\n    // Failover was manually triggered, and there were no errors\n    // previous endpoint CB should still be in CLOSED state\n    assertThat(provider.getDatabase(endpoint1.getHostAndPort()).getCircuitBreaker().getState(),\n      equalTo(CircuitBreaker.State.CLOSED));\n\n    jedis1.rpush(\"test-list\", \"somevalue\");\n\n    assertThat(blpop.get(), equalTo(Arrays.asList(\"test-list\", \"somevalue\")));\n  }\n\n  /**\n   * Verify that in-flight commands that complete with error during manual failover will propagate\n   * the error to the caller and toggle CB to OPEN state.\n   */\n  @Test\n  public void testManualFailoverInflightCommandsWithErrorsPropagateError() throws Exception {\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS1_ID));\n\n    await().atMost(1, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS)\n        .until(() -> provider.getDatabase(endpoint2.getHostAndPort()).isHealthy());\n\n    Future<List<String>> blpop = executor.submit(() -> failoverClient.blpop(10000, \"test-list-1\"));\n\n    // trigger failover manually\n    provider.setActiveDatabase(endpoint2.getHostAndPort());\n    Future<String> infoCmd = executor.submit(() -> failoverClient.info(\"server\"));\n\n    // After the manual failover, commands should be executed against Endpoint 2\n    assertThat(getNodeId(infoCmd.get()), equalTo(JEDIS2_ID));\n\n    // Disable redisProxy1 to drop active connections and trigger an error\n    redisProxy1.disable();\n\n    // previously submitted command should fail with JedisConnectionException\n    ExecutionException exception = assertThrows(ExecutionException.class, blpop::get);\n    assertThat(exception.getCause(), instanceOf(JedisConnectionException.class));\n\n    // Check that the circuit breaker for Endpoint 1 is open after the error\n    assertThat(provider.getDatabase(endpoint1.getHostAndPort()).getCircuitBreaker().getState(),\n      equalTo(CircuitBreaker.State.OPEN));\n\n    // Ensure that the active cluster is still Endpoint 2\n    assertThat(getNodeId(failoverClient.info(\"server\")), equalTo(JEDIS2_ID));\n  }\n\n  /**\n   * Tests that the CircuitBreaker counts each command error separately, and not just after all\n   * retries are exhausted. This ensures that the circuit breaker opens based on the actual number\n   * of send commands with failures, and not based on the number of logical operations.\n   */\n  @Test\n  public void testCircuitBreakerCountsEachConnectionErrorSeparately() throws IOException {\n    MultiDbConfig failoverConfig = new MultiDbConfig.Builder(getDatabaseConfigs(\n      DefaultJedisClientConfig.builder().socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS)\n          .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build(),\n      endpoint1, endpoint2))\n          .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(2).waitDuration(1).build())\n          .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(3)\n              .minNumOfFailures(2).failureRateThreshold(50f) // %50 failure rate\n              .build())\n          .build();\n\n    MultiDbConnectionProvider provider = new MultiDbConnectionProvider(failoverConfig);\n    try (MultiDbClient client = MultiDbClient.builder().connectionProvider(provider).build()) {\n      // Verify initial connection to first endpoint\n      assertThat(getNodeId(client.info(\"server\")), equalTo(JEDIS1_ID));\n\n      // Disable first endpoint\n      redisProxy1.disable();\n\n      // First command should fail and OPEN the circuit breaker immediately\n      //\n      // If CB is applied after retries:\n      // - It would take 2 commands to OPEN CB (error is propagated for both commands)\n      // - Failover to the next Endpoint happens on the 3rd command\n      //\n      // If CB is applied before retries:\n      // - It should open after just 1 command with retries\n      // - CB is OPEN after the 2nd retry of the first command\n      // - Failover to the next Endpoint happens on the 2nd command\n      //\n      // This test verifies the second case by checking that:\n      // 1. CB opens after the first command (with retries)\n      // 2. The second command is routed to the second endpoint\n      // Command 1\n      assertThrows(JedisConnectionException.class, () -> client.info(\"server\"));\n\n      // Circuit breaker should be open after just one command with retries\n      assertThat(provider.getDatabase(endpoint1.getHostAndPort()).getCircuitBreaker().getState(),\n        equalTo(CircuitBreaker.State.FORCED_OPEN));\n\n      // Next command should be routed to the second endpoint\n      // Command 2\n      assertThat(getNodeId(client.info(\"server\")), equalTo(JEDIS2_ID));\n\n      // Command 3\n      assertThat(getNodeId(client.info(\"server\")), equalTo(JEDIS2_ID));\n\n    }\n\n  }\n\n  /**\n   * Tests that in-flight commands are retried after automatic failover when retry is enabled.\n   */\n  @Test\n  public void testInflightCommandsAreRetriedAfterFailover() throws Exception {\n\n    MultiDbConnectionProvider customProvider = createProvider(\n      builder -> builder.retryOnFailover(true));\n\n    // Create a custom client with retryOnFailover enabled for this specific test\n    try (MultiDbClient customClient = MultiDbClient.builder().connectionProvider(customProvider)\n        .build()) {\n\n      assertThat(getNodeId(customClient.info(\"server\")), equalTo(JEDIS1_ID));\n      Thread.sleep(1000);\n\n      // We will trigger failover while this command is in-flight\n      Future<List<String>> blpop = executor.submit(() -> customClient.blpop(10000, \"test-list-1\"));\n\n      // Simulate error by sending more than 100 bytes. This causes the connection close, and\n      // CB -> OPEN, failover will be actually triggered by the next command\n      redisProxy1.toxics().limitData(\"simulate-socket-failure\", ToxicDirection.UPSTREAM, 100);\n      assertThrows(JedisConnectionException.class,\n        () -> customClient.set(\"test-key\", generateTestValue(150)));\n\n      // Actual failover is performed on first command received after CB is OPEN\n      // TODO : Remove second command. Once we Refactor existing code to perform actual failover\n      // immediately when CB state change to OPEN/FORCED_OPENs\n      assertThat(getNodeId(customClient.info(\"server\")), equalTo(JEDIS2_ID));\n      // Check that the circuit breaker for Endpoint 1 is open\n      assertThat(\n        customProvider.getDatabase(endpoint1.getHostAndPort()).getCircuitBreaker().getState(),\n        equalTo(CircuitBreaker.State.FORCED_OPEN));\n\n      // Disable redisProxy1 to enforce connection drop for the in-flight (blpop) command\n      redisProxy1.disable();\n\n      // The in-flight command should be retried and succeed after failover\n      customClient.rpush(\"test-list-1\", \"somevalue\");\n      assertThat(blpop.get(), equalTo(Arrays.asList(\"test-list-1\", \"somevalue\")));\n    }\n  }\n\n  /**\n   * Tests that in-flight commands are not retried after automatic failover when retry is disabled.\n   */\n  @Test\n  public void testInflightCommandsAreNotRetriedAfterFailover() throws Exception {\n    // Create a custom provider and client with retry disabled for this specific test\n    MultiDbConnectionProvider customProvider = createProvider(\n      builder -> builder.retryOnFailover(false));\n\n    try (MultiDbClient customClient = MultiDbClient.builder().connectionProvider(customProvider)\n        .build()) {\n\n      assertThat(getNodeId(customClient.info(\"server\")), equalTo(JEDIS1_ID));\n      Future<List<String>> blpop = executor.submit(() -> customClient.blpop(500, \"test-list-2\"));\n\n      // Simulate error by sending more than 100 bytes. This causes connection close, and triggers\n      // failover\n      redisProxy1.toxics().limitData(\"simulate-socket-failure\", ToxicDirection.UPSTREAM, 100);\n      assertThrows(JedisConnectionException.class,\n        () -> customClient.set(\"test-key\", generateTestValue(150)));\n\n      // Check that the circuit breaker for Endpoint 1 is open\n      assertThat(\n        customProvider.getDatabase(endpoint1.getHostAndPort()).getCircuitBreaker().getState(),\n        equalTo(CircuitBreaker.State.FORCED_OPEN));\n\n      // Disable redisProxy1 to enforce the current blpop command failure\n      redisProxy1.disable();\n\n      // The in-flight command should fail since the retry is disabled\n      ExecutionException exception = assertThrows(ExecutionException.class,\n        () -> blpop.get(1, TimeUnit.SECONDS));\n      assertThat(exception.getCause(), instanceOf(JedisConnectionException.class));\n    }\n  }\n\n  private static String getNodeId(UnifiedJedis client) {\n\n    return getNodeId(client.info(\"server\"));\n  }\n\n  private static String getNodeId(String info) {\n\n    Matcher m = pattern.matcher(info);\n    if (m.find()) {\n      return m.group(1);\n    }\n    return null;\n  }\n\n  /**\n   * Generates a string of a specific byte size.\n   * @param byteSize The desired size in bytes\n   * @return A string of the specified byte size\n   */\n  private static String generateTestValue(int byteSize) {\n    StringBuilder value = new StringBuilder(byteSize);\n    for (int i = 0; i < byteSize; i++) {\n      value.append('x');\n    }\n    return value.toString();\n  }\n\n  /**\n   * Creates a MultiDbConnectionProvider with standard configuration\n   * @return A configured provider\n   */\n  private MultiDbConnectionProvider createProvider() {\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS)\n        .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build();\n\n    MultiDbConfig failoverConfig = new MultiDbConfig.Builder(\n        getDatabaseConfigs(clientConfig, endpoint1, endpoint2))\n            .commandRetry(\n              MultiDbConfig.RetryConfig.builder().maxAttempts(1).waitDuration(1).build())\n            .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(3)\n                .minNumOfFailures(1).failureRateThreshold(50f).build())\n            .build();\n\n    return new MultiDbConnectionProvider(failoverConfig);\n  }\n\n  /**\n   * Creates a MultiDbConnectionProvider with standard configuration\n   * @return A configured provider\n   */\n  private MultiDbConnectionProvider createProvider(\n      Function<MultiDbConfig.Builder, MultiDbConfig.Builder> configCustomizer) {\n    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()\n        .socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS)\n        .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build();\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(\n        getDatabaseConfigs(clientConfig, endpoint1, endpoint2))\n            .commandRetry(\n              MultiDbConfig.RetryConfig.builder().maxAttempts(1).waitDuration(1).build())\n            .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(3)\n                .minNumOfFailures(1).failureRateThreshold(50f).build());\n\n    if (configCustomizer != null) {\n      builder = configCustomizer.apply(builder);\n    }\n\n    return new MultiDbConnectionProvider(builder.build());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/ActiveActiveLocalFailoverTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.ratelimiter.RateLimiterConfig;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Tags;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport eu.rekawek.toxiproxy.Proxy;\nimport eu.rekawek.toxiproxy.ToxiproxyClient;\nimport eu.rekawek.toxiproxy.model.Toxic;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.MultiDbConfig.CircuitBreakerConfig;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.MultiDbConfig.RetryConfig;\nimport redis.clients.jedis.scenario.MultiThreadedFakeApp;\nimport redis.clients.jedis.scenario.RecommendedSettings;\nimport redis.clients.jedis.scenario.FaultInjectionClient.TriggerActionResponse;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.util.ClientTestUtil;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\n@Tags({ @Tag(\"failover\"), @Tag(\"integration\") })\npublic class ActiveActiveLocalFailoverTest {\n  private static final Logger log = LoggerFactory.getLogger(ActiveActiveLocalFailoverTest.class);\n\n  private static EndpointConfig endpoint1;\n  private static EndpointConfig endpoint2;\n  private static final ToxiproxyClient tp = new ToxiproxyClient(\"localhost\", 8474);\n  public static final int ENDPOINT_PAUSE_TIME_MS = 10000;\n  private static Proxy redisProxy1;\n  private static Proxy redisProxy2;\n\n  @BeforeAll\n  public static void setupAdminClients() throws IOException {\n    endpoint1 = Endpoints.getRedisEndpoint(\"redis-failover-1\");\n    endpoint2 = Endpoints.getRedisEndpoint(\"redis-failover-2\");\n    if (tp.getProxyOrNull(\"redis-1\") != null) {\n      tp.getProxy(\"redis-1\").delete();\n    }\n    if (tp.getProxyOrNull(\"redis-2\") != null) {\n      tp.getProxy(\"redis-2\").delete();\n    }\n\n    redisProxy1 = tp.createProxy(\"redis-1\", \"0.0.0.0:29379\", \"redis-failover-1:9379\");\n    redisProxy2 = tp.createProxy(\"redis-2\", \"0.0.0.0:29380\", \"redis-failover-2:9380\");\n  }\n\n  @AfterAll\n  public static void cleanupAdminClients() throws IOException {\n    if (redisProxy1 != null) redisProxy1.delete();\n    if (redisProxy2 != null) redisProxy2.delete();\n  }\n\n  @BeforeEach\n  public void setup() throws IOException {\n    tp.getProxies().forEach(proxy -> {\n      try {\n        proxy.enable();\n        for (Toxic toxic : proxy.toxics().getAll()) {\n          toxic.remove();\n        }\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      }\n    });\n\n  }\n\n  @ParameterizedTest\n  @CsvSource({ \"true, 0, 2, 4\", \"true, 0, 2, 6\", \"true, 0, 2, 7\", \"true, 0, 2, 8\", \"true, 0, 2, 9\",\n      \"true, 0, 2, 16\", })\n  public void testFailover(boolean fastFailover, long minFailoverCompletionDuration,\n      long maxFailoverCompletionDuration, int numberOfThreads) {\n\n    log.info(\n      \"TESTING WITH PARAMETERS: fastFailover: {} numberOfThreads: {} minFailoverCompletionDuration: {} maxFailoverCompletionDuration: {] \",\n      fastFailover, numberOfThreads, minFailoverCompletionDuration, maxFailoverCompletionDuration);\n\n    JedisClientConfig config = endpoint1.getClientConfigBuilder()\n        .socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS)\n        .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build();\n\n    DatabaseConfig db1 = DatabaseConfig.builder(endpoint1.getHostAndPort(), config)\n        .connectionPoolConfig(RecommendedSettings.poolConfig).weight(1.0f).build();\n    DatabaseConfig db2 = DatabaseConfig.builder(endpoint2.getHostAndPort(), config)\n        .connectionPoolConfig(RecommendedSettings.poolConfig).weight(0.5f).build();\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder().database(db1).database(db2)\n        .failureDetector(\n          CircuitBreakerConfig.builder().slidingWindowSize(1).failureRateThreshold(10.0f).build())\n        .failbackSupported(false).gracePeriod(10000).commandRetry(RetryConfig.builder()\n            .waitDuration(10).maxAttempts(1).exponentialBackoffMultiplier(1).build());\n\n    // Use the parameterized fastFailover setting\n    builder.fastFailover(fastFailover);\n\n    class FailoverReporter implements Consumer<DatabaseSwitchEvent> {\n\n      String currentDatabaseName = \"not set\";\n\n      boolean failoverHappened = false;\n\n      Instant failoverAt = null;\n\n      boolean failbackHappened = false;\n\n      Instant failbackAt = null;\n\n      public String getCurrentDatabaseName() {\n        return currentDatabaseName;\n      }\n\n      @Override\n      public void accept(DatabaseSwitchEvent e) {\n        this.currentDatabaseName = e.getDatabaseName();\n        log.info(\"\\n\\n===={}=== \\nJedis switching to database: {}\\n====End of log===\\n\",\n          e.getReason(), e.getDatabaseName());\n        if ((e.getReason() == SwitchReason.CIRCUIT_BREAKER\n            || e.getReason() == SwitchReason.HEALTH_CHECK)) {\n          failoverHappened = true;\n          failoverAt = Instant.now();\n        }\n        if (e.getReason() == SwitchReason.FAILBACK) {\n          failbackHappened = true;\n          failbackAt = Instant.now();\n        }\n      }\n    }\n\n    // Ensure endpoints are healthy\n    assertTrue(redisProxy1.isEnabled());\n    assertTrue(redisProxy2.isEnabled());\n    ensureEndpointAvailability(endpoint1.getHostAndPort(), config);\n    ensureEndpointAvailability(endpoint2.getHostAndPort(), config);\n\n    FailoverReporter reporter = new FailoverReporter();\n\n    MultiDbClient multiDbClient = MultiDbClient.builder().multiDbConfig(builder.build())\n        .databaseSwitchListener(reporter).build();\n\n    multiDbClient.setActiveDatabase(endpoint1.getHostAndPort());\n\n    AtomicLong retryingThreadsCounter = new AtomicLong(0);\n    AtomicLong failedCommandsAfterFailover = new AtomicLong(0);\n    AtomicReference<Instant> lastFailedCommandAt = new AtomicReference<>();\n    AtomicReference<Instant> lastFailedBeforeFailover = new AtomicReference<>();\n    AtomicBoolean errorOccuredAfterFailover = new AtomicBoolean(false);\n    AtomicBoolean unexpectedErrors = new AtomicBoolean(false);\n    AtomicReference<Exception> lastException = new AtomicReference<Exception>();\n    AtomicLong stopRunningAt = new AtomicLong();\n    Endpoint db2Endpoint = endpoint2.getHostAndPort();\n\n    // Start thread that imitates an application that uses the multiDbClient\n    RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom().limitForPeriod(100)\n        .limitRefreshPeriod(Duration.ofSeconds(1)).timeoutDuration(Duration.ofSeconds(1)).build();\n\n    MultiThreadedFakeApp fakeApp = new MultiThreadedFakeApp(multiDbClient, (UnifiedJedis c) -> {\n\n      long threadId = Thread.currentThread().getId();\n\n      int attempt = 0;\n      int maxTries = 500;\n      int retryingDelay = 5;\n      String currentDbKey = null;\n      while (true) {\n        try {\n          if (System.currentTimeMillis() > stopRunningAt.get()) break;\n          currentDbKey = dbKey(multiDbClient.getActiveDatabaseEndpoint());\n          Map<String, String> executionInfo = new HashMap<String, String>() {\n            {\n              put(\"threadId\", String.valueOf(threadId));\n              put(\"database\", reporter.getCurrentDatabaseName());\n            }\n          };\n\n          multiDbClient.xadd(\"execution_log\", StreamEntryID.NEW_ENTRY, executionInfo);\n\n          if (attempt > 0) {\n            log.info(\"Thread {} recovered after {} ms. Threads still not recovered: {}\", threadId,\n              attempt * retryingDelay, retryingThreadsCounter.decrementAndGet());\n          }\n\n          break;\n        } catch (JedisConnectionException e) {\n          if (dbKey(db2Endpoint).equals(currentDbKey)) {\n            break;\n          }\n          lastException.set(e);\n          lastFailedBeforeFailover.set(Instant.now());\n\n          if (reporter.failoverHappened) {\n            errorOccuredAfterFailover.set(true);\n\n            long failedCommands = failedCommandsAfterFailover.incrementAndGet();\n            lastFailedCommandAt.set(Instant.now());\n            log.warn(\n              \"Thread {} failed to execute command after failover. Failed commands after failover: {}\",\n              threadId, failedCommands);\n          }\n\n          if (attempt == 0) {\n            long failedThreads = retryingThreadsCounter.incrementAndGet();\n            log.warn(\"Thread {} failed to execute command. Failed threads: {}\", threadId,\n              failedThreads);\n          }\n          try {\n            Thread.sleep(retryingDelay);\n          } catch (InterruptedException ie) {\n            throw new RuntimeException(ie);\n          }\n          if (++attempt == maxTries) throw e;\n        } catch (Exception e) {\n          if (dbKey(db2Endpoint).equals(currentDbKey)) {\n            break;\n          }\n          lastException.set(e);\n          unexpectedErrors.set(true);\n          lastFailedBeforeFailover.set(Instant.now());\n          log.error(\"UNEXPECTED exception\", e);\n          if (reporter.failoverHappened) {\n            errorOccuredAfterFailover.set(true);\n            lastFailedCommandAt.set(Instant.now());\n          }\n        }\n      }\n      return true;\n    }, numberOfThreads, rateLimiterConfig);\n    fakeApp.setKeepExecutingForSeconds(30);\n    Thread t = new Thread(fakeApp);\n    t.start();\n\n    stopRunningAt.set(System.currentTimeMillis() + 30000);\n\n    log.info(\"Triggering issue on endpoint1\");\n    try (Jedis jedis = new Jedis(endpoint1.getHostAndPort(),\n        endpoint1.getClientConfigBuilder().build())) {\n      jedis.clientPause(ENDPOINT_PAUSE_TIME_MS);\n    }\n\n    fakeApp.setAction(new TriggerActionResponse(null) {\n      private long completeAt = System.currentTimeMillis() + 10000;\n\n      @Override\n      public boolean isCompleted(Duration checkInterval, Duration delayAfter, Duration timeout) {\n        return System.currentTimeMillis() > completeAt;\n      }\n    });\n\n    log.info(\"Waiting for fake app to complete\");\n    try {\n      t.join();\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n    log.info(\"Fake app completed\");\n\n    MultiDbConnectionProvider provider = ClientTestUtil.getConnectionProvider(multiDbClient);\n    ConnectionPool pool = provider.getDatabase(endpoint1.getHostAndPort()).getConnectionPool();\n\n    log.info(\"First connection pool state: active: {}, idle: {}\", pool.getNumActive(),\n      pool.getNumIdle());\n    log.info(\"Failover happened at: {}\", reporter.failoverAt);\n    log.info(\"Failback happened at: {}\", reporter.failbackAt);\n\n    assertEquals(0, pool.getNumActive());\n    assertTrue(fakeApp.capturedExceptions().isEmpty());\n    assertTrue(reporter.failoverHappened);\n    if (errorOccuredAfterFailover.get()) {\n      log.info(\"Last failed command at: {}\", lastFailedCommandAt.get());\n      Duration fullFailoverTime = Duration.between(reporter.failoverAt, lastFailedCommandAt.get());\n      log.info(\"Full failover time: {} s\", fullFailoverTime.getSeconds());\n      log.info(\"Last failed command exception: {}\", lastException.get());\n\n      // assertTrue(reporter.failbackHappened);\n      assertThat(fullFailoverTime.getSeconds(),\n        Matchers.greaterThanOrEqualTo(minFailoverCompletionDuration));\n      assertThat(fullFailoverTime.getSeconds(),\n        Matchers.lessThanOrEqualTo(maxFailoverCompletionDuration));\n    } else {\n      log.info(\"No failed commands after failover!\");\n    }\n\n    if (lastFailedBeforeFailover.get() != null) {\n      log.info(\"Last failed command before failover at: {}\", lastFailedBeforeFailover.get());\n    }\n    assertFalse(unexpectedErrors.get());\n\n    multiDbClient.close();\n  }\n\n  private String dbKey(Endpoint endpoint) {\n    return endpoint.getHost() + \":\" + endpoint.getPort();\n  }\n\n  private static void ensureEndpointAvailability(HostAndPort endpoint, JedisClientConfig config) {\n    await().atMost(Duration.ofSeconds(ENDPOINT_PAUSE_TIME_MS)).until(() -> {\n      try (Jedis jedis = new Jedis(endpoint, config)) {\n        return \"PONG\".equals(jedis.ping());\n      } catch (Exception e) {\n        log.info(\"Waiting for endpoint {} to become available...\", endpoint);\n        return false;\n      }\n    });\n  }\n\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/ConnectionInitializationContextTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.mcf.InitializationPolicy.Decision;\n\n/**\n * Unit tests for {@link ConnectionInitializationContext}.\n */\n@ExtendWith(MockitoExtension.class)\npublic class ConnectionInitializationContextTest {\n\n  @Mock\n  private HealthStatusManager healthStatusManager;\n\n  @Mock\n  private MultiDbConnectionProvider.Database database1;\n\n  @Mock\n  private MultiDbConnectionProvider.Database database2;\n\n  @Mock\n  private MultiDbConnectionProvider.Database database3;\n\n  private Endpoint endpoint1;\n  private Endpoint endpoint2;\n  private Endpoint endpoint3;\n  private Map<Endpoint, MultiDbConnectionProvider.Database> databases;\n\n  @BeforeEach\n  void setUp() {\n    endpoint1 = new HostAndPort(\"fake\", 6379);\n    endpoint2 = new HostAndPort(\"fake\", 6380);\n    endpoint3 = new HostAndPort(\"fake\", 6381);\n    databases = new HashMap<>();\n  }\n\n  @Nested\n  @DisplayName(\"Context Construction Tests\")\n  class ContextConstructionTests {\n\n    @Test\n    @DisplayName(\"Should count healthy endpoints as available\")\n    void shouldCountHealthyAsAvailable() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.HEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint2)).thenReturn(HealthStatus.HEALTHY);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(2, ctx.getAvailableConnections());\n      assertEquals(0, ctx.getFailedConnections());\n      assertEquals(0, ctx.getPendingConnections());\n    }\n\n    @Test\n    @DisplayName(\"Should count unhealthy endpoints as failed\")\n    void shouldCountUnhealthyAsFailed() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.UNHEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint2)).thenReturn(HealthStatus.UNHEALTHY);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(0, ctx.getAvailableConnections());\n      assertEquals(2, ctx.getFailedConnections());\n      assertEquals(0, ctx.getPendingConnections());\n    }\n\n    @Test\n    @DisplayName(\"Should count unknown status as pending\")\n    void shouldCountUnknownAsPending() {\n      databases.put(endpoint1, database1);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.UNKNOWN);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(0, ctx.getAvailableConnections());\n      assertEquals(0, ctx.getFailedConnections());\n      assertEquals(1, ctx.getPendingConnections());\n    }\n\n    @Test\n    @DisplayName(\"Should count endpoints without health check as available\")\n    void shouldCountNoHealthCheckAsAvailable() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(false);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(false);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(2, ctx.getAvailableConnections());\n      assertEquals(0, ctx.getFailedConnections());\n      assertEquals(0, ctx.getPendingConnections());\n    }\n\n    @Test\n    @DisplayName(\"Should handle mixed health check states\")\n    void shouldHandleMixedStates() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n      databases.put(endpoint3, database3);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.HEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint2)).thenReturn(HealthStatus.UNHEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint3)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint3)).thenReturn(HealthStatus.UNKNOWN);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(1, ctx.getAvailableConnections());\n      assertEquals(1, ctx.getFailedConnections());\n      assertEquals(1, ctx.getPendingConnections());\n    }\n\n    @Test\n    @DisplayName(\"Should handle empty database map\")\n    void shouldHandleEmptyDatabaseMap() {\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(0, ctx.getAvailableConnections());\n      assertEquals(0, ctx.getFailedConnections());\n      assertEquals(0, ctx.getPendingConnections());\n    }\n\n    @Test\n    @DisplayName(\"Should handle mix of health check enabled and disabled endpoints\")\n    void shouldHandleMixedHealthCheckConfiguration() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n      databases.put(endpoint3, database3);\n\n      // endpoint1: health check enabled, healthy\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.HEALTHY);\n\n      // endpoint2: health check disabled - should be counted as available\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(false);\n\n      // endpoint3: health check enabled, pending\n      when(healthStatusManager.hasHealthCheck(endpoint3)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint3)).thenReturn(HealthStatus.UNKNOWN);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(2, ctx.getAvailableConnections()); // endpoint1 + endpoint2\n      assertEquals(0, ctx.getFailedConnections());\n      assertEquals(1, ctx.getPendingConnections()); // endpoint3\n    }\n  }\n\n  @Nested\n  @DisplayName(\"ConformsTo Policy Evaluation Tests\")\n  class ConformsToPolicyTests {\n\n    @Test\n    @DisplayName(\"Should delegate to policy evaluate method\")\n    void shouldDelegateToPolicy() {\n      databases.put(endpoint1, database1);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.HEALTHY);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(Decision.SUCCESS, ctx.conformsTo(InitializationPolicy.BuiltIn.ONE_AVAILABLE));\n      assertEquals(Decision.SUCCESS, ctx.conformsTo(InitializationPolicy.BuiltIn.ALL_AVAILABLE));\n      assertEquals(Decision.SUCCESS,\n        ctx.conformsTo(InitializationPolicy.BuiltIn.MAJORITY_AVAILABLE));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE for ONE_AVAILABLE when all pending\")\n    void shouldReturnContinueWhenAllPending() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.UNKNOWN);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint2)).thenReturn(HealthStatus.UNKNOWN);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(Decision.CONTINUE, ctx.conformsTo(InitializationPolicy.BuiltIn.ONE_AVAILABLE));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL for ALL_AVAILABLE when any failed\")\n    void shouldReturnFailWhenAnyFailed() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.HEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint2)).thenReturn(HealthStatus.UNHEALTHY);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      assertEquals(Decision.FAIL, ctx.conformsTo(InitializationPolicy.BuiltIn.ALL_AVAILABLE));\n    }\n  }\n\n  @Nested\n  @DisplayName(\"ToString Tests\")\n  class ToStringTests {\n\n    @Test\n    @DisplayName(\"Should include counts in toString\")\n    void shouldIncludeCountsInToString() {\n      databases.put(endpoint1, database1);\n      databases.put(endpoint2, database2);\n      databases.put(endpoint3, database3);\n\n      when(healthStatusManager.hasHealthCheck(endpoint1)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint1)).thenReturn(HealthStatus.HEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint2)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint2)).thenReturn(HealthStatus.UNHEALTHY);\n      when(healthStatusManager.hasHealthCheck(endpoint3)).thenReturn(true);\n      when(healthStatusManager.getHealthStatus(endpoint3)).thenReturn(HealthStatus.UNKNOWN);\n\n      ConnectionInitializationContext ctx = new ConnectionInitializationContext(databases,\n          healthStatusManager);\n\n      String result = ctx.toString();\n      assertTrue(result.contains(\"available=1\"));\n      assertTrue(result.contains(\"failed=1\"));\n      assertTrue(result.contains(\"pending=1\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/DatabaseEvaluateThresholdsTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\n\n/**\n * Tests for circuit breaker thresholds: both failure-rate threshold and minimum number of failures\n * must be exceeded to trigger failover. Uses a real CircuitBreaker and real Retry, but mocks the\n * provider and {@link Database} wiring to avoid network I/O.\n */\npublic class DatabaseEvaluateThresholdsTest {\n\n  private MultiDbConnectionProvider provider;\n  private Database database;\n  private CircuitBreaker circuitBreaker;\n  private CircuitBreaker.Metrics metrics;\n\n  @BeforeEach\n  public void setup() {\n    provider = mock(MultiDbConnectionProvider.class);\n    database = mock(Database.class);\n\n    circuitBreaker = mock(CircuitBreaker.class);\n    metrics = mock(CircuitBreaker.Metrics.class);\n\n    when(database.getCircuitBreaker()).thenReturn(circuitBreaker);\n    when(circuitBreaker.getMetrics()).thenReturn(metrics);\n    when(circuitBreaker.getState()).thenReturn(CircuitBreaker.State.CLOSED);\n\n    // Configure the mock to call the real evaluateThresholds method\n    doCallRealMethod().when(database).evaluateThresholds(anyBoolean());\n\n  }\n\n  /**\n   * Below minimum failures; even if all calls are failures, failover should NOT trigger. Note: The\n   * isThresholdsExceeded method adds +1 to account for the current failing call, so we set\n   * failures=1 which becomes 2 with +1, still below minFailures=3.\n   */\n  @Test\n  public void belowMinFailures_doesNotFailover() {\n    when(database.getCircuitBreakerMinNumOfFailures()).thenReturn(3);\n    when(metrics.getNumberOfFailedCalls()).thenReturn(1); // +1 becomes 2, still < 3\n    when(metrics.getNumberOfSuccessfulCalls()).thenReturn(0);\n    when(database.getCircuitBreakerFailureRateThreshold()).thenReturn(50.0f);\n    when(circuitBreaker.getState()).thenReturn(CircuitBreaker.State.CLOSED);\n\n    database.evaluateThresholds(false);\n    verify(circuitBreaker, never()).transitionToOpenState();\n    verify(provider, never()).switchToHealthyDatabase(any(), any());\n  }\n\n  /**\n   * Reaching minFailures and exceeding failure rate threshold should trigger circuit breaker to\n   * OPEN state. Note: The isThresholdsExceeded method adds +1 to account for the current failing\n   * call, so we set failures=2 which becomes 3 with +1, reaching minFailures=3.\n   */\n  @Test\n  public void minFailuresAndRateExceeded_triggersOpenState() {\n    when(database.getCircuitBreakerMinNumOfFailures()).thenReturn(3);\n    when(metrics.getNumberOfFailedCalls()).thenReturn(2); // +1 becomes 3, reaching minFailures\n    when(metrics.getNumberOfSuccessfulCalls()).thenReturn(0);\n    when(database.getCircuitBreakerFailureRateThreshold()).thenReturn(50.0f);\n    when(circuitBreaker.getState()).thenReturn(CircuitBreaker.State.CLOSED);\n\n    database.evaluateThresholds(false);\n    verify(circuitBreaker, times(1)).transitionToOpenState();\n  }\n\n  /**\n   * Even after reaching minFailures, if failure rate is below threshold, do not failover. Note: The\n   * isThresholdsExceeded method adds +1 to account for the current failing call, so we set\n   * failures=2 which becomes 3 with +1, reaching minFailures=3. Rate calculation: (3 failures) / (3\n   * failures + 3 successes) = 50% < 80% threshold.\n   */\n  @Test\n  public void rateBelowThreshold_doesNotFailover() {\n    when(database.getCircuitBreakerMinNumOfFailures()).thenReturn(3);\n    when(metrics.getNumberOfSuccessfulCalls()).thenReturn(3);\n    when(metrics.getNumberOfFailedCalls()).thenReturn(2); // +1 becomes 3, rate = 3/(3+3) = 50%\n    when(database.getCircuitBreakerFailureRateThreshold()).thenReturn(80.0f);\n    when(circuitBreaker.getState()).thenReturn(CircuitBreaker.State.CLOSED);\n\n    database.evaluateThresholds(false);\n\n    verify(circuitBreaker, never()).transitionToOpenState();\n    verify(provider, never()).switchToHealthyDatabase(any(), any());\n  }\n\n  @Test\n  public void providerBuilder_zeroRate_mapsToHundredAndHugeMinCalls() {\n    MultiDbConfig.Builder cfgBuilder = MultiDbConfig\n        .builder(java.util.Arrays.asList(MultiDbConfig.DatabaseConfig\n            .builder(new HostAndPort(\"dummy\", 6379), DefaultJedisClientConfig.builder().build())\n            .healthCheckEnabled(false).build()));\n    cfgBuilder.failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n        .failureRateThreshold(0.0f).minNumOfFailures(3).slidingWindowSize(10).build());\n    MultiDbConfig mcc = cfgBuilder.build();\n\n    CircuitBreakerThresholdsAdapter adapter = new CircuitBreakerThresholdsAdapter(mcc);\n\n    assertEquals(100.0f, adapter.getFailureRateThreshold(), 0.0001f);\n    assertEquals(Integer.MAX_VALUE, adapter.getMinimumNumberOfCalls());\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n      // Format: \"minFails, rate%, success, fails, lastFailRecorded, expected\"\n\n      // === Basic threshold crossing cases ===\n      \"0, 1.0, 0, 1, false, true\", // +1 = 2 fails, rate=100% >= 1%, min=0 -> trigger\n      \"0, 1.0, 0, 1, true, true\", // +0 = 1 fails, rate=100% >= 1%, min=0 -> trigger\n\n      \"1, 1.0, 0, 0, false, true\", // +1 = 1 fails, rate=100% >= 1%, min=1 -> trigger\n      \"1, 1.0, 0, 0, true, false\", // +0 = 0 fails, 0 < 1 min -> no trigger\n\n      \"3, 50.0, 0, 2, false, true\", // +1 = 3 fails, rate=100% >= 50%, min=3 -> trigger\n      \"3, 50.0, 0, 2, true, false\", // +0 = 2 fails, 2 < 3 min -> no trigger\n\n      // === Rate threshold boundary cases ===\n      \"1, 100.0, 0, 0, false, true\", // +1 = 1 fails, rate=100% >= 100%, min=1 -> trigger\n      \"1, 100.0, 0, 0, true, false\", // +0 = 0 fails, 0 < 1 min -> no trigger\n\n      \"0, 100.0, 99, 1, false, false\", // +1 = 2 fails, rate=1.98% < 100% -> no trigger\n      \"0, 100.0, 99, 1, true, false\", // +0 = 1 fails, rate=1.0% < 100% -> no trigger\n\n      \"0, 1.0, 99, 1, false, true\", // +1 = 2 fails, rate=1.98% >= 1%, min=0 -> trigger\n      \"0, 1.0, 99, 1, true, true\", // +0 = 1 fails, rate=1.0% >= 1%, min=0 -> trigger\n\n      // === Zero rate threshold (always trigger if min failures met) ===\n      \"1, 0.0, 0, 0, false, true\", // +1 = 1 fails, rate=100% >= 0%, min=1 -> trigger\n      \"1, 0.0, 0, 0, true, false\", // +0 = 0 fails, 0 < 1 min -> no trigger\n      \"1, 0.0, 100, 0, false, true\", // +1 = 1 fails, rate=0.99% >= 0%, min=1 -> trigger\n      \"1, 0.0, 100, 0, true, false\", // +0 = 0 fails, 0 < 1 min -> no trigger\n\n      // === High minimum failures cases ===\n      \"3, 50.0, 3, 1, false, false\", // +1 = 2 fails, 2 < 3 min -> no trigger\n      \"3, 50.0, 3, 1, true, false\", // +0 = 1 fails, 1 < 3 min -> no trigger\n      \"1000, 1.0, 198, 2, false, false\", // +1 = 3 fails, 3 < 1000 min -> no trigger\n      \"1000, 1.0, 198, 2, true, false\", // +0 = 2 fails, 2 < 1000 min -> no trigger\n\n      // === Corner cases ===\n      \"0, 50.0, 0, 0, false, true\", // +1 = 1 fails, rate=100% >= 50%, min=0 -> trigger\n      \"0, 50.0, 0, 0, true, false\", // +0 = 0 fails, no calls -> no trigger\n      \"1, 50.0, 1, 1, false, true\", // +1 = 2 fails, rate=66.7% >= 50%, min=1 -> trigger\n      \"1, 50.0, 1, 1, true, true\", // +0 = 1 fails, rate=50% >= 50%, min=1 -> trigger\n      \"2, 33.0, 2, 1, false, true\", // +1 = 2 fails, rate=50% >= 33%, min=2 -> trigger\n      \"2, 33.0, 2, 1, true, false\", // +0 = 1 fails, 1 < 2 min -> no trigger\n      \"5, 20.0, 20, 4, false, true\", // +1 = 5 fails, rate=20% >= 20%, min=5 -> trigger\n      \"5, 20.0, 20, 4, true, false\", // +0 = 4 fails, 4 < 5 min -> no trigger\n      \"3, 75.0, 1, 2, false, true\", // +1 = 3 fails, rate=75% >= 75%, min=3 -> trigger\n      \"3, 75.0, 1, 2, true, false\", // +0 = 2 fails, 2 < 3 min -> no trigger\n  })\n  public void thresholdMatrix(int minFailures, float ratePercent, int successes, int failures,\n      boolean lastFailRecorded, boolean expectOpenState) {\n\n    when(database.getCircuitBreakerMinNumOfFailures()).thenReturn(minFailures);\n    when(metrics.getNumberOfSuccessfulCalls()).thenReturn(successes);\n    when(metrics.getNumberOfFailedCalls()).thenReturn(failures);\n    when(database.getCircuitBreakerFailureRateThreshold()).thenReturn(ratePercent);\n    when(circuitBreaker.getState()).thenReturn(CircuitBreaker.State.CLOSED);\n\n    database.evaluateThresholds(lastFailRecorded);\n\n    if (expectOpenState) {\n      verify(circuitBreaker, times(1)).transitionToOpenState();\n    } else {\n      verify(circuitBreaker, never()).transitionToOpenState();\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/DefaultValuesTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\n\npublic class DefaultValuesTest {\n\n  HostAndPort fakeEndpoint = new HostAndPort(\"fake\", 6379);\n  JedisClientConfig config = DefaultJedisClientConfig.builder().build();\n\n  @Test\n  void testDefaultValuesInConfig() {\n\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(fakeEndpoint, config).build();\n    MultiDbConfig multiConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).build();\n\n    // check for grace period\n    assertEquals(60000, multiConfig.getGracePeriod());\n\n    // check for database config\n    assertEquals(databaseConfig, multiConfig.getDatabaseConfigs()[0]);\n\n    // check healthchecks enabled\n    assertNotNull(databaseConfig.getHealthCheckStrategySupplier());\n\n    // check default healthcheck strategy is PingStrategy\n    assertEquals(PingStrategy.DEFAULT, databaseConfig.getHealthCheckStrategySupplier());\n\n    // check number of probes\n    assertEquals(3,\n      databaseConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getNumProbes());\n\n    assertEquals(500, databaseConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config)\n        .getDelayInBetweenProbes());\n\n    assertEquals(ProbingPolicy.BuiltIn.ALL_SUCCESS,\n      databaseConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getPolicy());\n\n    // check health check interval\n    assertEquals(5000,\n      databaseConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getInterval());\n\n    // check lag aware tolerance\n    LagAwareStrategy.Config lagAwareConfig = LagAwareStrategy.Config\n        .builder(fakeEndpoint, config.getCredentialsProvider()).build();\n    assertEquals(Duration.ofMillis(5000), lagAwareConfig.getAvailabilityLagTolerance());\n\n    // check CB failure rate threshold\n    assertEquals(10, multiConfig.getFailureDetector().getFailureRateThreshold());\n\n    // check CB sliding window size\n    assertEquals(2, multiConfig.getFailureDetector().getSlidingWindowSize());\n\n    // check CB number of failures threshold\n    assertEquals(1000, multiConfig.getFailureDetector().getMinNumOfFailures());\n\n    // check failback check interval\n    assertEquals(120000, multiConfig.getFailbackCheckInterval());\n\n    // check failover max attempts before give up\n    assertEquals(10, multiConfig.getMaxNumFailoverAttempts());\n\n    // check delay between failover attempts\n    assertEquals(12000, multiConfig.getDelayInBetweenFailoverAttempts());\n\n    // check initialization policy\n    assertEquals(InitializationPolicy.BuiltIn.MAJORITY_AVAILABLE,\n      multiConfig.getInitializationPolicy());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/FailbackMechanismIntegrationTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nimport java.time.Duration;\n\nimport org.awaitility.Durations;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.MockedConstruction;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\n\n@ExtendWith(MockitoExtension.class)\nclass FailbackMechanismIntegrationTest {\n\n  private HostAndPort endpoint1;\n  private HostAndPort endpoint2;\n  private HostAndPort endpoint3;\n  private JedisClientConfig clientConfig;\n  private static Duration FIFTY_MILLISECONDS = Duration.ofMillis(50);\n\n  @BeforeEach\n  void setUp() {\n    endpoint1 = new HostAndPort(\"localhost\", 6379);\n    endpoint2 = new HostAndPort(\"localhost\", 6380);\n    endpoint3 = new HostAndPort(\"localhost\", 6381);\n    clientConfig = DefaultJedisClientConfig.builder().build();\n  }\n\n  private MockedConstruction<TrackingConnectionPool> mockPool() {\n    Connection mockConnection = mock(Connection.class);\n    lenient().when(mockConnection.ping()).thenReturn(true);\n    return mockConstruction(TrackingConnectionPool.class, (mock, context) -> {\n      when(mock.getResource()).thenReturn(mockConnection);\n      doNothing().when(mock).close();\n    });\n  }\n\n  @Test\n  void testFailbackDisabledDoesNotPerformFailback() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      // Create databases with different weights\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build(); // Lower\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(2.0f) // Higher weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(false) // Disabled\n              .failbackCheckInterval(100) // Short interval for testing\n              .build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database2 unhealthy to force failover to database1\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint2,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database1 (only healthy option)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Make database2 healthy again (higher weight - would normally trigger failback)\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint2,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait longer than failback interval\n        // Should still be on database1 since failback is disabled\n        await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint1) == provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testFailbackToHigherWeightDatabase() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      // Create databases with different weights\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(2.0f) // Higher weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(1.0f) // Lower weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(100) // Short interval for testing\n              .gracePeriod(100).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database1 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Make database1 unhealthy to force failover to database2\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database2 (lower weight, but only healthy option)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database1 healthy again\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for failback check interval + some buffer\n        // Should have failed back to database1 (higher weight)\n        await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint1) == provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testNoFailbackToLowerWeightDatabase() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      // Create three databases with different weights to properly test no failback to lower weight\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f) // Lowest weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(2.0f) // Medium weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database3 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint3, clientConfig).weight(3.0f) // Highest weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2, database3 })\n              .failbackSupported(true).failbackCheckInterval(100).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database3 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase());\n\n        // Make database3 unhealthy to force failover to database2 (medium weight)\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint3,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database2 (highest weight among healthy databases)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database1 (lowest weight) healthy - this should NOT trigger failback\n        // since we don't failback to lower weight databases\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for failback check interval\n        // Should still be on database2 (no failback to lower weight database1)\n        await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint2) == provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testFailbackToHigherWeightDatabaseImmediately() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(2.0f).healthCheckEnabled(false).build(); // Higher\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(1.0f).healthCheckEnabled(false).build(); // Lower\n                                                                                            // weight\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(100).gracePeriod(50).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database1 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Make database1 unhealthy to force failover to database2\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database2 (only healthy option)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database1 healthy again\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for failback check\n        // Should have failed back to database1 immediately (higher weight, no stability period\n        // required)\n        await().atMost(Durations.TWO_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint1) == provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testUnhealthyDatabaseCancelsFailback() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(2.0f).healthCheckEnabled(false).build(); // Higher\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(1.0f).healthCheckEnabled(false).build(); // Lower\n                                                                                            // weight\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(200).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database1 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Make database1 unhealthy to force failover to database2\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database2 (only healthy option)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database1 healthy again (should trigger failback attempt)\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait a bit\n        Thread.sleep(100);\n\n        // Make database1 unhealthy again before failback completes\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Wait past the original failback interval\n        // Should still be on database2 (failback was cancelled due to database1 becoming unhealthy)\n        await().atMost(Durations.TWO_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint2) == provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testMultipleDatabaseFailbackPriority() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build(); // Lowest\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(2.0f).healthCheckEnabled(false).build(); // Medium\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database3 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint3, clientConfig).weight(3.0f) // Highest weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2, database3 })\n              .failbackSupported(true).failbackCheckInterval(100).gracePeriod(100).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database3 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase());\n\n        // Make database3 unhealthy to force failover to database2 (next highest weight)\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint3,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database2 (highest weight among healthy databases)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database3 healthy again\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint3,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for failback\n        // Should fail back to database3 (highest weight)\n        await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint3) == provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testGracePeriodDisablesDatabaseOnUnhealthy() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build(); // Lower\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(2.0f).healthCheckEnabled(false).build(); // Higher\n                                                                                            // weight\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(100).gracePeriod(200) // 200ms grace\n                                                           // period\n              .build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Now make database2 unhealthy - it should be disabled for grace period\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint2,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should failover to database1\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Database2 should be in grace period\n        assertTrue(provider.getDatabase(endpoint2).isInGracePeriod());\n      }\n    }\n  }\n\n  @Test\n  void testGracePeriodReEnablesDatabaseAfterPeriod() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build(); // Lower\n                                                                                            // weight\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(2.0f).healthCheckEnabled(false).build(); // Higher\n                                                                                            // weight\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(50) // Short interval for testing\n              .gracePeriod(100) // Short grace period for testing\n              .build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database2 unhealthy to start grace period and force failover\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint2,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should failover to database1\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Database2 should be in grace period\n        assertTrue(provider.getDatabase(endpoint2).isInGracePeriod());\n\n        // Make database2 healthy again while it's still in grace period\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint2,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Should still be on database1 because database2 is in grace period\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Wait for grace period to expire\n        // Database2 should no longer be in grace period\n        await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> !provider.getDatabase(endpoint2).isInGracePeriod());\n\n        // Wait for failback check to run\n        // Should now failback to database2 (higher weight) since grace period has expired\n        await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(FIFTY_MILLISECONDS)\n            .until(() -> provider.getDatabase(endpoint2) == provider.getDatabase());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/FailbackMechanismUnitTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\n\n@ExtendWith(MockitoExtension.class)\nclass FailbackMechanismUnitTest {\n\n  private HostAndPort endpoint1;\n  private JedisClientConfig clientConfig;\n\n  @BeforeEach\n  void setUp() {\n    endpoint1 = new HostAndPort(\"dummy\", 6379);\n    clientConfig = DefaultJedisClientConfig.builder().build();\n  }\n\n  @Test\n  void testFailbackCheckIntervalConfiguration() {\n    // Test default value\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    MultiDbConfig defaultConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).build();\n\n    assertEquals(120000, defaultConfig.getFailbackCheckInterval());\n\n    // Test custom value\n    MultiDbConfig customConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).failbackCheckInterval(3000).build();\n\n    assertEquals(3000, customConfig.getFailbackCheckInterval());\n  }\n\n  @Test\n  void testFailbackSupportedConfiguration() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    // Test default (should be true)\n    MultiDbConfig defaultConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).build();\n\n    assertTrue(defaultConfig.isFailbackSupported());\n\n    // Test disabled\n    MultiDbConfig disabledConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).failbackSupported(false).build();\n\n    assertFalse(disabledConfig.isFailbackSupported());\n  }\n\n  @Test\n  void testFailbackCheckIntervalValidation() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    // Test zero interval (should be allowed)\n    MultiDbConfig zeroConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).failbackCheckInterval(0).build();\n\n    assertEquals(0, zeroConfig.getFailbackCheckInterval());\n\n    // Test negative interval (should be allowed - implementation decision)\n    MultiDbConfig negativeConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).failbackCheckInterval(-1000).build();\n\n    assertEquals(-1000, negativeConfig.getFailbackCheckInterval());\n  }\n\n  @Test\n  void testBuilderChaining() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    // Test that builder methods can be chained\n    MultiDbConfig config = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).failbackSupported(true)\n            .failbackCheckInterval(2000).retryOnFailover(true).build();\n\n    assertTrue(config.isFailbackSupported());\n    assertEquals(2000, config.getFailbackCheckInterval());\n    assertTrue(config.isRetryOnFailover());\n  }\n\n  @Test\n  void testGracePeriodConfiguration() {\n    // Test default value\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    MultiDbConfig defaultConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).build();\n\n    assertEquals(60000, defaultConfig.getGracePeriod());\n\n    // Test custom value\n    MultiDbConfig customConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).gracePeriod(5000).build();\n\n    assertEquals(5000, customConfig.getGracePeriod());\n  }\n\n  @Test\n  void testGracePeriodValidation() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    // Test zero grace period (should be allowed)\n    MultiDbConfig zeroConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).gracePeriod(0).build();\n\n    assertEquals(0, zeroConfig.getGracePeriod());\n\n    // Test negative grace period (should be allowed - implementation decision)\n    MultiDbConfig negativeConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).gracePeriod(-1000).build();\n\n    assertEquals(-1000, negativeConfig.getGracePeriod());\n  }\n\n  @Test\n  void testGracePeriodBuilderChaining() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(endpoint1, clientConfig).healthCheckEnabled(false).build();\n\n    // Test that builder methods can be chained\n    MultiDbConfig config = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).failbackSupported(true)\n            .failbackCheckInterval(2000).gracePeriod(8000).retryOnFailover(true).build();\n\n    assertTrue(config.isFailbackSupported());\n    assertEquals(2000, config.getFailbackCheckInterval());\n    assertEquals(8000, config.getGracePeriod());\n    assertTrue(config.isRetryOnFailover());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/HealthCheckIntegrationTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbClient;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.MultiDbConfig.StrategySupplier;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.mcf.ProbingPolicy.BuiltIn;\nimport redis.clients.jedis.scenario.RecommendedSettings;\n\npublic class HealthCheckIntegrationTest {\n\n  private static EndpointConfig endpoint1;\n  private static JedisClientConfig clientConfig;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    endpoint1 = Endpoints.getRedisEndpoint(\"standalone0\");\n    clientConfig = endpoint1.getClientConfigBuilder()\n        .socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS)\n        .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build();\n  }\n\n  @Test\n  public void testDisableHealthCheck() {\n    // No health check strategy supplier means health check is disabled\n    MultiDbConfig multiDbConfig = getMCCF(null);\n    try (\n        MultiDbClient customClient = MultiDbClient.builder().multiDbConfig(multiDbConfig).build()) {\n      // Verify that the client can connect and execute commands\n      String result = customClient.ping();\n      assertEquals(\"PONG\", result);\n    }\n  }\n\n  @Test\n  public void testDefaultStrategySupplier() {\n    // Create a default strategy supplier that creates PingStrategy instances\n    MultiDbConfig.StrategySupplier defaultSupplier = (hostAndPort, jedisClientConfig) -> {\n      return new PingStrategy(hostAndPort, jedisClientConfig);\n    };\n    MultiDbConfig multiDbConfig = getMCCF(defaultSupplier);\n    try (\n        MultiDbClient customClient = MultiDbClient.builder().multiDbConfig(multiDbConfig).build()) {\n      // Verify that the client can connect and execute commands\n      String result = customClient.ping();\n      assertEquals(\"PONG\", result);\n    }\n  }\n\n  @Test\n  public void testCustomStrategySupplier() {\n    // Create a StrategySupplier that uses the JedisClientConfig when available\n    MultiDbConfig.StrategySupplier strategySupplier = (hostAndPort, jedisClientConfig) -> {\n      return new TestHealthCheckStrategy(HealthCheckStrategy.Config.builder().interval(500)\n          .timeout(500).numProbes(1).policy(BuiltIn.ANY_SUCCESS).build(), (endpoint) -> {\n            // Create connection per health check to avoid resource leak\n            try (UnifiedJedis pinger = new UnifiedJedis(hostAndPort, jedisClientConfig)) {\n              String result = pinger.ping();\n              return \"PONG\".equals(result) ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;\n            } catch (Exception e) {\n              return HealthStatus.UNHEALTHY;\n            }\n          });\n    };\n\n    MultiDbConfig multiDbConfig = getMCCF(strategySupplier);\n    try (\n        MultiDbClient customClient = MultiDbClient.builder().multiDbConfig(multiDbConfig).build()) {\n      // Verify that the client can connect and execute commands\n      String result = customClient.ping();\n      assertEquals(\"PONG\", result);\n    }\n  }\n\n  private MultiDbConfig getMCCF(MultiDbConfig.StrategySupplier strategySupplier) {\n    Function<DatabaseConfig.Builder, DatabaseConfig.Builder> modifier = builder -> strategySupplier == null\n        ? builder.healthCheckEnabled(false)\n        : builder.healthCheckStrategySupplier(strategySupplier);\n\n    List<DatabaseConfig> databaseConfigs = Arrays.stream(new EndpointConfig[] { endpoint1 })\n        .map(e -> modifier\n            .apply(MultiDbConfig.DatabaseConfig.builder(e.getHostAndPort(), clientConfig)).build())\n        .collect(Collectors.toList());\n\n    MultiDbConfig multiDbConfig = new MultiDbConfig.Builder(databaseConfigs)\n        .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(1).waitDuration(1).build())\n        .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(1)\n            .failureRateThreshold(100).build())\n        .build();\n\n    return multiDbConfig;\n  }\n\n  // ========== Probe Logic Integration Tests ==========\n  @Test\n  public void testProbingLogic_RealHealthCheckWithProbes() throws InterruptedException {\n    // Create a strategy that fails first few times then succeeds\n    AtomicInteger attemptCount = new AtomicInteger(0);\n\n    StrategySupplier strategySupplier = (hostAndPort, jedisClientConfig) -> {\n      // Fast interval, short timeout, 3 probes, short delay\n      return new TestHealthCheckStrategy(100, 50, 3, BuiltIn.ANY_SUCCESS, 20, (endpoint) -> {\n        int attempt = attemptCount.incrementAndGet();\n        if (attempt <= 2) {\n          // First 2 attempts fail\n          throw new RuntimeException(\"Simulated failure on attempt \" + attempt);\n        }\n        // Third attempt succeeds - do actual health check\n        try (UnifiedJedis jedis = new UnifiedJedis(hostAndPort, jedisClientConfig)) {\n          String result = jedis.ping();\n          return \"PONG\".equals(result) ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;\n        } catch (Exception e) {\n          return HealthStatus.UNHEALTHY;\n        }\n      });\n    };\n\n    CountDownLatch healthyLatch = new CountDownLatch(1);\n    HealthStatusManager manager = new HealthStatusManager();\n\n    // Register listener to detect when health becomes HEALTHY\n    manager.registerListener(endpoint1.getHostAndPort(), event -> {\n      if (event.getNewStatus() == HealthStatus.HEALTHY) {\n        healthyLatch.countDown();\n      }\n    });\n\n    // Add health check with strategy\n    HealthCheck healthCheck = manager.add(endpoint1.getHostAndPort(),\n      strategySupplier.get(endpoint1.getHostAndPort(), clientConfig));\n\n    try {\n      // Wait for health check to eventually succeed after probes\n      assertTrue(healthyLatch.await(5, TimeUnit.SECONDS),\n        \"Health check should succeed after probes\");\n\n      assertEquals(HealthStatus.HEALTHY, healthCheck.getStatus());\n\n      // Verify that multiple attempts were made (should be 3: 2 failures + 1 success)\n      assertTrue(attemptCount.get() >= 3,\n        \"Should have made at least 3 probes, but made: \" + attemptCount.get());\n\n    } finally {\n      manager.remove(endpoint1.getHostAndPort());\n    }\n  }\n\n  @Test\n  public void testProbingLogic_ExhaustProbesAndStayUnhealthy() throws InterruptedException {\n    // Create a strategy that always fails\n    AtomicInteger attemptCount = new AtomicInteger(0);\n\n    StrategySupplier alwaysFailSupplier = (hostAndPort, jedisClientConfig) -> {\n      // Fast interval, short timeout, 3 probes, short delay\n      return new TestHealthCheckStrategy(100, 50, 3, BuiltIn.ANY_SUCCESS, 10, (endpoint) -> {\n        attemptCount.incrementAndGet();\n        throw new RuntimeException(\"Always fails\");\n      });\n    };\n\n    CountDownLatch unhealthyLatch = new CountDownLatch(1);\n    HealthStatusManager manager = new HealthStatusManager();\n\n    // Register listener to detect when health becomes UNHEALTHY\n    manager.registerListener(endpoint1.getHostAndPort(), event -> {\n      if (event.getNewStatus() == HealthStatus.UNHEALTHY) {\n        unhealthyLatch.countDown();\n      }\n    });\n\n    // Add health check with always-fail strategy\n    HealthCheck healthCheck = manager.add(endpoint1.getHostAndPort(),\n      alwaysFailSupplier.get(endpoint1.getHostAndPort(), clientConfig));\n\n    try {\n      // Wait for health check to fail after exhausting probes\n      assertTrue(unhealthyLatch.await(3, TimeUnit.SECONDS),\n        \"Health check should become UNHEALTHY after exhausting probes\");\n\n      assertEquals(HealthStatus.UNHEALTHY, healthCheck.getStatus());\n\n      // Verify that all attempts were made (should 3 probes)\n      assertTrue(attemptCount.get() >= 3,\n        \"Should have made at least 3 attempts , but made: \" + attemptCount.get());\n\n    } finally {\n      manager.remove(endpoint1.getHostAndPort());\n    }\n  }\n\n  @Test\n  public void testProbingLogic_AllSuccess_EarlyFail_Integration() throws InterruptedException {\n    AtomicInteger attemptCount = new AtomicInteger(0);\n\n    StrategySupplier supplier = (hostAndPort, jedisClientConfig) -> new TestHealthCheckStrategy(100,\n        100, 3, BuiltIn.ALL_SUCCESS, 10, e -> {\n          int c = attemptCount.incrementAndGet();\n          return c == 1 ? HealthStatus.UNHEALTHY : HealthStatus.HEALTHY;\n        });\n\n    CountDownLatch unhealthyLatch = new CountDownLatch(1);\n    HealthStatusManager manager = new HealthStatusManager();\n\n    manager.registerListener(endpoint1.getHostAndPort(), event -> {\n      if (event.getNewStatus() == HealthStatus.UNHEALTHY) unhealthyLatch.countDown();\n    });\n\n    HealthCheck hc = manager.add(endpoint1.getHostAndPort(),\n      supplier.get(endpoint1.getHostAndPort(), clientConfig));\n\n    try {\n      assertTrue(unhealthyLatch.await(2, TimeUnit.SECONDS),\n        \"Should become UNHEALTHY after first failure with ALL_SUCCESS\");\n      assertEquals(HealthStatus.UNHEALTHY, hc.getStatus());\n      assertEquals(1, attemptCount.get());\n    } finally {\n      manager.remove(endpoint1.getHostAndPort());\n    }\n  }\n\n  @Test\n  public void testProbingLogic_Majority_EarlySuccess_Integration() throws InterruptedException {\n    AtomicInteger attemptCount = new AtomicInteger(0);\n\n    StrategySupplier supplier = (hostAndPort, jedisClientConfig) -> new TestHealthCheckStrategy(100,\n        100, 5, BuiltIn.MAJORITY_SUCCESS, 10, e -> {\n          int c = attemptCount.incrementAndGet();\n          return c <= 3 ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;\n        });\n\n    CountDownLatch healthyLatch = new CountDownLatch(1);\n    HealthStatusManager manager = new HealthStatusManager();\n\n    manager.registerListener(endpoint1.getHostAndPort(), event -> {\n      if (event.getNewStatus() == HealthStatus.HEALTHY) healthyLatch.countDown();\n    });\n\n    HealthCheck hc = manager.add(endpoint1.getHostAndPort(),\n      supplier.get(endpoint1.getHostAndPort(), clientConfig));\n\n    try {\n      assertTrue(healthyLatch.await(2, TimeUnit.SECONDS),\n        \"Should become HEALTHY after reaching majority successes\");\n      assertEquals(HealthStatus.HEALTHY, hc.getStatus());\n      assertEquals(3, attemptCount.get());\n    } finally {\n      manager.remove(endpoint1.getHostAndPort());\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/HealthCheckTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.mcf.ProbingPolicy.BuiltIn;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.*;\n\npublic class HealthCheckTest {\n\n  @Mock\n  private UnifiedJedis mockJedis;\n\n  @Mock\n  private HealthCheckStrategy mockStrategy;\n\n  private final HealthCheckStrategy alwaysHealthyStrategy = new TestHealthCheckStrategy(100, 50, 1,\n      BuiltIn.ANY_SUCCESS, 10, e -> HealthStatus.HEALTHY);\n\n  @Mock\n  private Consumer<HealthStatusChangeEvent> mockCallback;\n\n  private HostAndPort testEndpoint;\n  private JedisClientConfig testConfig;\n\n  @BeforeEach\n  void setUp() {\n    MockitoAnnotations.openMocks(this);\n    testEndpoint = new HostAndPort(\"dummy\", 6379);\n    testConfig = DefaultJedisClientConfig.builder().build();\n\n    // Default stubs for mockStrategy used across tests\n    when(mockStrategy.getNumProbes()).thenReturn(1);\n    when(mockStrategy.getDelayInBetweenProbes()).thenReturn(100);\n    when(mockStrategy.getPolicy()).thenReturn(BuiltIn.ANY_SUCCESS);\n  }\n\n  // ========== HealthCheckCollection Tests ==========\n\n  @Test\n  void testHealthCheckCollectionAdd() {\n    HealthCheckCollection collection = new HealthCheckCollection();\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback);\n\n    HealthCheck previous = collection.add(healthCheck);\n    assertNull(previous);\n\n    assertEquals(healthCheck, collection.get(testEndpoint));\n  }\n\n  @Test\n  void testHealthCheckCollectionRemoveByEndpoint() {\n    HealthCheckCollection collection = new HealthCheckCollection();\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback);\n\n    collection.add(healthCheck);\n    HealthCheck removed = collection.remove(testEndpoint);\n\n    assertEquals(healthCheck, removed);\n    assertNull(collection.get(testEndpoint));\n  }\n\n  @Test\n  void testHealthCheckCollectionAddAll() {\n    HealthCheckCollection collection = new HealthCheckCollection();\n    HealthCheck[] healthChecks = {\n        new HealthCheckImpl(new HostAndPort(\"host1\", 6379), mockStrategy, mockCallback),\n        new HealthCheckImpl(new HostAndPort(\"host2\", 6379), mockStrategy, mockCallback) };\n\n    HealthCheck[] previous = collection.addAll(healthChecks);\n\n    assertNotNull(previous);\n    assertEquals(2, previous.length);\n    assertNull(previous[0]); // No previous health check for host1\n    assertNull(previous[1]); // No previous health check for host2\n\n    assertEquals(healthChecks[0], collection.get(new HostAndPort(\"host1\", 6379)));\n    assertEquals(healthChecks[1], collection.get(new HostAndPort(\"host2\", 6379)));\n  }\n\n  @Test\n  void testHealthCheckCollectionReplacement() {\n    HealthCheckCollection collection = new HealthCheckCollection();\n    HealthCheck healthCheck1 = new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback);\n    HealthCheck healthCheck2 = new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback);\n\n    collection.add(healthCheck1);\n    HealthCheck previous = collection.add(healthCheck2);\n\n    assertEquals(healthCheck1, previous);\n    assertEquals(healthCheck2, collection.get(testEndpoint));\n  }\n\n  @Test\n  void testHealthCheckCollectionRemoveByHealthCheck() {\n    HealthCheckCollection collection = new HealthCheckCollection();\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback);\n\n    collection.add(healthCheck);\n    HealthCheck removed = collection.remove(healthCheck);\n\n    assertEquals(healthCheck, removed);\n    assertNull(collection.get(testEndpoint));\n  }\n\n  @Test\n  void testHealthCheckCollectionClose() {\n    HealthCheckCollection collection = new HealthCheckCollection();\n\n    // Create mock health checks\n    HealthCheck mockHealthCheck1 = spy(\n      new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback));\n\n    collection.add(mockHealthCheck1);\n\n    // Call close\n    collection.close();\n\n    // Verify stop was called on all health checks\n    verify(mockHealthCheck1).stop();\n  }\n  // ========== HealthCheck Tests ==========\n\n  @Test\n  void testHealthCheckStatusUpdate() throws InterruptedException {\n    when(mockStrategy.getInterval()).thenReturn(1);\n    when(mockStrategy.getTimeout()).thenReturn(50);\n    when(mockStrategy.doHealthCheck(any(Endpoint.class))).thenReturn(HealthStatus.UNHEALTHY);\n\n    CountDownLatch latch = new CountDownLatch(1);\n    Consumer<HealthStatusChangeEvent> callback = event -> {\n      assertEquals(HealthStatus.UNKNOWN, event.getOldStatus());\n      assertEquals(HealthStatus.UNHEALTHY, event.getNewStatus());\n      latch.countDown();\n    };\n\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, callback);\n    healthCheck.start();\n\n    assertTrue(latch.await(2, TimeUnit.SECONDS));\n    healthCheck.stop();\n  }\n\n  @Test\n  void testSafeUpdateChecksDoNotTriggerFalseNotifications() {\n    AtomicInteger notificationCount = new AtomicInteger(0);\n    Consumer<HealthStatusChangeEvent> callback = event -> notificationCount.incrementAndGet();\n\n    HealthCheckImpl healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, callback);\n\n    // Simulate concurrent health checks with different results\n    healthCheck.safeUpdate(2000, HealthStatus.HEALTHY); // Newer timestamp\n    healthCheck.safeUpdate(1000, HealthStatus.UNHEALTHY); // Older timestamp (should be ignored)\n\n    // Should only have 1 notification (for the first update), not 2\n    assertEquals(1, notificationCount.get());\n    assertEquals(HealthStatus.HEALTHY, healthCheck.getStatus());\n  }\n\n  @Test\n  void testSafeUpdateWithConcurrentResults() {\n    AtomicInteger notificationCount = new AtomicInteger(0);\n    Consumer<HealthStatusChangeEvent> callback = event -> notificationCount.incrementAndGet();\n\n    HealthCheckImpl healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, callback);\n\n    // Test the exact scenario: newer result first, then older result\n    healthCheck.safeUpdate(2000, HealthStatus.HEALTHY); // Should update and notify\n    assertEquals(1, notificationCount.get());\n    assertEquals(HealthStatus.HEALTHY, healthCheck.getStatus());\n\n    healthCheck.safeUpdate(1000, HealthStatus.UNHEALTHY); // Should NOT update or notify\n    assertEquals(1, notificationCount.get()); // Still 1, no additional notification\n    assertEquals(HealthStatus.HEALTHY, healthCheck.getStatus()); // Status unchanged\n  }\n\n  @Test\n  void testHealthCheckStop() {\n    when(mockStrategy.getInterval()).thenReturn(1000);\n    when(mockStrategy.getTimeout()).thenReturn(500);\n\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, mockStrategy, mockCallback);\n    healthCheck.start();\n\n    assertDoesNotThrow(healthCheck::stop);\n  }\n\n  // ========== HealthStatusManager Tests ==========\n\n  @Test\n  void testHealthStatusManagerRegisterListener() {\n    HealthStatusManager manager = new HealthStatusManager();\n    HealthStatusListener listener = mock(HealthStatusListener.class);\n\n    manager.registerListener(listener);\n\n    // Verify listener is registered by triggering an event\n    manager.add(testEndpoint, alwaysHealthyStrategy);\n    // Give some time for health check to run\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n    }\n\n    verify(listener, atLeastOnce()).onStatusChange(any(HealthStatusChangeEvent.class));\n  }\n\n  @Test\n  void testHealthStatusManagerUnregisterListener() {\n    HealthStatusManager manager = new HealthStatusManager();\n    HealthStatusListener listener = mock(HealthStatusListener.class);\n\n    manager.registerListener(listener);\n    manager.unregisterListener(listener);\n\n    manager.add(testEndpoint, alwaysHealthyStrategy);\n\n    // Give some time for potential health check\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n    }\n\n    verify(listener, never()).onStatusChange(any(HealthStatusChangeEvent.class));\n  }\n\n  @Test\n  void testHealthStatusManagerEndpointSpecificListener() {\n    HealthStatusManager manager = new HealthStatusManager();\n    HealthStatusListener listener = mock(HealthStatusListener.class);\n    HostAndPort otherEndpoint = new HostAndPort(\"other\", 6379);\n\n    manager.registerListener(testEndpoint, listener);\n    manager.add(testEndpoint, alwaysHealthyStrategy);\n    manager.add(otherEndpoint, alwaysHealthyStrategy);\n\n    // Give some time for health checks\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n    }\n\n    // Listener should only receive events for testEndpoint\n    verify(listener, atLeastOnce())\n        .onStatusChange(argThat(event -> event.getEndpoint().equals(testEndpoint)));\n  }\n\n  @Test\n  void testHealthStatusManagerLifecycle() throws InterruptedException {\n    HealthStatusManager manager = new HealthStatusManager();\n\n    // Before adding health check\n    assertEquals(HealthStatus.UNKNOWN, manager.getHealthStatus(testEndpoint));\n\n    // Set up event listener to wait for initial health check completion\n    CountDownLatch healthCheckCompleteLatch = new CountDownLatch(1);\n    HealthStatusListener listener = event -> healthCheckCompleteLatch.countDown();\n\n    // Register listener before adding health check to capture the initial event\n    manager.registerListener(testEndpoint, listener);\n\n    HealthCheckStrategy delayedStrategy = new TestHealthCheckStrategy(2000, 1000, 3,\n        BuiltIn.ALL_SUCCESS, 100, e -> HealthStatus.HEALTHY);\n\n    // Add health check - this will start async health checking\n    manager.add(testEndpoint, delayedStrategy);\n\n    // Initially should still be UNKNOWN until first check completes\n    assertEquals(HealthStatus.UNKNOWN, manager.getHealthStatus(testEndpoint));\n\n    // Wait for initial health check to complete\n    assertTrue(healthCheckCompleteLatch.await(2, TimeUnit.SECONDS),\n      \"Initial health check should complete within timeout\");\n\n    // Now should be HEALTHY after initial check\n    assertEquals(HealthStatus.HEALTHY, manager.getHealthStatus(testEndpoint));\n\n    // Clean up and verify removal\n    manager.remove(testEndpoint);\n    assertEquals(HealthStatus.UNKNOWN, manager.getHealthStatus(testEndpoint));\n  }\n\n  @Test\n  void testHealthStatusManagerClose() {\n    HealthCheckStrategy closeableStrategy = mock(HealthCheckStrategy.class);\n    when(closeableStrategy.getNumProbes()).thenReturn(1);\n    when(closeableStrategy.getInterval()).thenReturn(1000);\n    when(closeableStrategy.getTimeout()).thenReturn(500);\n    when(closeableStrategy.doHealthCheck(any(Endpoint.class))).thenReturn(HealthStatus.HEALTHY);\n\n    HealthStatusManager manager = new HealthStatusManager();\n\n    // Add health check\n    manager.add(testEndpoint, closeableStrategy);\n\n    // Close manager\n    manager.close();\n\n    // Verify health check is stopped\n    verify(closeableStrategy).close();\n  }\n\n  // ========== PingStrategy Tests ==========\n\n  @Test\n  void testPingStrategyCustomIntervalTimeout() {\n    try (PingStrategy strategy = new PingStrategy(testEndpoint, testConfig,\n        HealthCheckStrategy.Config.builder().interval(2000).timeout(1500).delayInBetweenProbes(50)\n            .numProbes(11).policy(BuiltIn.ANY_SUCCESS).build())) {\n      assertEquals(2000, strategy.getInterval());\n      assertEquals(1500, strategy.getTimeout());\n      assertEquals(11, strategy.getNumProbes());\n      assertEquals(BuiltIn.ANY_SUCCESS, strategy.getPolicy());\n      assertEquals(50, strategy.getDelayInBetweenProbes());\n    }\n  }\n\n  @Test\n  void testPingStrategyDefaultSupplier() {\n    MultiDbConfig.StrategySupplier supplier = PingStrategy.DEFAULT;\n    HealthCheckStrategy strategy = supplier.get(testEndpoint, testConfig);\n\n    assertInstanceOf(PingStrategy.class, strategy);\n  }\n\n  // ========== Failover configuration Tests ==========\n\n  @Test\n  void testNewFieldLocations() {\n    // Test new field locations in DatabaseConfig and MultiDbConfig\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).weight(2.5f).build();\n\n    MultiDbConfig multiConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).retryOnFailover(true)\n            .failbackSupported(false).build();\n\n    assertEquals(2.5f, databaseConfig.getWeight());\n    assertTrue(multiConfig.isRetryOnFailover());\n    assertFalse(multiConfig.isFailbackSupported());\n  }\n\n  @Test\n  void testDefaultValues() {\n    // Test default values in DatabaseConfig\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).build();\n\n    assertEquals(1.0f, databaseConfig.getWeight()); // Default weight\n    assertEquals(PingStrategy.DEFAULT, databaseConfig.getHealthCheckStrategySupplier()); // Default\n                                                                                         // is null\n                                                                                         // (no\n                                                                                         // health\n                                                                                         // check)\n\n    // Test default values in MultiDbConfig\n    MultiDbConfig multiConfig = new MultiDbConfig.Builder(\n        new MultiDbConfig.DatabaseConfig[] { databaseConfig }).build();\n\n    assertFalse(multiConfig.isRetryOnFailover()); // Default is false\n    assertTrue(multiConfig.isFailbackSupported()); // Default is true\n  }\n\n  @Test\n  void testDatabaseConfigWithHealthCheckStrategy() {\n    HealthCheckStrategy customStrategy = mock(HealthCheckStrategy.class);\n\n    MultiDbConfig.StrategySupplier supplier = (hostAndPort, jedisClientConfig) -> customStrategy;\n\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).healthCheckStrategySupplier(supplier).build();\n\n    assertNotNull(databaseConfig.getHealthCheckStrategySupplier());\n    HealthCheckStrategy result = databaseConfig.getHealthCheckStrategySupplier().get(testEndpoint,\n      testConfig);\n    assertEquals(customStrategy, result);\n  }\n\n  @Test\n  void testDatabaseConfigWithStrategySupplier() {\n    MultiDbConfig.StrategySupplier customSupplier = (hostAndPort, jedisClientConfig) -> {\n      return mock(HealthCheckStrategy.class);\n    };\n\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).healthCheckStrategySupplier(customSupplier).build();\n\n    assertEquals(customSupplier, databaseConfig.getHealthCheckStrategySupplier());\n  }\n\n  @Test\n  void testDatabaseConfigWithPingStrategy() {\n    MultiDbConfig.StrategySupplier pingSupplier = (hostAndPort, jedisClientConfig) -> {\n      return new PingStrategy(hostAndPort, jedisClientConfig);\n    };\n\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).healthCheckStrategySupplier(pingSupplier).build();\n\n    MultiDbConfig.StrategySupplier supplier = databaseConfig.getHealthCheckStrategySupplier();\n    assertNotNull(supplier);\n    assertInstanceOf(PingStrategy.class, supplier.get(testEndpoint, testConfig));\n  }\n\n  @Test\n  void testDatabaseConfigWithDefaultHealthCheck() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).build(); // Should use default PingStrategy\n\n    assertNotNull(databaseConfig.getHealthCheckStrategySupplier());\n    assertEquals(PingStrategy.DEFAULT, databaseConfig.getHealthCheckStrategySupplier());\n  }\n\n  @Test\n  void testDatabaseConfigWithDisabledHealthCheck() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).healthCheckEnabled(false).build();\n\n    assertNull(databaseConfig.getHealthCheckStrategySupplier());\n  }\n\n  @Test\n  void testDatabaseConfigHealthCheckEnabledExplicitly() {\n    MultiDbConfig.DatabaseConfig databaseConfig = MultiDbConfig.DatabaseConfig\n        .builder(testEndpoint, testConfig).healthCheckEnabled(true).build();\n\n    assertNotNull(databaseConfig.getHealthCheckStrategySupplier());\n    assertEquals(PingStrategy.DEFAULT, databaseConfig.getHealthCheckStrategySupplier());\n  }\n\n  // ========== Integration Tests ==========\n  @Test\n  @Timeout(5)\n  void testHealthCheckRecoversAfterException() throws InterruptedException {\n    // Create a strategy that alternates between exception/UNHEALTHY and HEALTHY\n    AtomicBoolean isHealthy = new AtomicBoolean(true);\n    AtomicInteger exceptionOccurred = new AtomicInteger(0);\n    int exceptionLimit = 2;\n    HealthCheckStrategy alternatingStrategy = new TestHealthCheckStrategy(\n        HealthCheckStrategy.Config.builder().interval(1).timeout(5).numProbes(1).build(), e -> {\n          if (isHealthy.get()) {\n            isHealthy.set(false);\n            if (exceptionOccurred.getAndIncrement() < exceptionLimit) {\n              throw new RuntimeException(\"Simulated exception\");\n            } else {\n              return HealthStatus.UNHEALTHY;\n            }\n          } else {\n            isHealthy.set(true);\n            return HealthStatus.HEALTHY;\n          }\n        });\n\n    // Wait for 2 status changes,\n    // it will start with unhealthy(due to simulated exception) and then switch to healthy\n    CountDownLatch statusChangeLatch = new CountDownLatch(2);\n    HealthStatusListener listener = event -> statusChangeLatch.countDown();\n\n    HealthStatusManager manager = new HealthStatusManager();\n    manager.registerListener(listener);\n    manager.add(testEndpoint, alternatingStrategy);\n\n    assertTrue(statusChangeLatch.await(1, TimeUnit.SECONDS));\n\n    manager.remove(testEndpoint);\n  }\n\n  @Test\n  @Timeout(5)\n  void testHealthCheckIntegration() throws InterruptedException {\n    // Create a mock strategy that alternates between healthy and unhealthy\n    AtomicReference<HealthStatus> statusToReturn = new AtomicReference<>(HealthStatus.HEALTHY);\n    HealthCheckStrategy alternatingStrategy = new TestHealthCheckStrategy(100, 50, 1,\n        BuiltIn.ALL_SUCCESS, 10, e -> {\n          HealthStatus current = statusToReturn.get();\n          statusToReturn\n              .set(current == HealthStatus.HEALTHY ? HealthStatus.UNHEALTHY : HealthStatus.HEALTHY);\n          return current;\n        });\n\n    CountDownLatch statusChangeLatch = new CountDownLatch(2); // Wait for 2 status changes\n    HealthStatusListener listener = event -> statusChangeLatch.countDown();\n\n    HealthStatusManager manager = new HealthStatusManager();\n    manager.registerListener(listener);\n    manager.add(testEndpoint, alternatingStrategy);\n\n    assertTrue(statusChangeLatch.await(3, TimeUnit.SECONDS));\n\n    manager.remove(testEndpoint);\n  }\n\n  @Test\n  void testStrategySupplierPolymorphism() {\n    // Test that the polymorphic design works correctly\n    MultiDbConfig.StrategySupplier supplier = (hostAndPort, jedisClientConfig) -> {\n      if (jedisClientConfig != null) {\n        return new PingStrategy(hostAndPort, jedisClientConfig,\n            HealthCheckStrategy.Config.builder().interval(500).timeout(250).numProbes(1).build());\n      } else {\n        return new PingStrategy(hostAndPort, DefaultJedisClientConfig.builder().build());\n      }\n    };\n\n    // Test with config\n    HealthCheckStrategy strategyWithConfig = supplier.get(testEndpoint, testConfig);\n    assertNotNull(strategyWithConfig);\n    assertEquals(500, strategyWithConfig.getInterval());\n    assertEquals(250, strategyWithConfig.getTimeout());\n\n    // Test without config\n    HealthCheckStrategy strategyWithoutConfig = supplier.get(testEndpoint, null);\n    assertNotNull(strategyWithoutConfig);\n    assertEquals(5000, strategyWithoutConfig.getInterval()); // Default values\n    assertEquals(1000, strategyWithoutConfig.getTimeout());\n  }\n\n  // ========== Retry Logic Unit Tests ==========\n\n  @Test\n  void testRetryLogic_SuccessOnFirstAttempt() throws InterruptedException {\n    AtomicInteger callCount = new AtomicInteger(0);\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(100, 50, 2, BuiltIn.ANY_SUCCESS,\n        10, e -> {\n          callCount.incrementAndGet();\n          return HealthStatus.HEALTHY; // Always succeeds\n        });\n\n    CountDownLatch latch = new CountDownLatch(1);\n    Consumer<HealthStatusChangeEvent> callback = event -> latch.countDown();\n\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, strategy, callback);\n    healthCheck.start();\n\n    assertTrue(latch.await(2, TimeUnit.SECONDS));\n    assertEquals(HealthStatus.HEALTHY, healthCheck.getStatus());\n\n    // Should only call doHealthCheck once (no retries needed)\n    assertEquals(1, callCount.get());\n\n    healthCheck.stop();\n  }\n\n  @Test\n  void testRetryLogic_FailThenSucceedOnRetry() throws InterruptedException {\n    AtomicInteger callCount = new AtomicInteger(0);\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(100, 50, 2, BuiltIn.ANY_SUCCESS,\n        10, e -> {\n          int attempt = callCount.incrementAndGet();\n          if (attempt == 1) {\n            throw new RuntimeException(\"First attempt fails\");\n          }\n          return HealthStatus.HEALTHY; // Second attempt succeeds\n        });\n\n    CountDownLatch latch = new CountDownLatch(1);\n    Consumer<HealthStatusChangeEvent> callback = event -> {\n      if (event.getNewStatus() == HealthStatus.HEALTHY) {\n        latch.countDown();\n      }\n    };\n\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, strategy, callback);\n    healthCheck.start();\n\n    assertTrue(latch.await(3, TimeUnit.SECONDS));\n    assertEquals(HealthStatus.HEALTHY, healthCheck.getStatus());\n\n    // Should call doHealthCheck twice (first fails, second succeeds)\n    assertEquals(2, callCount.get());\n\n    healthCheck.stop();\n  }\n\n  @Test\n  void testRetryLogic_ExhaustAllProbesAndFail() throws InterruptedException {\n    AtomicInteger callCount = new AtomicInteger(0);\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(100, 50, 3, BuiltIn.ANY_SUCCESS,\n        10, e -> {\n          callCount.incrementAndGet();\n          throw new RuntimeException(\"Always fails\");\n        });\n\n    CountDownLatch latch = new CountDownLatch(1);\n    Consumer<HealthStatusChangeEvent> callback = event -> {\n      if (event.getNewStatus() == HealthStatus.UNHEALTHY) {\n        latch.countDown();\n      }\n    };\n\n    HealthCheck healthCheck = new HealthCheckImpl(testEndpoint, strategy, callback);\n    healthCheck.start();\n\n    assertTrue(latch.await(3, TimeUnit.SECONDS));\n    assertEquals(HealthStatus.UNHEALTHY, healthCheck.getStatus());\n\n    // Should call doHealthCheck 3 times (all probes fail)\n    assertEquals(3, callCount.get());\n\n    healthCheck.stop();\n  }\n\n  @Test\n  void testRetryLogic_ZeroProbes() throws InterruptedException {\n    AtomicInteger callCount = new AtomicInteger(0);\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(100, 50, 0, BuiltIn.ANY_SUCCESS,\n        10, e -> {\n          callCount.incrementAndGet();\n          throw new RuntimeException(\"Fails\");\n        });\n\n    CountDownLatch latch = new CountDownLatch(1);\n    Consumer<HealthStatusChangeEvent> callback = event -> {\n      if (event.getNewStatus() == HealthStatus.UNHEALTHY) {\n        latch.countDown();\n      }\n    };\n\n    assertThrows(IllegalArgumentException.class,\n      () -> new HealthCheckImpl(testEndpoint, strategy, callback));\n  }\n\n  @Test\n  void testRetryLogic_NegativeProbes() throws InterruptedException {\n    AtomicInteger callCount = new AtomicInteger(0);\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(100, 50, -1, BuiltIn.ANY_SUCCESS,\n        10, e -> {\n          callCount.incrementAndGet();\n          throw new RuntimeException(\"Fails\");\n        });\n\n    CountDownLatch latch = new CountDownLatch(1);\n    Consumer<HealthStatusChangeEvent> callback = event -> {\n      if (event.getNewStatus() == HealthStatus.UNHEALTHY) {\n        latch.countDown();\n      }\n    };\n\n    assertThrows(IllegalArgumentException.class,\n      () -> new HealthCheckImpl(testEndpoint, strategy, callback));\n  }\n\n  /**\n   * <p>\n   * - Verifies that the health check probes stop after the first probe when the scheduler thread is\n   * interrupted.\n   * <p>\n   * - The scheduler thread is the one that calls healthCheck(), which in turn calls\n   * doHealthCheck().\n   * <p>\n   * - This test interrupts the scheduler thread while it is waiting on the future from the first\n   * probe.\n   * <p>\n   * - The health check operation itself is not interrupted. This test does not validate\n   * interruption of the health check operation itself, as that is not the responsibility of the\n   * HealthCheckImpl.\n   */\n  @Test\n  void testRetryLogic_InterruptionStopsProbes() throws Exception {\n    AtomicInteger callCount = new AtomicInteger(0);\n    CountDownLatch schedulerTaskStarted = new CountDownLatch(1);\n    CountDownLatch statusChanged = new CountDownLatch(1);\n\n    Thread[] schedulerThread = new Thread[1];\n\n    final int OPERATION_TIMEOUT = 1000;\n    final int LESS_THAN_OPERATION_TIMEOUT = 800;\n    final int NUM_PROBES = 3;\n    // Long interval so no second run, generous timeout so we can interrupt while waiting\n    Function<Endpoint, HealthStatus> healthCheckOperation = e -> {\n      callCount.incrementAndGet();\n      try {\n        Thread.sleep(LESS_THAN_OPERATION_TIMEOUT); // keep worker busy so scheduler waits on\n                                                   // future.get\n      } catch (InterruptedException ie) {\n      }\n      return HealthStatus.UNHEALTHY;\n    };\n\n    // Override getPolicy() to capture the scheduler thread\n    HealthCheckStrategy strategy = new TestHealthCheckStrategy(5000, OPERATION_TIMEOUT, NUM_PROBES,\n        BuiltIn.ANY_SUCCESS, 10, healthCheckOperation) {\n      public ProbingPolicy getPolicy() {\n        schedulerThread[0] = Thread.currentThread();\n        schedulerTaskStarted.countDown();\n        return super.getPolicy();\n      }\n    };\n\n    Consumer<HealthStatusChangeEvent> callback = evt -> statusChanged.countDown();\n\n    HealthCheckImpl hc = new HealthCheckImpl(testEndpoint, strategy, callback);\n    hc.start();\n\n    // Ensure first probe is in flight (scheduler is blocked on future.get)\n    assertTrue(schedulerTaskStarted.await(1, TimeUnit.SECONDS), \"Task should have started\");\n\n    // Interrupt the scheduler thread that runs HealthCheckImpl.healthCheck()\n    schedulerThread[0].interrupt();\n\n    // No status change should be published because healthCheck() returns early without safeUpdate\n    assertFalse(statusChanged.await(hc.getMaxWaitFor(), TimeUnit.MILLISECONDS),\n      \"No status change expected\");\n    assertEquals(HealthStatus.UNKNOWN, hc.getStatus());\n\n    // Only the first probe should have been attempted\n    int calls = callCount.get();\n    assertTrue(calls <= 1, \"Only one probe should have been attempted: \" + calls);\n\n    hc.stop();\n  }\n\n  // ========== ProbingPolicy Unit Tests ==========\n  @Test\n  void testPolicy_AllSuccess_StopsOnFirstFailure() throws Exception {\n    AtomicInteger callCount = new AtomicInteger(0);\n    CountDownLatch unhealthyLatch = new CountDownLatch(1);\n\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(\n        HealthCheckStrategy.Config.builder().interval(5).timeout(200).numProbes(3)\n            .policy(BuiltIn.ALL_SUCCESS).delayInBetweenProbes(5).build(),\n        e -> {\n          int c = callCount.incrementAndGet();\n          return c == 1 ? HealthStatus.UNHEALTHY : HealthStatus.HEALTHY;\n        });\n\n    HealthCheckImpl hc = new HealthCheckImpl(testEndpoint, strategy, evt -> {\n      if (evt.getNewStatus() == HealthStatus.UNHEALTHY) unhealthyLatch.countDown();\n    });\n\n    hc.start();\n    assertTrue(unhealthyLatch.await(1, TimeUnit.SECONDS));\n    assertEquals(HealthStatus.UNHEALTHY, hc.getStatus());\n    assertEquals(1, callCount.get(), \"ALL_SUCCESS should stop after first failure\");\n    hc.stop();\n  }\n\n  @Test\n  void testPolicy_Majority_EarlySuccessStopsAtThree() throws Exception {\n    AtomicInteger callCount = new AtomicInteger(0);\n    CountDownLatch healthyLatch = new CountDownLatch(1);\n\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(\n        HealthCheckStrategy.Config.builder().interval(5000).timeout(200).numProbes(5)\n            .policy(BuiltIn.MAJORITY_SUCCESS).delayInBetweenProbes(5).build(),\n        e -> {\n          int c = callCount.incrementAndGet();\n          return c <= 3 ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;\n        });\n\n    HealthCheckImpl hc = new HealthCheckImpl(testEndpoint, strategy, evt -> {\n      if (evt.getNewStatus() == HealthStatus.HEALTHY) healthyLatch.countDown();\n    });\n\n    hc.start();\n    assertTrue(healthyLatch.await(1, TimeUnit.SECONDS));\n    assertEquals(HealthStatus.HEALTHY, hc.getStatus());\n    assertEquals(3, callCount.get(), \"MAJORITY early success should stop after 3 successes\");\n    hc.stop();\n  }\n\n  @Test\n  void testPolicy_Majority_EarlyFailStopsAtTwo() throws Exception {\n    AtomicInteger callCount = new AtomicInteger(0);\n    CountDownLatch unhealthyLatch = new CountDownLatch(1);\n\n    TestHealthCheckStrategy strategy = new TestHealthCheckStrategy(\n        HealthCheckStrategy.Config.builder().interval(5000).timeout(200).numProbes(4)\n            .policy(BuiltIn.MAJORITY_SUCCESS).delayInBetweenProbes(5).build(),\n        e -> {\n          int c = callCount.incrementAndGet();\n          return c <= 2 ? HealthStatus.UNHEALTHY : HealthStatus.HEALTHY;\n        });\n\n    HealthCheckImpl hc = new HealthCheckImpl(testEndpoint, strategy, evt -> {\n      if (evt.getNewStatus() == HealthStatus.UNHEALTHY) unhealthyLatch.countDown();\n    });\n\n    hc.start();\n    assertTrue(unhealthyLatch.await(1, TimeUnit.SECONDS));\n    assertEquals(HealthStatus.UNHEALTHY, hc.getStatus());\n    assertEquals(2, callCount.get(), \"MAJORITY early fail should stop when majority impossible\");\n    hc.stop();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/InitializationPolicyTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.mcf.InitializationPolicy.Decision;\nimport redis.clients.jedis.mcf.InitializationPolicy.InitializationContext;\n\n/**\n * Unit tests for {@link InitializationPolicy} implementations.\n * <p>\n * Tests verify the decision logic for different combinations of available, failed, and pending\n * connections for each built-in policy:\n * <ul>\n * <li>{@link InitializationPolicy.BuiltIn#ALL_AVAILABLE}</li>\n * <li>{@link InitializationPolicy.BuiltIn#MAJORITY_AVAILABLE}</li>\n * <li>{@link InitializationPolicy.BuiltIn#ONE_AVAILABLE}</li>\n * </ul>\n */\n@DisplayName(\"InitializationPolicy Unit Tests\")\npublic class InitializationPolicyTest {\n\n  /**\n   * Test implementation of InitializationContext for unit testing.\n   */\n  private static class TestContext implements InitializationContext {\n\n    private final int available;\n    private final int failed;\n    private final int pending;\n\n    TestContext(int available, int failed, int pending) {\n      this.available = available;\n      this.failed = failed;\n      this.pending = pending;\n    }\n\n    @Override\n    public int getAvailableConnections() {\n      return available;\n    }\n\n    @Override\n    public int getFailedConnections() {\n      return failed;\n    }\n\n    @Override\n    public int getPendingConnections() {\n      return pending;\n    }\n  }\n\n  @Nested\n  @DisplayName(\"ALL_AVAILABLE Policy Tests\")\n  class AllAvailablePolicyTests {\n\n    private final InitializationPolicy policy = InitializationPolicy.BuiltIn.ALL_AVAILABLE;\n\n    @Test\n    @DisplayName(\"Should return SUCCESS when all connections are available\")\n    void shouldSucceedWhenAllAvailable() {\n      TestContext ctx = new TestContext(3, 0, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS when single connection is available and no others\")\n    void shouldSucceedWithSingleConnection() {\n      TestContext ctx = new TestContext(1, 0, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when no connections configured (0,0,0)\")\n    void shouldFailWithEmptyContext() {\n      TestContext ctx = new TestContext(0, 0, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when any connection fails\")\n    void shouldFailWhenAnyConnectionFails() {\n      TestContext ctx = new TestContext(2, 1, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when all connections fail\")\n    void shouldFailWhenAllConnectionsFail() {\n      TestContext ctx = new TestContext(0, 3, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when single connection fails\")\n    void shouldFailWithSingleFailure() {\n      TestContext ctx = new TestContext(0, 1, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL immediately when any failure even with pending\")\n    void shouldFailImmediatelyWithAnyFailure() {\n      TestContext ctx = new TestContext(1, 1, 2);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when connections are pending\")\n    void shouldContinueWhenPending() {\n      TestContext ctx = new TestContext(2, 0, 1);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when all connections are pending\")\n    void shouldContinueWhenAllPending() {\n      TestContext ctx = new TestContext(0, 0, 5);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n  }\n\n  @Nested\n  @DisplayName(\"MAJORITY_AVAILABLE Policy Tests\")\n  class MajorityAvailablePolicyTests {\n\n    private final InitializationPolicy policy = InitializationPolicy.BuiltIn.MAJORITY_AVAILABLE;\n\n    @Test\n    @DisplayName(\"Should return SUCCESS when majority is reached (3 of 5)\")\n    void shouldSucceedWithMajority() {\n      TestContext ctx = new TestContext(3, 1, 1);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS when all are available\")\n    void shouldSucceedWhenAllAvailable() {\n      TestContext ctx = new TestContext(5, 0, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS with exact majority (2 of 3)\")\n    void shouldSucceedWithExactMajority() {\n      TestContext ctx = new TestContext(2, 1, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS with single connection out of one\")\n    void shouldSucceedWithSingleConnection() {\n      TestContext ctx = new TestContext(1, 0, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS early when majority reached with pending (3 of 5)\")\n    void shouldSucceedEarlyWithMajority() {\n      TestContext ctx = new TestContext(3, 0, 2);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS early when majority reached (2 of 3, 1 pending)\")\n    void shouldSucceedEarlyWithMajorityOf3() {\n      TestContext ctx = new TestContext(2, 0, 1);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should handle even number of connections (3 of 4)\")\n    void shouldHandleEvenNumberOfConnections() {\n      TestContext ctx = new TestContext(3, 1, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should handle two connections (2 of 2)\")\n    void shouldHandleTwoConnections() {\n      TestContext ctx = new TestContext(2, 0, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS with large number of connections (51 of 100)\")\n    void shouldSucceedWithLargeNumberOfConnections() {\n      TestContext ctx = new TestContext(51, 49, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when no connections configured (0,0,0)\")\n    void shouldFailWithEmptyContext() {\n      TestContext ctx = new TestContext(0, 0, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when majority is impossible (2 failed of 3)\")\n    void shouldFailWhenMajorityImpossible() {\n      TestContext ctx = new TestContext(0, 2, 1);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when all connections fail\")\n    void shouldFailWhenAllFail() {\n      TestContext ctx = new TestContext(0, 5, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when single database fails\")\n    void shouldFailWithSingleDatabaseFailed() {\n      TestContext ctx = new TestContext(0, 1, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when majority not reached and no pending (1 of 3)\")\n    void shouldFailWhenMajorityNotReachedNoPending() {\n      TestContext ctx = new TestContext(1, 2, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL early when majority impossible with pending (1,3,1)\")\n    void shouldFailEarlyWhenMajorityImpossibleWithPending() {\n      TestContext ctx = new TestContext(1, 3, 1);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL early when majority impossible (3 failed of 5, 2 pending)\")\n    void shouldFailEarlyWhenMajorityImpossible() {\n      TestContext ctx = new TestContext(0, 3, 2);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should fail with even split (2 of 4)\")\n    void shouldFailWithEvenSplit() {\n      TestContext ctx = new TestContext(2, 2, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should fail with one of two connections\")\n    void shouldFailWithOneOfTwo() {\n      TestContext ctx = new TestContext(1, 1, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when majority possible but not yet reached\")\n    void shouldContinueWhenMajorityPossible() {\n      TestContext ctx = new TestContext(1, 1, 3);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when all pending\")\n    void shouldContinueWhenAllPending() {\n      TestContext ctx = new TestContext(0, 0, 5);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when below majority with pending (2 of 4)\")\n    void shouldContinueBelowMajority() {\n      TestContext ctx = new TestContext(2, 0, 2);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE with one available and one pending (1,0,1)\")\n    void shouldContinueWithOneAvailableOnePending() {\n      TestContext ctx = new TestContext(1, 0, 1);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when majority still possible (1,1,1)\")\n    void shouldContinueWhenMajorityStillPossible() {\n      TestContext ctx = new TestContext(1, 1, 1);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n  }\n\n  @Nested\n  @DisplayName(\"ONE_AVAILABLE Policy Tests\")\n  class OneAvailablePolicyTests {\n\n    private final InitializationPolicy policy = InitializationPolicy.BuiltIn.ONE_AVAILABLE;\n\n    @Test\n    @DisplayName(\"Should return SUCCESS when one connection is available\")\n    void shouldSucceedWithOneAvailable() {\n      TestContext ctx = new TestContext(1, 2, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS when all connections are available\")\n    void shouldSucceedWhenAllAvailable() {\n      TestContext ctx = new TestContext(5, 0, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS early with one available and pending\")\n    void shouldSucceedEarlyWithOneAvailable() {\n      TestContext ctx = new TestContext(1, 0, 4);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return SUCCESS with multiple available\")\n    void shouldSucceedWithMultipleAvailable() {\n      TestContext ctx = new TestContext(3, 2, 0);\n      assertEquals(Decision.SUCCESS, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when no connections configured (0,0,0)\")\n    void shouldFailWithEmptyContext() {\n      TestContext ctx = new TestContext(0, 0, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when all connections fail\")\n    void shouldFailWhenAllFail() {\n      TestContext ctx = new TestContext(0, 5, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return FAIL when single connection fails\")\n    void shouldFailWithSingleFailure() {\n      TestContext ctx = new TestContext(0, 1, 0);\n      assertEquals(Decision.FAIL, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when all connections are pending\")\n    void shouldContinueWhenAllPending() {\n      TestContext ctx = new TestContext(0, 0, 5);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n\n    @Test\n    @DisplayName(\"Should return CONTINUE when some failed but some pending\")\n    void shouldContinueWithFailedAndPending() {\n      TestContext ctx = new TestContext(0, 2, 3);\n      assertEquals(Decision.CONTINUE, policy.evaluate(ctx));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/LagAwareStrategyUnitTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.*;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.function.Supplier;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedConstruction;\n\nimport redis.clients.jedis.DefaultRedisCredentials;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.RedisCredentials;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.mcf.LagAwareStrategy.Config;\n\npublic class LagAwareStrategyUnitTest {\n\n  private Endpoint endpoint;\n  private Supplier<RedisCredentials> creds;\n\n  @BeforeEach\n  void setup() {\n    endpoint = new Endpoint() {\n      @Override\n      public String getHost() {\n        return \"dummy\";\n      }\n\n      @Override\n      public int getPort() {\n        return 8443;\n      }\n    };\n    creds = () -> new DefaultRedisCredentials(\"user\", \"pwd\");\n  }\n\n  @Test\n  void healthy_when_bdb_available_and_cached_uid_used_on_next_check() throws Exception {\n    RedisRestAPI.BdbInfo bdbInfo = new RedisRestAPI.BdbInfo(\"1\", Arrays\n        .asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"127.0.0.1\"), \"dummy\", 6379, \"1:1\")));\n\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        when(mock.getBdbs()).thenReturn(Arrays.asList(bdbInfo));\n        when(mock.checkBdbAvailability(\"1\", true, 5000L)).thenReturn(true);\n        reference[0] = mock;\n      })) {\n      Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250)\n          .numProbes(2).build();\n      try (LagAwareStrategy strategy = new LagAwareStrategy(lagCheckConfig)) {\n        assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));\n        RedisRestAPI api = reference[0];\n\n        assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));\n        verify(api, times(1)).getBdbs(); // Should not call getBdbs again when cached\n        verify(api, times(2)).checkBdbAvailability(\"1\", true, 5000L);\n      }\n    }\n  }\n\n  @Test\n  void exception_when_no_bdb_returned() throws Exception {\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        when(mock.getBdbs()).thenReturn(Collections.emptyList()); // No BDBs found\n        reference[0] = mock;\n      })) {\n\n      Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250)\n          .numProbes(1).build();\n      try (LagAwareStrategy strategy = new LagAwareStrategy(lagCheckConfig)) {\n        assertThrows(JedisException.class, () -> strategy.doHealthCheck(endpoint));\n        RedisRestAPI api = reference[0];\n        verify(api, times(1)).getBdbs();\n        verify(api, never()).checkBdbAvailability(anyString(), anyBoolean());\n      }\n    }\n  }\n\n  @Test\n  void exception_and_cache_reset_on_exception_then_recovers_next_time() throws Exception {\n    RedisRestAPI.BdbInfo bdbInfo = new RedisRestAPI.BdbInfo(\"42\", Arrays\n        .asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"127.0.0.1\"), \"dummy\", 6379, \"1:1\")));\n\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        // First call throws exception, second call returns bdbInfo\n        when(mock.getBdbs()).thenThrow(new RuntimeException(\"boom\"))\n            .thenReturn(Arrays.asList(bdbInfo));\n        when(mock.checkBdbAvailability(\"42\", true, 5000L)).thenReturn(true);\n        reference[0] = mock;\n      })) {\n\n      Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250)\n          .numProbes(1).build();\n      try (LagAwareStrategy strategy = new LagAwareStrategy(lagCheckConfig)) {\n        RedisRestAPI api = reference[0];\n\n        // First call should throw JedisException due to getBdbs() throwing RuntimeException\n        assertThrows(JedisException.class, () -> strategy.doHealthCheck(endpoint));\n\n        // Second call should succeed - getBdbs() now returns bdbInfo and availability check passes\n        assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));\n\n        // Verify getBdbs was called twice (once failed, once succeeded)\n        verify(api, times(2)).getBdbs();\n        // Verify availability check was called only once (on the successful attempt)\n        verify(api, times(1)).checkBdbAvailability(\"42\", true, 5000L);\n      }\n    }\n  }\n\n  @Test\n  void healthy_when_matching_bdb_found_by_host() throws Exception {\n    RedisRestAPI.BdbInfo matchingBdb = new RedisRestAPI.BdbInfo(\"matched-bdb-123\", Arrays\n        .asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"127.0.0.1\"), \"dummy\", 6379, \"1:1\")));\n\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        when(mock.getBdbs()).thenReturn(Arrays.asList(matchingBdb));\n        when(mock.checkBdbAvailability(\"matched-bdb-123\", true, 100L)).thenReturn(true);\n        reference[0] = mock;\n      })) {\n      Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250)\n          .numProbes(2).extendedCheckEnabled(true).availabilityLagTolerance(Duration.ofMillis(100))\n          .build();\n      try (LagAwareStrategy strategy = new LagAwareStrategy(lagCheckConfig)) {\n        assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));\n        RedisRestAPI api = reference[0];\n        verify(api, times(1)).getBdbs();\n        verify(api, times(1)).checkBdbAvailability(\"matched-bdb-123\", true, 100L);\n      }\n    }\n  }\n\n  @Test\n  void exception_when_no_matching_host_found() throws Exception {\n    RedisRestAPI.BdbInfo nonMatchingBdb = new RedisRestAPI.BdbInfo(\"other-bdb-456\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"192.168.1.100\"),\n            \"other-host.example.com\", 6379, \"2:1\")));\n\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        when(mock.getBdbs()).thenReturn(Arrays.asList(nonMatchingBdb)); // BDB that doesn't match\n                                                                        // localhost\n        reference[0] = mock;\n      })) {\n      Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250)\n          .numProbes(2).build();\n      try (LagAwareStrategy strategy = new LagAwareStrategy(lagCheckConfig)) {\n        assertThrows(JedisException.class, () -> strategy.doHealthCheck(endpoint));\n        RedisRestAPI api = reference[0];\n        verify(api, times(1)).getBdbs();\n        verify(api, never()).checkBdbAvailability(anyString(), anyBoolean()); // Should not check\n                                                                              // availability\n      }\n    }\n  }\n\n  @Test\n  void config_builder_creates_config_with_default_values() {\n    Config config = Config.builder(endpoint, creds).build();\n\n    assertEquals(5000, config.interval);\n    assertEquals(1000, config.timeout);\n    assertEquals(3, config.numProbes);\n    assertEquals(Duration.ofMillis(5000), config.getAvailabilityLagTolerance());\n    assertEquals(endpoint, config.getRestEndpoint());\n    assertEquals(creds, config.getCredentialsSupplier());\n  }\n\n  @Test\n  void config_builder_creates_config_with_custom_values() {\n    Config config = Config.builder(endpoint, creds).interval(500).timeout(250).numProbes(2)\n        .availabilityLagTolerance(Duration.ofMillis(50)).build();\n\n    assertEquals(500, config.interval);\n    assertEquals(250, config.timeout);\n    assertEquals(2, config.numProbes);\n    assertEquals(Duration.ofMillis(50), config.getAvailabilityLagTolerance());\n    assertEquals(endpoint, config.getRestEndpoint());\n    assertEquals(creds, config.getCredentialsSupplier());\n  }\n\n  @Test\n  void config_builder_allows_fluent_chaining() {\n    // Test that all builder methods return the builder instance for chaining\n    Config config = Config.builder(endpoint, creds).interval(800).timeout(400).numProbes(5)\n        .availabilityLagTolerance(Duration.ofMillis(200)).build();\n\n    assertNotNull(config);\n    assertEquals(800, config.interval);\n    assertEquals(400, config.timeout);\n    assertEquals(5, config.numProbes);\n    assertEquals(Duration.ofMillis(200), config.getAvailabilityLagTolerance());\n  }\n\n  @Test\n  void config_builder_creates_config_with_extended_check_enabled() {\n    Config config = Config.builder(endpoint, creds).extendedCheckEnabled(true)\n        .availabilityLagTolerance(Duration.ofMillis(150)).build();\n\n    assertTrue(config.isExtendedCheckEnabled());\n    assertEquals(Duration.ofMillis(150), config.getAvailabilityLagTolerance());\n  }\n\n  @Test\n  void config_builder_creates_config_with_extended_check_disabled_by_default() {\n    Config config = Config.builder(endpoint, creds).build();\n\n    assertTrue(config.isExtendedCheckEnabled());\n  }\n\n  @Test\n  void healthy_when_extended_check_enabled_and_lag_check_passes() throws Exception {\n    RedisRestAPI.BdbInfo bdbInfo = new RedisRestAPI.BdbInfo(\"1\", Arrays\n        .asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"127.0.0.1\"), \"dummy\", 6379, \"1:1\")));\n\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        when(mock.getBdbs()).thenReturn(Arrays.asList(bdbInfo));\n        when(mock.checkBdbAvailability(\"1\", true, 100L)).thenReturn(true);\n        reference[0] = mock;\n      })) {\n\n      Config config = Config.builder(endpoint, creds).extendedCheckEnabled(true)\n          .availabilityLagTolerance(Duration.ofMillis(100)).build();\n\n      try (LagAwareStrategy strategy = new LagAwareStrategy(config)) {\n        assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));\n        RedisRestAPI api = reference[0];\n        verify(api, times(1)).getBdbs();\n        verify(api, times(1)).checkBdbAvailability(\"1\", true, 100L);\n        verify(api, never()).checkBdbAvailability(\"1\", false);\n      }\n    }\n  }\n\n  @Test\n  void healthy_when_extended_check_disabled_and_standard_check_passes() throws Exception {\n    RedisRestAPI.BdbInfo bdbInfo = new RedisRestAPI.BdbInfo(\"1\", Arrays\n        .asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"127.0.0.1\"), \"dummy\", 6379, \"1:1\")));\n\n    RedisRestAPI[] reference = new RedisRestAPI[1];\n    try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,\n      (mock, context) -> {\n        when(mock.getBdbs()).thenReturn(Arrays.asList(bdbInfo));\n        when(mock.checkBdbAvailability(\"1\", false)).thenReturn(true);\n        reference[0] = mock;\n      })) {\n\n      Config config = Config.builder(endpoint, creds).extendedCheckEnabled(false).build();\n\n      try (LagAwareStrategy strategy = new LagAwareStrategy(config)) {\n        assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));\n        RedisRestAPI api = reference[0];\n        verify(api, times(1)).getBdbs();\n        verify(api, times(1)).checkBdbAvailability(\"1\", false);\n        verify(api, never()).checkBdbAvailability(eq(\"1\"), eq(true), any());\n      }\n    }\n  }\n\n  @Test\n  void base_config_builder_factory_method_works() {\n    HealthCheckStrategy.Config config = HealthCheckStrategy.Config.builder().interval(2000)\n        .timeout(1500).numProbes(5).build();\n\n    assertEquals(2000, config.getInterval());\n    assertEquals(1500, config.getTimeout());\n    assertEquals(5, config.getNumProbes());\n  }\n\n  @Test\n  void base_config_create_factory_method_uses_defaults() {\n    HealthCheckStrategy.Config config = HealthCheckStrategy.Config.create();\n\n    assertEquals(5000, config.getInterval());\n    assertEquals(1000, config.getTimeout());\n    assertEquals(3, config.getNumProbes());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/MultiDbCircuitBreakerThresholdsTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\nimport redis.clients.jedis.util.ReflectionTestUtil;\n\n/**\n * Tests for circuit breaker thresholds: both failure-rate threshold and minimum number of failures\n * must be exceeded to trigger failover. Uses a real CircuitBreaker and real Retry, but mocks the\n * provider and database wiring to avoid network I/O.\n */\npublic class MultiDbCircuitBreakerThresholdsTest {\n\n  private MultiDbConnectionProvider realProvider;\n  private MultiDbConnectionProvider spyProvider;\n  private Database database;\n  private MultiDbCommandExecutor executor;\n  private CommandObject<String> dummyCommand;\n  private TrackingConnectionPool poolMock;\n  private final HostAndPort fakeEndpoint = new HostAndPort(\"fake\", 6379);\n  private final HostAndPort fakeEndpoint2 = new HostAndPort(\"fake2\", 6379);\n  private DatabaseConfig[] fakeDatabaseConfigs;\n\n  @BeforeEach\n  public void setup() throws Exception {\n\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[] {\n        DatabaseConfig.builder(fakeEndpoint, DefaultJedisClientConfig.builder().build())\n            .healthCheckEnabled(false).weight(1.0f).build(),\n        DatabaseConfig.builder(fakeEndpoint2, DefaultJedisClientConfig.builder().build())\n            .healthCheckEnabled(false).weight(0.5f).build() };\n    fakeDatabaseConfigs = databaseConfigs;\n\n    MultiDbConfig.Builder cfgBuilder = MultiDbConfig.builder(databaseConfigs)\n        .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().failureRateThreshold(50.0f)\n            .minNumOfFailures(3).slidingWindowSize(10).build())\n        .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(1).build())\n        .retryOnFailover(false);\n\n    MultiDbConfig mcc = cfgBuilder.build();\n\n    realProvider = new MultiDbConnectionProvider(mcc);\n    spyProvider = spy(realProvider);\n\n    database = spyProvider.getDatabase();\n\n    executor = new MultiDbCommandExecutor(spyProvider);\n\n    dummyCommand = new CommandObject<>(new CommandArguments(Protocol.Command.PING),\n        BuilderFactory.STRING);\n\n    // Replace the database's pool with a mock to avoid real network I/O\n    poolMock = mock(TrackingConnectionPool.class);\n    ReflectionTestUtil.setField(database, \"connectionPool\", poolMock);\n  }\n\n  /**\n   * Below minimum failures; even if all calls are failures, failover should NOT trigger.\n   */\n  @Test\n  public void belowMinFailures_doesNotFailover() {\n    // Always failing connections\n    Connection failing = mock(Connection.class);\n    when(failing.executeCommand(org.mockito.Mockito.<CommandObject<?>> any()))\n        .thenThrow(new JedisConnectionException(\"fail\"));\n    doNothing().when(failing).close();\n    when(poolMock.getResource()).thenReturn(failing);\n\n    for (int i = 0; i < 2; i++) {\n      assertThrows(JedisConnectionException.class, () -> executor.executeCommand(dummyCommand));\n    }\n\n    // Below min failures; CB remains CLOSED\n    assertEquals(CircuitBreaker.State.CLOSED, spyProvider.getDatabaseCircuitBreaker().getState());\n  }\n\n  /**\n   * Reaching minFailures and exceeding failure rate threshold should trigger failover.\n   */\n  @Test\n  public void minFailuresAndRateExceeded_triggersFailover() {\n    // Always failing connections\n    Connection failing = mock(Connection.class);\n    when(failing.executeCommand(org.mockito.Mockito.<CommandObject<?>> any()))\n        .thenThrow(new JedisConnectionException(\"fail\"));\n    doNothing().when(failing).close();\n    when(poolMock.getResource()).thenReturn(failing);\n\n    // Reach min failures and exceed rate threshold\n    for (int i = 0; i < 3; i++) {\n      assertThrows(JedisConnectionException.class, () -> executor.executeCommand(dummyCommand));\n    }\n\n    // Next call should hit open CB (CallNotPermitted) and trigger failover\n    assertThrows(JedisConnectionException.class, () -> executor.executeCommand(dummyCommand));\n\n    verify(spyProvider, atLeastOnce()).switchToHealthyDatabase(eq(SwitchReason.CIRCUIT_BREAKER),\n      any());\n    assertEquals(CircuitBreaker.State.FORCED_OPEN,\n      spyProvider.getDatabase(fakeEndpoint).getCircuitBreaker().getState());\n  }\n\n  /**\n   * Even after reaching minFailures, if failure rate is below threshold, do not failover.\n   */\n  @Test\n  public void rateBelowThreshold_doesNotFailover() throws Exception {\n    // Use local provider with higher threshold (80%) and no retries\n    MultiDbConfig.Builder cfgBuilder = MultiDbConfig.builder(fakeDatabaseConfigs)\n        .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().failureRateThreshold(80.0f)\n            .minNumOfFailures(3).slidingWindowSize(10).build())\n        .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(1).build())\n        .retryOnFailover(false);\n    MultiDbConnectionProvider rp = new MultiDbConnectionProvider(cfgBuilder.build());\n    MultiDbConnectionProvider sp = spy(rp);\n    Database c = sp.getDatabase();\n    try (MultiDbCommandExecutor ex = new MultiDbCommandExecutor(sp)) {\n      CommandObject<String> cmd = new CommandObject<>(new CommandArguments(Protocol.Command.PING),\n          BuilderFactory.STRING);\n\n      TrackingConnectionPool pool = mock(TrackingConnectionPool.class);\n      ReflectionTestUtil.setField(c, \"connectionPool\", pool);\n\n      // 3 successes\n      Connection success = mock(Connection.class);\n      when(success.executeCommand(org.mockito.Mockito.<CommandObject<String>> any()))\n          .thenReturn(\"PONG\");\n      doNothing().when(success).close();\n      when(pool.getResource()).thenReturn(success);\n      for (int i = 0; i < 3; i++) {\n        assertEquals(\"PONG\", ex.executeCommand(cmd));\n      }\n\n      // 3 failures -> total 6 calls, 50% failure rate; threshold 80% means stay CLOSED\n      Connection failing = mock(Connection.class);\n      when(failing.executeCommand(org.mockito.Mockito.<CommandObject<?>> any()))\n          .thenThrow(new JedisConnectionException(\"fail\"));\n      doNothing().when(failing).close();\n      when(pool.getResource()).thenReturn(failing);\n      for (int i = 0; i < 3; i++) {\n        assertThrows(JedisConnectionException.class, () -> ex.executeCommand(cmd));\n      }\n\n      assertEquals(CircuitBreaker.State.CLOSED, sp.getDatabaseCircuitBreaker().getState());\n    }\n  }\n\n  @Test\n  public void providerBuilder_zeroRate_mapsToHundredAndHugeMinCalls() {\n    MultiDbConfig.Builder cfgBuilder = MultiDbConfig.builder(fakeDatabaseConfigs);\n    cfgBuilder.failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n        .failureRateThreshold(0.0f).minNumOfFailures(3).slidingWindowSize(10).build());\n    MultiDbConfig mcc = cfgBuilder.build();\n\n    CircuitBreakerThresholdsAdapter adapter = new CircuitBreakerThresholdsAdapter(mcc);\n\n    assertEquals(100.0f, adapter.getFailureRateThreshold(), 0.0001f);\n    assertEquals(Integer.MAX_VALUE, adapter.getMinimumNumberOfCalls());\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n      // minFailures, ratePercent, successes, failures, expectFailoverOnNext\n      \"0, 1.0, 0, 1, true\", //\n      \"1, 1.0, 0, 1, true\", //\n      \"3, 50.0, 0, 3, true\", //\n      \"1, 100.0, 0, 1, true\", //\n      \"0, 100.0, 99, 1, false\", //\n      \"0, 1.0, 99, 1, true\", //\n      // additional edge cases\n      \"1, 0.0, 0, 1, true\", //\n      \"3, 50.0, 3, 2, false\", //\n      \"1000, 1.0, 198, 2, false\", })\n  public void thresholdMatrix(int minFailures, float ratePercent, int successes, int failures,\n      boolean expectFailoverOnNext) throws Exception {\n\n    MultiDbConfig.Builder cfgBuilder = MultiDbConfig.builder(fakeDatabaseConfigs)\n        .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n            .failureRateThreshold(ratePercent).minNumOfFailures(minFailures)\n            .slidingWindowSize(Math.max(10, successes + failures + 2)).build())\n        .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(1).build())\n        .retryOnFailover(false);\n\n    MultiDbConnectionProvider real = new MultiDbConnectionProvider(cfgBuilder.build());\n    MultiDbConnectionProvider spy = spy(real);\n    Database c = spy.getDatabase();\n    try (MultiDbCommandExecutor ex = new MultiDbCommandExecutor(spy)) {\n\n      CommandObject<String> cmd = new CommandObject<>(new CommandArguments(Protocol.Command.PING),\n          BuilderFactory.STRING);\n\n      TrackingConnectionPool pool = mock(TrackingConnectionPool.class);\n      ReflectionTestUtil.setField(c, \"connectionPool\", pool);\n\n      if (successes > 0) {\n        Connection ok = mock(Connection.class);\n        when(ok.executeCommand(org.mockito.Mockito.<CommandObject<String>> any()))\n            .thenReturn(\"PONG\");\n        doNothing().when(ok).close();\n        when(pool.getResource()).thenReturn(ok);\n        for (int i = 0; i < successes; i++) {\n          ex.executeCommand(cmd);\n        }\n      }\n\n      if (failures > 0) {\n        Connection bad = mock(Connection.class);\n        when(bad.executeCommand(org.mockito.Mockito.<CommandObject<?>> any()))\n            .thenThrow(new JedisConnectionException(\"fail\"));\n        doNothing().when(bad).close();\n        when(pool.getResource()).thenReturn(bad);\n        for (int i = 0; i < failures; i++) {\n          try {\n            ex.executeCommand(cmd);\n          } catch (Exception ignore) {\n          }\n        }\n      }\n\n      if (expectFailoverOnNext) {\n        assertThrows(Exception.class, () -> ex.executeCommand(cmd));\n        verify(spy, atLeastOnce()).switchToHealthyDatabase(eq(SwitchReason.CIRCUIT_BREAKER), any());\n        assertEquals(CircuitBreaker.State.FORCED_OPEN, c.getCircuitBreaker().getState());\n      } else {\n        CircuitBreaker.State st = c.getCircuitBreaker().getState();\n        assertTrue(st == CircuitBreaker.State.CLOSED || st == CircuitBreaker.State.HALF_OPEN);\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/MultiDbConnectionProviderDynamicEndpointUnitTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedConstruction;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbClient;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.exceptions.JedisValidationException;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockConstruction;\nimport static org.mockito.Mockito.when;\n\npublic class MultiDbConnectionProviderDynamicEndpointUnitTest {\n\n  private MultiDbConnectionProvider provider;\n  private JedisClientConfig clientConfig;\n  private static EndpointConfig endpoint1;\n  private static EndpointConfig endpoint2;\n\n  private static final float WEIGHT1 = 0.9f;\n  private static final float WEIGHT2 = 0.8f;\n\n  @BeforeAll\n  static void prepareEndpoints() {\n    endpoint1 = Endpoints.getRedisEndpoint(\"standalone0\");\n    endpoint2 = Endpoints.getRedisEndpoint(\"standalone1\");\n  }\n\n  @BeforeEach\n  void setUp() {\n    clientConfig = DefaultJedisClientConfig.builder().build();\n\n    // Create initial provider with endpoint1\n    DatabaseConfig initialConfig = createDatabaseConfig(endpoint1.getHostAndPort(), 1.0f);\n\n    MultiDbConfig multiConfig = new MultiDbConfig.Builder(new DatabaseConfig[] { initialConfig })\n        .build();\n\n    provider = new MultiDbConnectionProvider(multiConfig);\n  }\n\n  // Helper method to create database configurations\n  private DatabaseConfig createDatabaseConfig(HostAndPort hostAndPort, float weight) {\n    // Disable health check for unit tests to avoid real connections\n    return DatabaseConfig.builder(hostAndPort, clientConfig).weight(weight)\n        .healthCheckEnabled(false).build();\n  }\n\n  @Test\n  void testAddNewDatabase() {\n    DatabaseConfig newConfig = createDatabaseConfig(endpoint2.getHostAndPort(), 2.0f);\n\n    // Should not throw exception\n    assertDoesNotThrow(() -> provider.add(newConfig));\n\n    // Verify the database was added by checking it can be retrieved\n    assertNotNull(provider.getDatabase(endpoint2.getHostAndPort()));\n  }\n\n  @Test\n  void testAddDuplicateDatabase() {\n    DatabaseConfig duplicateConfig = createDatabaseConfig(endpoint1.getHostAndPort(), 2.0f);\n\n    // Should throw validation exception for duplicate endpoint\n    assertThrows(JedisValidationException.class, () -> provider.add(duplicateConfig));\n  }\n\n  @Test\n  void testAddNullDatabaseConfig() {\n    // Should throw validation exception for null config\n    assertThrows(JedisValidationException.class, () -> provider.add(null));\n  }\n\n  @Test\n  void testRemoveExistingDatabase() {\n    Connection mockConnection = mock(Connection.class);\n    when(mockConnection.ping()).thenReturn(true);\n\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool(mockConnection)) {\n      // Create initial provider with endpoint1\n      DatabaseConfig dbConfig1 = createDatabaseConfig(endpoint1.getHostAndPort(), 1.0f);\n\n      MultiDbConfig multiConfig = MultiDbConfig.builder(new DatabaseConfig[] { dbConfig1 }).build();\n\n      try (MultiDbConnectionProvider providerWithMockedPool = new MultiDbConnectionProvider(\n          multiConfig)) {\n\n        // Add endpoint2 as second database\n        DatabaseConfig newConfig = createDatabaseConfig(endpoint2.getHostAndPort(), 2.0f);\n        providerWithMockedPool.add(newConfig);\n\n        // Now remove endpoint1 (original database)\n        assertDoesNotThrow(() -> providerWithMockedPool.remove(endpoint1.getHostAndPort()));\n\n        // Verify endpoint1 was removed\n        assertNull(providerWithMockedPool.getDatabase(endpoint1.getHostAndPort()));\n        // Verify endpoint2 still exists\n        assertNotNull(providerWithMockedPool.getDatabase(endpoint2.getHostAndPort()));\n      }\n    }\n  }\n\n  private MockedConstruction<TrackingConnectionPool> mockPool(Connection mockConnection) {\n    return mockConstruction(TrackingConnectionPool.class, (mock, context) -> {\n      when(mock.getResource()).thenReturn(mockConnection);\n      doNothing().when(mock).close();\n    });\n  }\n\n  @Test\n  void testRemoveNonExistentDatabase() {\n    HostAndPort nonExistentEndpoint = new HostAndPort(\"dummy\", 9999);\n\n    // Should throw validation exception for non-existent endpoint\n    assertThrows(JedisValidationException.class, () -> provider.remove(nonExistentEndpoint));\n  }\n\n  @Test\n  void testRemoveLastRemainingDatabase() {\n    // Should throw validation exception when trying to remove the last database\n    assertThrows(JedisValidationException.class, () -> provider.remove(endpoint1.getHostAndPort()));\n  }\n\n  @Test\n  void testRemoveNullEndpoint() {\n    // Should throw validation exception for null endpoint\n    assertThrows(JedisValidationException.class, () -> provider.remove(null));\n  }\n\n  @Test\n  void testAddAndRemoveMultipleDatabases() {\n    // Add endpoint2 as second database\n    DatabaseConfig config2 = createDatabaseConfig(endpoint2.getHostAndPort(), 2.0f);\n\n    // Create a third endpoint for this test\n    HostAndPort endpoint3 = new HostAndPort(\"dummy\", 6381);\n    DatabaseConfig config3 = createDatabaseConfig(endpoint3, 3.0f);\n\n    provider.add(config2);\n    provider.add(config3);\n\n    // Verify all databases exist\n    assertNotNull(provider.getDatabase(endpoint1.getHostAndPort()));\n    assertNotNull(provider.getDatabase(endpoint2.getHostAndPort()));\n    assertNotNull(provider.getDatabase(endpoint3));\n\n    // Remove endpoint2\n    provider.remove(endpoint2.getHostAndPort());\n\n    // Verify correct database was removed\n    assertNull(provider.getDatabase(endpoint2.getHostAndPort()));\n    assertNotNull(provider.getDatabase(endpoint1.getHostAndPort()));\n    assertNotNull(provider.getDatabase(endpoint3));\n  }\n\n  @Test\n  void testActiveDatabaseHandlingOnAdd() {\n    // The initial database should be active\n    assertNotNull(provider.getDatabase());\n\n    // Add endpoint2 with higher weight\n    DatabaseConfig newConfig = createDatabaseConfig(endpoint2.getHostAndPort(), 5.0f);\n    provider.add(newConfig);\n\n    // Active database should still be valid (implementation may or may not switch)\n    assertNotNull(provider.getDatabase());\n  }\n\n  @Test\n  void testActiveDatabaseHandlingOnRemove() {\n    Connection mockConnection = mock(Connection.class);\n    when(mockConnection.ping()).thenReturn(true);\n\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool(mockConnection)) {\n      // Create initial provider with endpoint1\n      DatabaseConfig dbConfig1 = createDatabaseConfig(endpoint1.getHostAndPort(), 1.0f);\n\n      MultiDbConfig multiConfig = MultiDbConfig.builder(new DatabaseConfig[] { dbConfig1 }).build();\n\n      try (MultiDbConnectionProvider providerWithMockedPool = new MultiDbConnectionProvider(\n          multiConfig)) {\n\n        // Add endpoint2 as second database\n        DatabaseConfig newConfig = createDatabaseConfig(endpoint2.getHostAndPort(), 2.0f);\n        providerWithMockedPool.add(newConfig);\n\n        // Get current active database\n        Object initialActiveDb = providerWithMockedPool.getDatabase();\n        assertNotNull(initialActiveDb);\n\n        // Remove endpoint1 (original database, might be active)\n        providerWithMockedPool.remove(endpoint1.getHostAndPort());\n\n        // Should still have an active database\n        assertNotNull(providerWithMockedPool.getDatabase());\n      }\n    }\n  }\n\n  private MultiDbClient getClient() {\n    MultiDbClient client = MultiDbClient.builder()\n        .multiDbConfig(MultiDbConfig.builder()\n            .database(createDatabaseConfig(endpoint1.getHostAndPort(), WEIGHT1))\n            .database(createDatabaseConfig(endpoint2.getHostAndPort(), WEIGHT2)).build())\n        .build();\n    return client;\n  }\n\n  @Test\n  void testGetWeight() {\n    MultiDbClient client = getClient();\n    // Verify we can get the initial weight set during configuration\n    float weight1 = client.getWeight(endpoint1.getHostAndPort());\n    float weight2 = client.getWeight(endpoint2.getHostAndPort());\n\n    assertEquals(WEIGHT1, weight1);\n    assertEquals(WEIGHT2, weight2);\n  }\n\n  @Test\n  void testGetWeightWithNonExistingEndpoint() {\n    MultiDbClient client = getClient();\n\n    Endpoint nonExistingEndpoint = new HostAndPort(\"non-existing-host\", 6379);\n    assertThrows(JedisValidationException.class, () -> client.getWeight(nonExistingEndpoint));\n  }\n\n  @Test\n  void testSetWeightWithNonExistingEndpoint() {\n    MultiDbClient client = getClient();\n\n    Endpoint nonExistingEndpoint = new HostAndPort(\"non-existing-host\", 6379);\n    assertThrows(JedisValidationException.class, () -> client.setWeight(nonExistingEndpoint, 1.0f));\n  }\n\n  @Test\n  void testSetWeight() {\n    MultiDbClient client = getClient();\n\n    Endpoint endpoint = endpoint1.getHostAndPort();\n\n    // Verify initial weight\n    assertEquals(WEIGHT1, client.getWeight(endpoint));\n\n    // Set a new weight\n    client.setWeight(endpoint, 75.0f);\n\n    // Verify the weight has changed\n    assertEquals(75.0f, client.getWeight(endpoint));\n  }\n\n  @Test\n  void testSetWeightToZero() {\n    MultiDbClient client = getClient();\n\n    Endpoint endpoint = endpoint2.getHostAndPort();\n\n    // Set weight to zero\n    assertThrows(IllegalArgumentException.class, () -> client.setWeight(endpoint, 0.0f));\n  }\n\n  @Test\n  void testSetWeightMultipleTimes() {\n    MultiDbClient client = getClient();\n\n    Endpoint endpoint = endpoint1.getHostAndPort();\n\n    // Set weight multiple times\n    client.setWeight(endpoint, 25.0f);\n    assertEquals(25.0f, client.getWeight(endpoint));\n\n    client.setWeight(endpoint, 80.0f);\n    assertEquals(80.0f, client.getWeight(endpoint));\n\n    client.setWeight(endpoint, 1.0f);\n    assertEquals(1.0f, client.getWeight(endpoint));\n  }\n\n  @Test\n  void testSetWeightWithNegativeWeight() {\n    MultiDbClient client = getClient();\n\n    Endpoint endpoint = endpoint1.getHostAndPort();\n    // Verify that setting a negative weight is rejected\n    assertThrows(IllegalArgumentException.class, () -> client.setWeight(endpoint, -1.0f));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/MultiDbConnectionProviderFailoverAttemptsConfigTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport org.awaitility.Durations;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.mcf.JedisFailoverException.JedisPermanentlyNotAvailableException;\nimport redis.clients.jedis.mcf.JedisFailoverException.JedisTemporarilyNotAvailableException;\nimport redis.clients.jedis.util.ReflectionTestUtil;\n\nimport java.time.Duration;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Tests for how getMaxNumFailoverAttempts and getDelayInBetweenFailoverAttempts impact\n * MultiDbConnectionProvider behaviour when no healthy databases are available.\n */\npublic class MultiDbConnectionProviderFailoverAttemptsConfigTest {\n\n  private HostAndPort endpoint0 = new HostAndPort(\"purposefully-incorrect\", 0000);\n  private HostAndPort endpoint1 = new HostAndPort(\"purposefully-incorrect\", 0001);\n\n  private MultiDbConnectionProvider provider;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    JedisClientConfig clientCfg = DefaultJedisClientConfig.builder().build();\n\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[] {\n        DatabaseConfig.builder(endpoint0, clientCfg).weight(1.0f).healthCheckEnabled(false).build(),\n        DatabaseConfig.builder(endpoint1, clientCfg).weight(0.5f).healthCheckEnabled(false)\n            .build() };\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(databaseConfigs);\n\n    // Use small values by default for tests unless overridden per-test via reflection\n    setBuilderFailoverConfig(builder, /* maxAttempts */ 10, /* delayMs */ 12000);\n\n    provider = new MultiDbConnectionProvider(builder.build());\n\n    // Disable both databases to force handleNoHealthyDatabase path\n    provider.getDatabase(endpoint0).setDisabled(true);\n    provider.getDatabase(endpoint1).setDisabled(true);\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (provider != null) provider.close();\n  }\n\n  @Test\n  void delayBetweenFailoverAttempts_gatesCounterIncrementsWithinWindow() throws Exception {\n    // Configure: small max (2) with a large non-zero delay window to ensure rapid calls stay within\n    // window\n    setProviderFailoverConfig(/* maxAttempts */ 2, /* delayMs */ 5000);\n\n    assertEquals(2, getProviderMaxAttempts());\n    assertEquals(5000, getProviderDelayMs());\n    assertEquals(0, getProviderAttemptCount());\n\n    // First call: should throw temporary and start the freeze window, incrementing attempt count to\n    // 1\n    assertThrows(JedisTemporarilyNotAvailableException.class,\n      () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase()));\n    int afterFirst = getProviderAttemptCount();\n    assertEquals(1, afterFirst);\n\n    // Many rapid subsequent calls within the delay window should continue to throw temporary\n    // and should NOT increment the attempt count beyond 1\n    for (int i = 0; i < 50; i++) {\n      assertThrows(JedisTemporarilyNotAvailableException.class,\n        () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase()));\n      assertEquals(1, getProviderAttemptCount());\n    }\n  }\n\n  @Test\n  void delayBetweenFailoverAttempts_permanentExceptionAfterAttemptsExhausted() throws Exception {\n    // Configure: small max (2) with a large non-zero delay window to ensure rapid calls stay within\n    // window\n    setProviderFailoverConfig(/* maxAttempts */ 2, /* delayMs */ 20);\n\n    assertEquals(2, getProviderMaxAttempts());\n    assertEquals(20, getProviderDelayMs());\n    assertEquals(0, getProviderAttemptCount());\n\n    // First call: should throw temporary and start the freeze window, incrementing attempt count to\n    // 1\n    assertThrows(JedisTemporarilyNotAvailableException.class,\n      () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase()));\n    int afterFirst = getProviderAttemptCount();\n    assertEquals(1, afterFirst);\n\n    // Many rapid subsequent calls within the delay window should continue to throw temporary\n    // and should NOT increment the attempt count beyond 1\n    for (int i = 0; i < 50; i++) {\n      assertThrows(JedisTemporarilyNotAvailableException.class,\n        () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase()));\n      assertEquals(1, getProviderAttemptCount());\n    }\n\n    await().atMost(Durations.TWO_HUNDRED_MILLISECONDS).pollInterval(Duration.ofMillis(10))\n        .until(() -> {\n          Exception e = assertThrows(JedisFailoverException.class, () -> provider\n              .switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase()));\n          return e instanceof JedisPermanentlyNotAvailableException;\n        });\n  }\n\n  @Test\n  void maxNumFailoverAttempts_zeroDelay_leadsToPermanentAfterExceeding() throws Exception {\n    // Configure: maxAttempts = 2, delay = 0 so each call increments the counter immediately\n    setProviderFailoverConfig(/* maxAttempts */ 2, /* delayMs */ 0);\n\n    assertEquals(2, getProviderMaxAttempts());\n    assertEquals(0, getProviderDelayMs());\n    assertEquals(0, getProviderAttemptCount());\n\n    // Expect exactly 'maxAttempts' temporary exceptions, then a permanent one\n    assertThrows(JedisTemporarilyNotAvailableException.class,\n      () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase())); // attempt\n    // 1\n    assertThrows(JedisTemporarilyNotAvailableException.class,\n      () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase())); // attempt\n    // 2\n\n    // Next should exceed max and become permanent\n    assertThrows(JedisPermanentlyNotAvailableException.class,\n      () -> provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase())); // attempt\n    // 3\n    // ->\n    // permanent\n  }\n\n  // ======== Test helper methods (reflection) ========\n\n  private static void setBuilderFailoverConfig(MultiDbConfig.Builder builder, int maxAttempts,\n      int delayMs) throws Exception {\n    ReflectionTestUtil.setField(builder, \"maxNumFailoverAttempts\", maxAttempts);\n\n    ReflectionTestUtil.setField(builder, \"delayInBetweenFailoverAttempts\", delayMs);\n  }\n\n  private void setProviderFailoverConfig(int maxAttempts, int delayMs) throws Exception {\n    // Access the underlying MultiDbConfig inside provider and adjust fields for this\n    // test\n    Object cfg = ReflectionTestUtil.getField(provider, \"multiDbConfig\");\n\n    ReflectionTestUtil.setField(cfg, \"maxNumFailoverAttempts\", maxAttempts);\n\n    ReflectionTestUtil.setField(cfg, \"delayInBetweenFailoverAttempts\", delayMs);\n  }\n\n  private int getProviderMaxAttempts() throws Exception {\n    Object cfg = ReflectionTestUtil.getField(provider, \"multiDbConfig\");\n\n    return ReflectionTestUtil.getField(cfg, \"maxNumFailoverAttempts\");\n  }\n\n  private int getProviderDelayMs() throws Exception {\n    Object cfg = ReflectionTestUtil.getField(provider, \"multiDbConfig\");\n\n    return ReflectionTestUtil.getField(cfg, \"delayInBetweenFailoverAttempts\");\n  }\n\n  private int getProviderAttemptCount() throws Exception {\n    AtomicInteger val = ReflectionTestUtil.getField(provider, \"failoverAttemptCount\");\n    return val.get();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/MultiDbConnectionProviderHelper.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport redis.clients.jedis.Endpoint;\n\npublic class MultiDbConnectionProviderHelper {\n\n  public static void onHealthStatusChange(MultiDbConnectionProvider provider, Endpoint endpoint,\n      HealthStatus oldStatus, HealthStatus newStatus) {\n    provider.onHealthStatusChange(new HealthStatusChangeEvent(endpoint, oldStatus, newStatus));\n  }\n\n  public static void periodicFailbackCheck(MultiDbConnectionProvider provider) {\n    provider.periodicFailbackCheck();\n  }\n\n  public static Endpoint switchToHealthyDatabase(MultiDbConnectionProvider provider,\n      SwitchReason reason, MultiDbConnectionProvider.Database iterateFrom) {\n    return provider.switchToHealthyDatabase(reason, iterateFrom);\n  }\n\n  public static MultiDbConnectionProvider.Database waitForInitializationPolicy(\n      MultiDbConnectionProvider provider, StatusTracker statusTracker) {\n    return provider.waitForInitializationPolicy(statusTracker);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/MultiDbConnectionProviderInitializationTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.MockedConstruction;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.exceptions.JedisValidationException;\n\n/**\n * Tests for MultiDbConnectionProvider initialization edge cases\n */\n@ExtendWith(MockitoExtension.class)\npublic class MultiDbConnectionProviderInitializationTest {\n\n  private HostAndPort endpoint1;\n  private HostAndPort endpoint2;\n  private HostAndPort endpoint3;\n  private JedisClientConfig clientConfig;\n\n  @BeforeEach\n  void setUp() {\n    endpoint1 = new HostAndPort(\"fake\", 6379);\n    endpoint2 = new HostAndPort(\"fake\", 6380);\n    endpoint3 = new HostAndPort(\"fake\", 6381);\n    clientConfig = DefaultJedisClientConfig.builder().build();\n  }\n\n  private MockedConstruction<ConnectionPool> mockPool() {\n    Connection mockConnection = mock(Connection.class);\n    lenient().when(mockConnection.ping()).thenReturn(true);\n    return mockConstruction(ConnectionPool.class, (mock, context) -> {\n      when(mock.getResource()).thenReturn(mockConnection);\n      doNothing().when(mock).close();\n    });\n  }\n\n  @Test\n  void testInitializationWithMixedHealthCheckConfiguration() {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockPool()) {\n      // Create databases with mixed health check configuration\n      DatabaseConfig db1 = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n          .healthCheckEnabled(false) // No health\n                                     // check\n          .build();\n\n      DatabaseConfig db2 = DatabaseConfig.builder(endpoint2, clientConfig).weight(2.0f)\n          .healthCheckStrategySupplier(PingStrategy.DEFAULT) // With\n                                                             // health\n                                                             // check\n          .build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db1, db2 })\n          .initializationPolicy(InitializationPolicy.BuiltIn.ONE_AVAILABLE).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Should initialize successfully\n        assertNotNull(provider.getDatabase());\n\n        // Should select db1 (no health check, assumed healthy) or db2 based on weight\n        // Since db2 has higher weight and health checks, it should be selected if healthy\n        assertTrue(provider.getDatabase() == provider.getDatabase(endpoint1)\n            || provider.getDatabase() == provider.getDatabase(endpoint2));\n      }\n    }\n  }\n\n  @Test\n  void testInitializationWithAllHealthChecksDisabled() {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockPool()) {\n      // Create databases with no health checks\n      DatabaseConfig db1 = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n          .healthCheckEnabled(false).build();\n\n      DatabaseConfig db22 = DatabaseConfig.builder(endpoint2, clientConfig).weight(3.0f) // Higher\n                                                                                         // weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db1, db22 }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Should select db22 (highest weight, no health checks)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testInitializationWithSingleDatabase() {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockPool()) {\n      DatabaseConfig db = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Should select the only available db\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testErrorHandlingWithNullConfiguration() {\n    assertThrows(JedisValidationException.class, () -> {\n      new MultiDbConnectionProvider(null);\n    });\n  }\n\n  @Test\n  void testErrorHandlingWithEmptyDatabaseArray() {\n    assertThrows(JedisValidationException.class, () -> {\n      new MultiDbConfig.Builder(new DatabaseConfig[0]).build();\n    });\n  }\n\n  @Test\n  void testErrorHandlingWithNullDatabaseConfig() {\n    assertThrows(IllegalArgumentException.class, () -> {\n      new MultiDbConfig.Builder(new DatabaseConfig[] { null }).build();\n    });\n  }\n\n  @Test\n  void testInitializationWithZeroWeights() {\n    assertThrows(IllegalArgumentException.class, () -> {\n      DatabaseConfig.builder(endpoint1, clientConfig).weight(0.0f);\n    });\n  }\n\n  @Test\n  void testInitializationWithOneAvailablePolicy() {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockPool()) {\n      DatabaseConfig db1 = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n          .healthCheckEnabled(false).build();\n\n      DatabaseConfig db2 = DatabaseConfig.builder(endpoint2, clientConfig).weight(2.0f)\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db1, db2 })\n          .initializationPolicy(InitializationPolicy.BuiltIn.ONE_AVAILABLE).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Should initialize successfully with ONE_AVAILABLE policy\n        assertNotNull(provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testInitializationWithAllAvailablePolicy() {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockPool()) {\n      DatabaseConfig db1 = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n          .healthCheckEnabled(false).build();\n\n      DatabaseConfig db2 = DatabaseConfig.builder(endpoint2, clientConfig).weight(2.0f)\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db1, db2 })\n          .initializationPolicy(InitializationPolicy.BuiltIn.ALL_AVAILABLE).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Should initialize successfully with ALL_AVAILABLE policy when all health checks\n        // are disabled\n        assertNotNull(provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testInitializationWithMajorityAvailablePolicy() {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockPool()) {\n      DatabaseConfig db1 = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n          .healthCheckEnabled(false).build();\n\n      DatabaseConfig db2 = DatabaseConfig.builder(endpoint2, clientConfig).weight(2.0f)\n          .healthCheckEnabled(false).build();\n\n      DatabaseConfig db3 = DatabaseConfig.builder(endpoint3, clientConfig).weight(3.0f)\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db1, db2, db3 })\n          .initializationPolicy(InitializationPolicy.BuiltIn.MAJORITY_AVAILABLE).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Should initialize successfully with MAJORITY_AVAILABLE policy\n        assertNotNull(provider.getDatabase());\n        // Should select db3 (highest weight)\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testInitializationPolicyNullThrowsException() {\n    DatabaseConfig db = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n        .healthCheckEnabled(false).build();\n\n    assertThrows(IllegalArgumentException.class, () -> {\n      new MultiDbConfig.Builder(new DatabaseConfig[] { db }).initializationPolicy(null).build();\n    });\n  }\n\n  @Test\n  void testInitializationPolicyIsConfigured() {\n    DatabaseConfig db = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n        .healthCheckEnabled(false).build();\n\n    MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db })\n        .initializationPolicy(InitializationPolicy.BuiltIn.ALL_AVAILABLE).build();\n\n    assertEquals(InitializationPolicy.BuiltIn.ALL_AVAILABLE, config.getInitializationPolicy());\n  }\n\n  @Test\n  void testInitializationPolicyDefaultValue() {\n    DatabaseConfig db = DatabaseConfig.builder(endpoint1, clientConfig).weight(1.0f)\n        .healthCheckEnabled(false).build();\n\n    MultiDbConfig config = new MultiDbConfig.Builder(new DatabaseConfig[] { db }).build();\n\n    // Default should be MAJORITY_AVAILABLE\n    assertEquals(InitializationPolicy.BuiltIn.MAJORITY_AVAILABLE, config.getInitializationPolicy());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/MultiDbConnectionProviderTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreaker;\nimport org.awaitility.Awaitility;\nimport org.awaitility.Durations;\nimport org.junit.jupiter.api.*;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisValidationException;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider.Database;\nimport redis.clients.jedis.mcf.ProbingPolicy.BuiltIn;\nimport redis.clients.jedis.mcf.JedisFailoverException.JedisPermanentlyNotAvailableException;\nimport redis.clients.jedis.mcf.JedisFailoverException.JedisTemporarilyNotAvailableException;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @see MultiDbConnectionProvider\n */\n@Tag(\"integration\")\npublic class MultiDbConnectionProviderTest {\n\n  private static EndpointConfig endpointStandalone0;\n  private static EndpointConfig endpointStandalone1;\n\n  private MultiDbConnectionProvider provider;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    endpointStandalone0 = Endpoints.getRedisEndpoint(\"standalone0\");\n    endpointStandalone1 = Endpoints.getRedisEndpoint(\"standalone1\");\n  }\n\n  @BeforeEach\n  public void setUp() {\n\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[2];\n    databaseConfigs[0] = DatabaseConfig.builder(endpointStandalone0.getHostAndPort(),\n      endpointStandalone0.getClientConfigBuilder().build()).weight(0.5f).build();\n    databaseConfigs[1] = DatabaseConfig.builder(endpointStandalone1.getHostAndPort(),\n      endpointStandalone1.getClientConfigBuilder().build()).weight(0.3f).build();\n\n    provider = new MultiDbConnectionProvider(new MultiDbConfig.Builder(databaseConfigs).build());\n  }\n\n  @AfterEach\n  public void destroy() {\n    provider.close();\n    provider = null;\n  }\n\n  @Test\n  public void testCircuitBreakerForcedTransitions() {\n\n    CircuitBreaker circuitBreaker = provider.getDatabaseCircuitBreaker();\n    circuitBreaker.getState();\n\n    if (CircuitBreaker.State.FORCED_OPEN.equals(circuitBreaker.getState()))\n      circuitBreaker.transitionToClosedState();\n\n    circuitBreaker.transitionToForcedOpenState();\n    assertEquals(CircuitBreaker.State.FORCED_OPEN, circuitBreaker.getState());\n\n    circuitBreaker.transitionToClosedState();\n    assertEquals(CircuitBreaker.State.CLOSED, circuitBreaker.getState());\n  }\n\n  @Test\n  public void testSwitchToHealthyDatabase() throws InterruptedException {\n    waitForDatabaseToGetHealthy(provider.getDatabase(endpointStandalone0.getHostAndPort()),\n      provider.getDatabase(endpointStandalone1.getHostAndPort()));\n\n    Endpoint e2 = provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK,\n      provider.getDatabase());\n    assertEquals(endpointStandalone1.getHostAndPort(), e2);\n  }\n\n  @Test\n  public void testCanIterateOnceMore() {\n    Endpoint endpoint0 = endpointStandalone0.getHostAndPort();\n    waitForDatabaseToGetHealthy(provider.getDatabase(endpoint0),\n      provider.getDatabase(endpointStandalone1.getHostAndPort()));\n\n    provider.setActiveDatabase(endpoint0);\n    provider.getDatabase().setDisabled(true);\n    provider.switchToHealthyDatabase(SwitchReason.HEALTH_CHECK, provider.getDatabase(endpoint0));\n\n    assertFalse(provider.canIterateFrom(provider.getDatabase()));\n  }\n\n  private void waitForDatabaseToGetHealthy(Database... databases) {\n    Awaitility.await().pollInterval(Durations.ONE_HUNDRED_MILLISECONDS)\n        .atMost(Durations.TWO_SECONDS)\n        .until(() -> Arrays.stream(databases).allMatch(Database::isHealthy));\n  }\n\n  @Test\n  public void testDatabaseSwitchListener() {\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[2];\n    databaseConfigs[0] = DatabaseConfig\n        .builder(new HostAndPort(\"purposefully-incorrect\", 0000),\n          DefaultJedisClientConfig.builder().build())\n        .weight(0.5f).healthCheckEnabled(false).build();\n    databaseConfigs[1] = DatabaseConfig\n        .builder(new HostAndPort(\"purposefully-incorrect\", 0001),\n          DefaultJedisClientConfig.builder().build())\n        .weight(0.4f).healthCheckEnabled(false).build();\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(databaseConfigs);\n\n    // Configures a single failed command to trigger an open circuit on the next subsequent failure\n    builder.failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(3)\n        .minNumOfFailures(1).failureRateThreshold(0).build());\n\n    AtomicBoolean isValidTest = new AtomicBoolean(false);\n\n    MultiDbConnectionProvider localProvider = new MultiDbConnectionProvider(builder.build());\n    localProvider.setDatabaseSwitchListener(a -> {\n      isValidTest.set(true);\n    });\n\n    try (MultiDbClient jedis = MultiDbClient.builder().connectionProvider(localProvider).build()) {\n\n      // This will fail due to unable to connect and open the circuit which will trigger the post\n      // processor\n      try {\n        jedis.get(\"foo\");\n      } catch (Exception e) {\n      }\n\n    }\n\n    assertTrue(isValidTest.get());\n  }\n\n  @Test\n  public void testSetActiveDatabaseNull() {\n    assertThrows(JedisValidationException.class, () -> provider.setActiveDatabase(null));\n  }\n\n  @Test\n  public void testSetActiveDatabaseByMissingEndpoint() {\n    assertThrows(JedisValidationException.class, () -> provider.setActiveDatabase(new Endpoint() {\n      @Override\n      public String getHost() {\n        return \"purposefully-incorrect\";\n      }\n\n      @Override\n      public int getPort() {\n        return 0000;\n      }\n    })); // Should throw an exception\n  }\n\n  @Test\n  public void testConnectionPoolConfigApplied() {\n    ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();\n    poolConfig.setMaxTotal(8);\n    poolConfig.setMaxIdle(4);\n    poolConfig.setMinIdle(1);\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[2];\n\n    databaseConfigs[0] = DatabaseConfig\n        .builder(endpointStandalone0.getHostAndPort(),\n          endpointStandalone0.getClientConfigBuilder().build())\n        .connectionPoolConfig(poolConfig).build();\n    databaseConfigs[1] = DatabaseConfig\n        .builder(endpointStandalone1.getHostAndPort(),\n          endpointStandalone1.getClientConfigBuilder().build())\n        .connectionPoolConfig(poolConfig).build();\n    try (MultiDbConnectionProvider customProvider = new MultiDbConnectionProvider(\n        new MultiDbConfig.Builder(databaseConfigs).build())) {\n      MultiDbConnectionProvider.Database activeDatabase = customProvider.getDatabase();\n      ConnectionPool connectionPool = activeDatabase.getConnectionPool();\n      assertEquals(8, connectionPool.getMaxTotal());\n      assertEquals(4, connectionPool.getMaxIdle());\n      assertEquals(1, connectionPool.getMinIdle());\n    }\n  }\n\n  @Test\n  @Timeout(5)\n  void testHealthChecksStopAfterProviderClose() throws InterruptedException {\n    AtomicInteger healthCheckCount = new AtomicInteger(0);\n\n    // Custom strategy that counts health checks\n    HealthCheckStrategy countingStrategy = new redis.clients.jedis.mcf.TestHealthCheckStrategy(\n        redis.clients.jedis.mcf.HealthCheckStrategy.Config.builder().interval(5).timeout(50)\n            .policy(BuiltIn.ANY_SUCCESS).build(),\n        e -> {\n          healthCheckCount.incrementAndGet();\n          return HealthStatus.HEALTHY;\n        });\n\n    // Create new provider with health check strategy (don't use the setUp() provider)\n    DatabaseConfig config = DatabaseConfig\n        .builder(endpointStandalone0.getHostAndPort(),\n          endpointStandalone0.getClientConfigBuilder().build())\n        .healthCheckStrategy(countingStrategy).build();\n\n    MultiDbConnectionProvider testProvider = new MultiDbConnectionProvider(\n        new MultiDbConfig.Builder(Collections.singletonList(config)).build());\n\n    try {\n      // Wait for some health checks to occur\n      Awaitility.await().atMost(Durations.ONE_SECOND).until(() -> healthCheckCount.get() > 2);\n\n      int checksBeforeClose = healthCheckCount.get();\n\n      // Close provider\n      testProvider.close();\n\n      // Wait longer than health check interval\n      Thread.sleep(100);\n\n      int checksAfterClose = healthCheckCount.get();\n\n      // Health check count should not increase after close\n      assertEquals(checksBeforeClose, checksAfterClose,\n        \"Health checks should stop after provider is closed\");\n\n    } finally {\n      // Ensure cleanup even if test fails\n      testProvider.close();\n    }\n  }\n\n  @Test\n  public void userCommand_firstTemporary_thenPermanent_inOrder() {\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[2];\n    databaseConfigs[0] = DatabaseConfig.builder(endpointStandalone0.getHostAndPort(),\n      endpointStandalone0.getClientConfigBuilder().build()).weight(0.5f).build();\n    databaseConfigs[1] = DatabaseConfig.builder(endpointStandalone1.getHostAndPort(),\n      endpointStandalone1.getClientConfigBuilder().build()).weight(0.3f).build();\n\n    MultiDbConnectionProvider testProvider = new MultiDbConnectionProvider(\n        new MultiDbConfig.Builder(databaseConfigs).delayInBetweenFailoverAttempts(100)\n            .maxNumFailoverAttempts(2)\n            .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(1).build()).build());\n\n    try (MultiDbClient jedis = MultiDbClient.builder().connectionProvider(testProvider).build()) {\n      jedis.get(\"foo\");\n\n      // Disable both databases so any attempt to switch results in 'no healthy database' path\n      testProvider.getDatabase(endpointStandalone0.getHostAndPort()).setDisabled(true);\n      testProvider.getDatabase(endpointStandalone1.getHostAndPort()).setDisabled(true);\n\n      // Simulate user running a command that fails and triggers failover iteration\n      assertThrows(JedisTemporarilyNotAvailableException.class, () -> jedis.get(\"foo\"));\n\n      // Next immediate attempt should exceed max attempts and become permanent (expected to fail\n      // until feature exists)\n      await().atMost(Durations.ONE_SECOND).pollInterval(Durations.ONE_HUNDRED_MILLISECONDS)\n          .until(() -> (assertThrows(JedisFailoverException.class,\n            () -> jedis.get(\"foo\")) instanceof JedisPermanentlyNotAvailableException));\n    }\n  }\n\n  @Test\n  public void userCommand_connectionExceptions_thenMultipleTemporary_thenPermanent_inOrder() {\n    DatabaseConfig[] databaseConfigs = new DatabaseConfig[2];\n    databaseConfigs[0] = DatabaseConfig\n        .builder(endpointStandalone0.getHostAndPort(),\n          endpointStandalone0.getClientConfigBuilder().build())\n        .weight(0.5f).healthCheckEnabled(false).build();\n    databaseConfigs[1] = DatabaseConfig\n        .builder(endpointStandalone1.getHostAndPort(),\n          endpointStandalone1.getClientConfigBuilder().build())\n        .weight(0.3f).healthCheckEnabled(false).build();\n\n    // ATTENTION: these configuration settings are not random and\n    // adjusted to get exact numbers of failures with exact exception types\n    // and open to impact from other defaulted values withing the components in use.\n    MultiDbConnectionProvider testProvider = new MultiDbConnectionProvider(\n        new MultiDbConfig.Builder(databaseConfigs).delayInBetweenFailoverAttempts(100)\n            .maxNumFailoverAttempts(2)\n            .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(1).build())\n            .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(5)\n                .failureRateThreshold(60).build())\n            .build()) {\n    };\n\n    try (MultiDbClient jedis = MultiDbClient.builder().connectionProvider(testProvider).build()) {\n      jedis.get(\"foo\");\n\n      // disable most weighted database so that it will fail on initial requests\n      testProvider.getDatabase(endpointStandalone0.getHostAndPort()).setDisabled(true);\n\n      Exception e = assertThrows(JedisConnectionException.class, () -> jedis.get(\"foo\"));\n      assertEquals(JedisConnectionException.class, e.getClass());\n\n      e = assertThrows(JedisConnectionException.class, () -> jedis.get(\"foo\"));\n      assertEquals(JedisConnectionException.class, e.getClass());\n\n      // then disable the second ones\n      testProvider.getDatabase(endpointStandalone1.getHostAndPort()).setDisabled(true);\n      assertThrows(JedisTemporarilyNotAvailableException.class, () -> jedis.get(\"foo\"));\n      assertThrows(JedisTemporarilyNotAvailableException.class, () -> jedis.get(\"foo\"));\n\n      // Third get request should exceed max attempts and throw\n      // JedisPermanentlyNotAvailableException\n      await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS).pollInterval(Duration.ofMillis(50))\n          .until(() -> (assertThrows(JedisFailoverException.class,\n            () -> jedis.get(\"foo\")) instanceof JedisPermanentlyNotAvailableException));\n\n      // Fourth get request should continue to throw JedisPermanentlyNotAvailableException\n      assertThrows(JedisPermanentlyNotAvailableException.class, () -> jedis.get(\"foo\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/PeriodicFailbackTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\nimport static redis.clients.jedis.mcf.MultiDbConnectionProviderHelper.onHealthStatusChange;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.MockedConstruction;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\n\n@ExtendWith(MockitoExtension.class)\nclass PeriodicFailbackTest {\n\n  private HostAndPort endpoint1;\n  private HostAndPort endpoint2;\n  private JedisClientConfig databaseConfig;\n\n  @BeforeEach\n  void setUp() {\n    endpoint1 = new HostAndPort(\"dummy\", 6379);\n    endpoint2 = new HostAndPort(\"dummy\", 6380);\n    databaseConfig = DefaultJedisClientConfig.builder().build();\n  }\n\n  private MockedConstruction<TrackingConnectionPool> mockPool() {\n    Connection mockConnection = mock(Connection.class);\n    lenient().when(mockConnection.ping()).thenReturn(true);\n    return mockConstruction(TrackingConnectionPool.class, (mock, context) -> {\n      when(mock.getResource()).thenReturn(mockConnection);\n      doNothing().when(mock).close();\n    });\n  }\n\n  @Test\n  void testPeriodicFailbackCheckWithDisabledDatabase() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, databaseConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, databaseConfig).weight(2.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(100).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight: 2.0f vs 1.0f)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Start grace period for database2 manually\n        provider.getDatabase(endpoint2).setGracePeriod();\n        provider.getDatabase(endpoint2).setDisabled(true);\n\n        // Force failover to database1 since database2 is disabled\n        provider.switchToHealthyDatabase(SwitchReason.FORCED, provider.getDatabase(endpoint2));\n\n        // Manually trigger periodic check\n        MultiDbConnectionProviderHelper.periodicFailbackCheck(provider);\n\n        // Should still be on database1 (database2 is in grace period)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testPeriodicFailbackCheckWithHealthyDatabase() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, databaseConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, databaseConfig).weight(2.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(50).gracePeriod(100).build(); // Add\n                                                                   // grace\n                                                                   // period\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight: 2.0f vs 1.0f)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database2 unhealthy to force failover to database1\n        onHealthStatusChange(provider, endpoint2, HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database1 (database2 is in grace period)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Verify database2 is in grace period\n        assertTrue(provider.getDatabase(endpoint2).isInGracePeriod());\n\n        // Make database2 healthy again (but it's still in grace period)\n        onHealthStatusChange(provider, endpoint2, HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Trigger periodic check immediately - should still be on database1\n        MultiDbConnectionProviderHelper.periodicFailbackCheck(provider);\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Wait for grace period to expire\n        Thread.sleep(150);\n\n        // Trigger periodic check after grace period expires\n        MultiDbConnectionProviderHelper.periodicFailbackCheck(provider);\n\n        // Should have failed back to database2 (higher weight, grace period expired)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testPeriodicFailbackCheckWithFailbackDisabled() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, databaseConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, databaseConfig).weight(2.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(false) // Disabled\n              .failbackCheckInterval(50).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight: 2.0f vs 1.0f)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database2 unhealthy to force failover to database1\n        onHealthStatusChange(provider, endpoint2, HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database1\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Make database2 healthy again\n        onHealthStatusChange(provider, endpoint2, HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for stability period\n        Thread.sleep(100);\n\n        // Trigger periodic check\n        MultiDbConnectionProviderHelper.periodicFailbackCheck(provider);\n\n        // Should still be on database1 (failback disabled)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testPeriodicFailbackCheckSelectsHighestWeightDatabase() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      HostAndPort endpoint3 = new HostAndPort(\"dummy\", 6381);\n\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, databaseConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, databaseConfig).weight(2.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database3 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint3, databaseConfig).weight(3.0f) // Highest weight\n          .healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2, database3 })\n              .failbackSupported(true).failbackCheckInterval(50).gracePeriod(100).build(); // Add\n                                                                                           // grace\n                                                                                           // period\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database3 should be active (highest weight: 3.0f vs 2.0f vs 1.0f)\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase());\n\n        // Make database3 unhealthy to force failover to database2 (next highest weight)\n        onHealthStatusChange(provider, endpoint3, HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database2 (weight 2.0f, higher than database1's 1.0f)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database2 unhealthy to force failover to database1\n        onHealthStatusChange(provider, endpoint2, HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database1 (only healthy databases left)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Make database2 and database3 healthy again\n        onHealthStatusChange(provider, endpoint2, HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n        onHealthStatusChange(provider, endpoint3, HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for grace period to expire\n        Thread.sleep(150);\n\n        // Trigger periodic check\n        MultiDbConnectionProviderHelper.periodicFailbackCheck(provider);\n\n        // Should have failed back to database3 (highest weight, grace period expired)\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase());\n      }\n    }\n  }\n\n  @Test\n  void testSettingHigherWeightCausesFailback() throws InterruptedException {\n    try (MockedConstruction<TrackingConnectionPool> mockedPool = mockPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, databaseConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, databaseConfig).weight(2.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).failbackSupported(true)\n              .failbackCheckInterval(50).gracePeriod(100).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Initially, database2 should be active (highest weight: 2.0f vs 1.0f)\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase());\n\n        // Make database2 unhealthy to force failover to database1\n        onHealthStatusChange(provider, endpoint2, HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Should now be on database1 (only healthy option)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n\n        // Increase weight of database1 to be higher than database2\n        provider.getDatabase(endpoint1).setWeight(3.0f);\n\n        // Make database2 healthy again\n        onHealthStatusChange(provider, endpoint2, HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Wait for grace period to expire\n        Thread.sleep(150);\n\n        // Trigger periodic check\n        MultiDbConnectionProviderHelper.periodicFailbackCheck(provider);\n\n        // Should still be on database1 (now has higher weight: 3.0f vs 2.0f)\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/PingStrategyIntegrationTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport eu.rekawek.toxiproxy.Proxy;\nimport eu.rekawek.toxiproxy.ToxiproxyClient;\nimport eu.rekawek.toxiproxy.model.ToxicDirection;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.exceptions.JedisException;\n\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n@Tag(\"failover\")\npublic class PingStrategyIntegrationTest {\n\n  private static EndpointConfig endpoint;\n  private static HostAndPort proxyHostAndPort;\n  private static final ToxiproxyClient tp = new ToxiproxyClient(\"localhost\", 8474);\n  private static Proxy redisProxy;\n\n  @BeforeAll\n  public static void setupProxy() throws IOException {\n    endpoint = Endpoints.getRedisEndpoint(\"redis-failover-1\");\n    proxyHostAndPort = endpoint.getHostAndPort();\n    if (tp.getProxyOrNull(\"redis-health-test\") != null) {\n      tp.getProxy(\"redis-health-test\").delete();\n    }\n    redisProxy = tp.createProxy(\"redis-health-test\", \"0.0.0.0:29379\", \"redis-failover-1:9379\");\n  }\n\n  @AfterAll\n  public static void cleanupProxy() throws IOException {\n    if (redisProxy != null) {\n      redisProxy.delete();\n    }\n  }\n\n  @BeforeEach\n  public void resetProxy() throws IOException {\n    redisProxy.enable();\n    redisProxy.toxics().getAll().forEach(toxic -> {\n      try {\n        toxic.remove();\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      }\n    });\n  }\n\n  @Test\n  public void testPingStrategyRecoversAfterDisconnect() throws Exception {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().socketTimeoutMillis(1000)\n        .connectionTimeoutMillis(1000).build();\n    try (PingStrategy strategy = new PingStrategy(proxyHostAndPort, config,\n        HealthCheckStrategy.Config.create())) {\n\n      // Initial health check should work\n      HealthStatus initialStatus = strategy.doHealthCheck(proxyHostAndPort);\n      assertEquals(HealthStatus.HEALTHY, initialStatus);\n\n      // Disable the proxy to simulate network failure\n      redisProxy.disable();\n\n      // Health check should now fail - this will expose the bug\n      assertThrows(JedisException.class, () -> strategy.doHealthCheck(proxyHostAndPort));\n\n      // Re-enable proxy\n      redisProxy.enable();\n      // Health check should recover\n      HealthStatus statusAfterEnable = strategy.doHealthCheck(proxyHostAndPort);\n      assertEquals(HealthStatus.HEALTHY, statusAfterEnable);\n    }\n\n  }\n\n  @Test\n  public void testPingStrategyWithConnectionTimeout() throws Exception {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().socketTimeoutMillis(100)\n        .connectionTimeoutMillis(100).build();\n\n    try (PingStrategy strategy = new PingStrategy(proxyHostAndPort, config,\n        HealthCheckStrategy.Config.builder().interval(1000).timeout(500).numProbes(1).build())) {\n\n      // Initial health check should work\n      assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(proxyHostAndPort));\n\n      // Add latency toxic to simulate slow network\n      redisProxy.toxics().latency(\"slow-connection\", ToxicDirection.DOWNSTREAM, 1000);\n\n      // Health check should timeout and return unhealthy\n      assertThrows(JedisException.class, () -> strategy.doHealthCheck(proxyHostAndPort));\n\n      // Remove toxic\n      redisProxy.toxics().get(\"slow-connection\").remove();\n\n      // Health check should recover\n      HealthStatus recoveredStatus = strategy.doHealthCheck(proxyHostAndPort);\n      assertEquals(HealthStatus.HEALTHY, recoveredStatus,\n        \"Health check should recover from high latency\");\n    }\n  }\n\n  @Test\n  public void testConnectionDropDuringHealthCheck() throws Exception {\n    JedisClientConfig config = DefaultJedisClientConfig.builder().socketTimeoutMillis(2000).build();\n    try (PingStrategy strategy = new PingStrategy(proxyHostAndPort, config,\n        HealthCheckStrategy.Config.create())) {\n\n      // Initial health check\n      assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(proxyHostAndPort));\n\n      // Simulate connection drop by limiting data transfer\n      redisProxy.toxics().limitData(\"connection-drop\", ToxicDirection.UPSTREAM, 10);\n\n      // This should fail due to connection issues\n      assertThrows(JedisException.class, () -> strategy.doHealthCheck(proxyHostAndPort));\n\n      // Remove toxic\n      redisProxy.toxics().get(\"connection-drop\").remove();\n\n      // Health check should recover\n      HealthStatus afterRecovery = strategy.doHealthCheck(proxyHostAndPort);\n      assertEquals(HealthStatus.HEALTHY, afterRecovery);\n    }\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/RedisRestAPIIT.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\nimport java.security.cert.X509Certificate;\nimport java.util.List;\nimport java.util.function.Supplier;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Tags;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.DefaultRedisCredentials;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisCredentials;\nimport redis.clients.jedis.scenario.RestEndpointUtil;\n\n@Tags({ @Tag(\"failover\"), @Tag(\"scenario\") })\npublic class RedisRestAPIIT {\n  public static class SSLBypass {\n    private static SSLSocketFactory originalSSLSocketFactory;\n    private static HostnameVerifier originalHostnameVerifier;\n\n    public static void disableSSLVerification() {\n      try {\n        // Store original settings BEFORE changing them\n        originalSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();\n        originalHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();\n\n        // Create trust-all manager\n        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {\n          public X509Certificate[] getAcceptedIssuers() {\n            return null;\n          }\n\n          public void checkClientTrusted(X509Certificate[] certs, String authType) {\n          }\n\n          public void checkServerTrusted(X509Certificate[] certs, String authType) {\n          }\n        } };\n\n        // Apply bypass\n        SSLContext sc = SSLContext.getInstance(\"SSL\");\n        sc.init(null, trustAllCerts, new java.security.SecureRandom());\n        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());\n        HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);\n\n      } catch (Exception e) {\n        log.error(\"Failed to disable SSL verification\", e);\n      }\n    }\n\n    public static void restoreSSLVerification() {\n      // Restore original settings\n      if (originalSSLSocketFactory != null) {\n        HttpsURLConnection.setDefaultSSLSocketFactory(originalSSLSocketFactory);\n      }\n      if (originalHostnameVerifier != null) {\n        HttpsURLConnection.setDefaultHostnameVerifier(originalHostnameVerifier);\n      }\n    }\n  }\n\n  private static EndpointConfig crdb;\n  private static EndpointConfig db1;\n\n  private static Endpoint restAPIEndpoint;\n  private static Supplier<RedisCredentials> credentialsSupplier;\n  private static final Logger log = LoggerFactory.getLogger(RedisRestAPIIT.class);\n\n  @BeforeAll\n  public static void beforeClass() {\n    try {\n      crdb = Endpoints.getRedisEndpoint(\"re-active-active\");\n      db1 = Endpoints.getRedisEndpoint(\"re-standalone\");\n      restAPIEndpoint = RestEndpointUtil.getRestAPIEndpoint(crdb);\n      credentialsSupplier = () -> new DefaultRedisCredentials(\"test@redis.com\", \"test123\");\n      SSLBypass.disableSSLVerification();\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false);\n    }\n  }\n\n  @AfterAll\n  public static void teardownTrustStore() {\n    SSLBypass.restoreSSLVerification();\n  }\n\n  @Test\n  void testGetBdbs() throws Exception {\n    RedisRestAPI api = new RedisRestAPI(restAPIEndpoint, credentialsSupplier);\n\n    List<RedisRestAPI.BdbInfo> bdbs = api.getBdbs();\n    assertEquals(4, bdbs.size());\n    assertFalse(bdbs.isEmpty());\n\n    // Verify that each BDB has a UID and endpoints\n    for (RedisRestAPI.BdbInfo bdb : bdbs) {\n      assertNotNull(bdb.getUid());\n      assertNotNull(bdb.getEndpoints());\n    }\n  }\n\n  @Test\n  void testCheckAvailability() throws Exception {\n    RedisRestAPI api = new RedisRestAPI(restAPIEndpoint, credentialsSupplier);\n\n    List<RedisRestAPI.BdbInfo> bdbs = api.getBdbs();\n    // Verify availability against CRDB - without extended lag aware checks\n    assertTrue(api.checkBdbAvailability(String.valueOf(crdb.getBdbId()), false));\n    // Verify availability against CRDB - with lag aware\n    assertTrue(api.checkBdbAvailability(String.valueOf(crdb.getBdbId()), true));\n\n    // Verify availability checks against non-CRDB with lag aware\n    assertTrue(api.checkBdbAvailability(String.valueOf(db1.getBdbId()), false));\n    assertFalse(api.checkBdbAvailability(String.valueOf(db1.getBdbId()), true));\n\n    assertFalse(api.checkBdbAvailability(\"non-existent-bdb\", false));\n    assertFalse(api.checkBdbAvailability(\"non-existent-bdb\", true));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/RedisRestAPIUnitTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.*;\n\nimport java.io.ByteArrayInputStream;\nimport java.net.HttpURLConnection;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Supplier;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultRedisCredentials;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.RedisCredentials;\n\npublic class RedisRestAPIUnitTest {\n\n  static class TestEndpoint implements Endpoint {\n    @Override\n    public String getHost() {\n      return \"dummy\";\n    }\n\n    @Override\n    public int getPort() {\n      return 8443;\n    }\n  }\n\n  @Test\n  void getBdbs_parsesArrayOfObjects() throws Exception {\n    RedisRestAPI api = spy(new RedisRestAPI(new TestEndpoint(), creds(), 1000));\n    HttpURLConnection conn = mock(HttpURLConnection.class);\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n\n    when(conn.getResponseCode()).thenReturn(200);\n    String body = \"[ {\\\"uid\\\":\\\"1\\\", \\\"endpoints\\\":[]}, {\\\"uid\\\":\\\"2\\\", \\\"endpoints\\\":[]} ]\";\n    when(conn.getInputStream()).thenReturn(new ByteArrayInputStream(body.getBytes()));\n\n    List<RedisRestAPI.BdbInfo> result = api.getBdbs();\n    assertEquals(2, result.size());\n    assertEquals(\"1\", result.get(0).getUid());\n    assertEquals(\"2\", result.get(1).getUid());\n    verify(conn, times(1)).disconnect();\n  }\n\n  @Test\n  void availability_logsAndReturnsFalseForNon200() throws Exception {\n    RedisRestAPI api = spy(new RedisRestAPI(new TestEndpoint(), creds(), 1000));\n    HttpURLConnection conn = mock(HttpURLConnection.class);\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n\n    when(conn.getResponseCode()).thenReturn(503);\n    String body = \"{\\\"error_code\\\":\\\"bdb_unavailable\\\",\\\"description\\\":\\\"Database is not available\\\"}\";\n    when(conn.getErrorStream()).thenReturn(new ByteArrayInputStream(body.getBytes()));\n\n    assertFalse(api.checkBdbAvailability(\"2\", false));\n  }\n\n  private static Supplier<RedisCredentials> creds() {\n    return () -> new DefaultRedisCredentials(\"testUser\", \"testPwd\");\n  }\n\n  @Test\n  void availability_200_and_503_paths_cover_lagAware_toggle() throws Exception {\n    RedisRestAPI api = spy(new RedisRestAPI(new TestEndpoint(), creds(), 1000));\n    HttpURLConnection conn = mock(HttpURLConnection.class);\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n\n    // Healthy path (200)\n    when(conn.getResponseCode()).thenReturn(200);\n    assertTrue(api.checkBdbAvailability(\"123\", true));\n\n    // Unhealthy path (503) with error body\n    reset(conn);\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n    when(conn.getResponseCode()).thenReturn(503);\n    when(conn.getErrorStream())\n        .thenReturn(new ByteArrayInputStream(\"{\\\"error_code\\\":\\\"bdb_unavailable\\\"}\".getBytes()));\n    assertFalse(api.checkBdbAvailability(\"123\", false));\n  }\n\n  @Test\n  void testCheckBdbAvailabilityWithExtendedCheck() throws Exception {\n    RedisRestAPI api = spy(\n      new RedisRestAPI(new TestEndpoint(), () -> new DefaultRedisCredentials(\"user\", \"pass\")));\n    HttpURLConnection conn = mock(HttpURLConnection.class);\n\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n    when(conn.getResponseCode()).thenReturn(200);\n    assertTrue(api.checkBdbAvailability(\"123\", true, 100L));\n\n    // Verify the correct URL was constructed with extended check parameters\n    verify(api).createConnection(eq(\n      \"https://dummy:8443/v1/bdbs/123/availability?extend_check=lag&availability_lag_tolerance_ms=100\"),\n      eq(\"GET\"), any());\n  }\n\n  @Test\n  void testCheckBdbAvailabilityWithExtendedCheckNoTolerance() throws Exception {\n    RedisRestAPI api = spy(\n      new RedisRestAPI(new TestEndpoint(), () -> new DefaultRedisCredentials(\"user\", \"pass\")));\n    HttpURLConnection conn = mock(HttpURLConnection.class);\n\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n    when(conn.getResponseCode()).thenReturn(200);\n    assertTrue(api.checkBdbAvailability(\"123\", true, null));\n\n    // Verify the correct URL was constructed with extended check but no tolerance parameter\n    verify(api).createConnection(eq(\"https://dummy:8443/v1/bdbs/123/availability?extend_check=lag\"),\n      eq(\"GET\"), any());\n  }\n\n  @Test\n  void testCheckBdbAvailabilityWithStandardCheck() throws Exception {\n    RedisRestAPI api = spy(\n      new RedisRestAPI(new TestEndpoint(), () -> new DefaultRedisCredentials(\"user\", \"pass\")));\n    HttpURLConnection conn = mock(HttpURLConnection.class);\n\n    doReturn(conn).when(api).createConnection(any(), any(), any());\n    when(conn.getResponseCode()).thenReturn(200);\n    assertTrue(api.checkBdbAvailability(\"123\", false, null));\n\n    // Verify the correct URL was constructed for standard check (no query parameters)\n    verify(api).createConnection(eq(\"https://dummy:8443/v1/bdbs/123/availability\"), eq(\"GET\"),\n      any());\n  }\n\n  // ========== Parsing and BDB Matching Tests ==========\n\n  @Test\n  void parseBdbInfoFromResponse_parses_correctly() {\n    String responseBody = \"[\\n\" + \"    {\\n\" + \"        \\\"uid\\\": \\\"1\\\",\\n\"\n        + \"        \\\"endpoints\\\": [\\n\" + \"            {\\n\"\n        + \"                \\\"dns_name\\\": \\\"redis-db1.example.com\\\",\\n\"\n        + \"                \\\"addr\\\": [\\\"10.0.1.100\\\"],\\n\" + \"                \\\"port\\\": 6379,\\n\"\n        + \"                \\\"uid\\\": \\\"1:1\\\"\\n\" + \"            }\\n\" + \"        ]\\n\" + \"    },\\n\"\n        + \"    {\\n\" + \"        \\\"uid\\\": \\\"2\\\",\\n\" + \"        \\\"endpoints\\\": [\\n\" + \"            {\\n\"\n        + \"                \\\"dns_name\\\": \\\"redis-db2.example.com\\\",\\n\"\n        + \"                \\\"addr\\\": [\\\"10.0.1.101\\\"],\\n\" + \"                \\\"port\\\": 6380,\\n\"\n        + \"                \\\"uid\\\": \\\"2:1\\\"\\n\" + \"            }\\n\" + \"        ]\\n\" + \"    }\\n\"\n        + \"]\";\n\n    List<RedisRestAPI.BdbInfo> result = RedisRestAPI.parseBdbInfoFromResponse(responseBody);\n    assertEquals(2, result.size());\n\n    RedisRestAPI.BdbInfo bdb1 = result.get(0);\n    assertEquals(\"1\", bdb1.getUid());\n    assertEquals(1, bdb1.getEndpoints().size());\n\n    RedisRestAPI.EndpointInfo endpoint1 = bdb1.getEndpoints().get(0);\n    assertEquals(\"redis-db1.example.com\", endpoint1.getDnsName());\n    assertEquals(Arrays.asList(\"10.0.1.100\"), endpoint1.getAddr());\n    assertEquals(Integer.valueOf(6379), endpoint1.getPort());\n    assertEquals(\"1:1\", endpoint1.getUid());\n  }\n\n  @Test\n  void findMatchingBdb_matches_dns_name() {\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"redis-db1.example.com\", 6379, \"1:1\")));\n    RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo(\"2\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.101\"),\n            \"redis-db2.example.com\", 6380, \"2:1\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,\n      \"redis-db2.example.com\");\n\n    assertNotNull(result);\n    assertEquals(\"2\", result.getUid());\n  }\n\n  @Test\n  void findMatchingBdb_matches_ip_address() {\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\", \"192.168.1.100\"),\n            \"redis-db1.example.com\", 6379, \"1:1\")));\n    RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo(\"2\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.101\"),\n            \"redis-db2.example.com\", 6380, \"2:1\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, \"192.168.1.100\");\n\n    assertNotNull(result);\n    assertEquals(\"1\", result.getUid());\n  }\n\n  @Test\n  void findMatchingBdb_returns_null_when_no_match() {\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"redis-db1.example.com\", 6379, \"1:1\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1);\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,\n      \"nonexistent.example.com\");\n\n    assertNull(result);\n  }\n\n  @Test\n  void findMatchingBdb_handles_multiple_endpoints_per_bdb() {\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(\n          new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n              \"redis-db1-primary.example.com\", 6379, \"1:1\"),\n          new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.101\"),\n              \"redis-db1-replica.example.com\", 6380, \"1:2\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1);\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,\n      \"redis-db1-replica.example.com\");\n\n    assertNotNull(result);\n    assertEquals(\"1\", result.getUid());\n  }\n\n  @Test\n  void parseBdbInfoFromResponse_handles_missing_fields_gracefully() {\n    String responseBody = \"[\\n\" + \"    {\\n\" + \"        \\\"uid\\\": \\\"1\\\"\\n\" + \"    },\\n\" + \"    {\\n\"\n        + \"        \\\"endpoints\\\": [\\n\" + \"            {\\n\"\n        + \"                \\\"dns_name\\\": \\\"redis-db2.example.com\\\"\\n\" + \"            }\\n\"\n        + \"        ]\\n\" + \"    },\\n\" + \"    {\\n\" + \"        \\\"uid\\\": \\\"3\\\",\\n\"\n        + \"        \\\"endpoints\\\": [\\n\" + \"            {\\n\"\n        + \"                \\\"dns_name\\\": \\\"redis-db3.example.com\\\",\\n\"\n        + \"                \\\"addr\\\": [\\\"10.0.1.103\\\"],\\n\" + \"                \\\"port\\\": 6379\\n\"\n        + \"            }\\n\" + \"        ]\\n\" + \"    }\\n\" + \"]\";\n\n    List<RedisRestAPI.BdbInfo> result = RedisRestAPI.parseBdbInfoFromResponse(responseBody);\n    assertEquals(2, result.size()); // Only BDBs with uid are included\n\n    RedisRestAPI.BdbInfo bdb1 = result.get(0);\n    assertEquals(\"1\", bdb1.getUid());\n    assertEquals(0, bdb1.getEndpoints().size()); // No endpoints\n\n    RedisRestAPI.BdbInfo bdb3 = result.get(1);\n    assertEquals(\"3\", bdb3.getUid());\n    assertEquals(1, bdb3.getEndpoints().size());\n\n    RedisRestAPI.EndpointInfo endpoint = bdb3.getEndpoints().get(0);\n    assertEquals(\"redis-db3.example.com\", endpoint.getDnsName());\n    assertEquals(Arrays.asList(\"10.0.1.103\"), endpoint.getAddr());\n    assertEquals(Integer.valueOf(6379), endpoint.getPort());\n    assertNull(endpoint.getUid()); // Missing uid field\n  }\n\n  @Test\n  void parseBdbInfoFromResponse_handles_empty_response() {\n    String responseBody = \"[]\";\n\n    List<RedisRestAPI.BdbInfo> result = RedisRestAPI.parseBdbInfoFromResponse(responseBody);\n    assertTrue(result.isEmpty());\n  }\n\n  @Test\n  void findMatchingBdb_prefers_dns_name_over_addr() {\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"target-host.example.com\", 6379, \"1:1\")));\n    RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo(\"2\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"target-host.example.com\"),\n            \"other-host.example.com\", 6380, \"2:1\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);\n\n    // Should match BDB 1 by dns_name, not BDB 2 by addr\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,\n      \"target-host.example.com\");\n    assertNotNull(result);\n    assertEquals(\"1\", result.getUid());\n  }\n\n  @Test\n  void findMatchingBdb_matches_correct_bdb_with_same_host_different_ports() {\n    // Two BDBs with same DNS name but different ports\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"redis.example.com\", 6379, \"1:1\")));\n    RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo(\"2\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"redis.example.com\", 6380, \"2:1\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);\n\n    // Should match first BDB found with the DNS name (current implementation matches by host only)\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, \"redis.example.com\");\n    assertNotNull(result);\n    assertEquals(\"1\", result.getUid()); // First match wins\n  }\n\n  @Test\n  void findMatchingBdb_matches_correct_bdb_with_same_ip_different_ports() {\n    // Two BDBs with same IP but different ports and DNS names\n    RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo(\"1\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"redis1.example.com\", 6379, \"1:1\")));\n    RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo(\"2\",\n        Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList(\"10.0.1.100\"),\n            \"redis2.example.com\", 6380, \"2:1\")));\n\n    List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);\n\n    // Should match first BDB found with the IP address (current implementation matches by host\n    // only)\n    RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, \"10.0.1.100\");\n    assertNotNull(result);\n    assertEquals(\"1\", result.getUid()); // First match wins\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/StatusTrackerTest.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\nimport static org.awaitility.Awaitility.await;\n\nimport java.time.Duration;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport org.awaitility.Durations;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport redis.clients.jedis.HostAndPort;\n\npublic class StatusTrackerTest {\n\n  @Mock\n  private HealthStatusManager mockHealthStatusManager;\n\n  private StatusTracker statusTracker;\n  private HostAndPort testEndpoint;\n\n  @BeforeEach\n  void setUp() {\n    MockitoAnnotations.openMocks(this);\n    statusTracker = new StatusTracker(mockHealthStatusManager);\n    testEndpoint = new HostAndPort(\"dummy\", 6379);\n  }\n\n  @Test\n  void testWaitForHealthStatus_AlreadyDetermined() {\n    // Given: Health status is already HEALTHY\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.HEALTHY);\n\n    // When: Waiting for health status\n    HealthStatus result = statusTracker.waitForHealthStatus(testEndpoint);\n\n    // Then: Should return immediately without waiting\n    assertEquals(HealthStatus.HEALTHY, result);\n    verify(mockHealthStatusManager, never()).registerListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n  }\n\n  @Test\n  void testWaitForHealthStatus_EventDriven() throws InterruptedException {\n    // Given: Health status is initially UNKNOWN\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.UNKNOWN) // First\n                                                                                                 // call\n        .thenReturn(HealthStatus.UNKNOWN); // Second call after registering listener\n    when(mockHealthStatusManager.getMaxWaitFor(testEndpoint)).thenReturn(3000L);\n\n    // Capture the registered listener\n    final HealthStatusListener[] capturedListener = new HealthStatusListener[1];\n    doAnswer(invocation -> {\n      capturedListener[0] = invocation.getArgument(1);\n      return null;\n    }).when(mockHealthStatusManager).registerListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n\n    // When: Start waiting in a separate thread\n    CountDownLatch testLatch = new CountDownLatch(1);\n    final HealthStatus[] result = new HealthStatus[1];\n\n    Thread waitingThread = new Thread(() -> {\n      result[0] = statusTracker.waitForHealthStatus(testEndpoint);\n      testLatch.countDown();\n    });\n    waitingThread.start();\n\n    await().atMost(Duration.ofMillis(100L)).pollInterval(Duration.ofMillis(5L)).untilAsserted(\n      () -> assertNotNull(capturedListener[0], \"Listener should have been registered\"));\n\n    HealthStatusChangeEvent event = new HealthStatusChangeEvent(testEndpoint, HealthStatus.UNKNOWN,\n        HealthStatus.HEALTHY);\n    capturedListener[0].onStatusChange(event);\n\n    // Then: Should complete and return the new status\n    assertTrue(testLatch.await(1, TimeUnit.SECONDS), \"Should complete within timeout\");\n    assertEquals(HealthStatus.HEALTHY, result[0]);\n\n    // Verify cleanup\n    verify(mockHealthStatusManager).unregisterListener(eq(testEndpoint), eq(capturedListener[0]));\n  }\n\n  @Test\n  void testWaitForHealthStatus_IgnoresUnknownStatus() throws InterruptedException {\n    // Given: Health status is initially UNKNOWN\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.UNKNOWN);\n    when(mockHealthStatusManager.getMaxWaitFor(testEndpoint)).thenReturn(3000L);\n\n    // Capture the registered listener\n    final HealthStatusListener[] capturedListener = new HealthStatusListener[1];\n    doAnswer(invocation -> {\n      capturedListener[0] = invocation.getArgument(1);\n      return null;\n    }).when(mockHealthStatusManager).registerListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n\n    // When: Start waiting in a separate thread\n    CountDownLatch testLatch = new CountDownLatch(1);\n    final HealthStatus[] result = new HealthStatus[1];\n\n    Thread waitingThread = new Thread(() -> {\n      result[0] = statusTracker.waitForHealthStatus(testEndpoint);\n      testLatch.countDown();\n    });\n    waitingThread.start();\n\n    // Give some time for the listener to be registered\n    Thread.sleep(50);\n\n    // Simulate UNKNOWN status change (should be ignored)\n    assertNotNull(capturedListener[0], \"Listener should have been registered\");\n    HealthStatusChangeEvent unknownEvent = new HealthStatusChangeEvent(testEndpoint,\n        HealthStatus.UNKNOWN, HealthStatus.UNKNOWN);\n    capturedListener[0].onStatusChange(unknownEvent);\n\n    // Should not complete yet\n    assertFalse(testLatch.await(100, TimeUnit.MILLISECONDS),\n      \"Should not complete with UNKNOWN status\");\n\n    // Now send a real status change\n    HealthStatusChangeEvent realEvent = new HealthStatusChangeEvent(testEndpoint,\n        HealthStatus.UNKNOWN, HealthStatus.UNHEALTHY);\n    capturedListener[0].onStatusChange(realEvent);\n\n    // Then: Should complete now\n    assertTrue(testLatch.await(1, TimeUnit.SECONDS), \"Should complete with real status\");\n    assertEquals(HealthStatus.UNHEALTHY, result[0]);\n  }\n\n  @Test\n  void testWaitForHealthStatus_IgnoresOtherEndpoints() throws InterruptedException {\n    // Given: Health status is initially UNKNOWN\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.UNKNOWN);\n    when(mockHealthStatusManager.getMaxWaitFor(testEndpoint)).thenReturn(3000L);\n    HostAndPort otherEndpoint = new HostAndPort(\"other\", 6379);\n\n    // Capture the registered listener\n    final HealthStatusListener[] capturedListener = new HealthStatusListener[1];\n    doAnswer(invocation -> {\n      capturedListener[0] = invocation.getArgument(1);\n      return null;\n    }).when(mockHealthStatusManager).registerListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n\n    // When: Start waiting in a separate thread\n    CountDownLatch testLatch = new CountDownLatch(1);\n    final HealthStatus[] result = new HealthStatus[1];\n\n    Thread waitingThread = new Thread(() -> {\n      result[0] = statusTracker.waitForHealthStatus(testEndpoint);\n      testLatch.countDown();\n    });\n    waitingThread.start();\n\n    // Give some time for the listener to be registered\n    await().atMost(Durations.FIVE_HUNDRED_MILLISECONDS)\n        .pollInterval(Durations.ONE_HUNDRED_MILLISECONDS).untilAsserted(() -> {\n          assertNotNull(capturedListener[0], \"Listener should have been registered\");\n        });\n\n    // Simulate status change for different endpoint (should be ignored)\n    HealthStatusChangeEvent otherEvent = new HealthStatusChangeEvent(otherEndpoint,\n        HealthStatus.UNKNOWN, HealthStatus.HEALTHY);\n    capturedListener[0].onStatusChange(otherEvent);\n\n    // Should not complete yet\n    assertFalse(testLatch.await(100, TimeUnit.MILLISECONDS),\n      \"Should not complete with other endpoint\");\n\n    // Now send event for correct endpoint\n    HealthStatusChangeEvent correctEvent = new HealthStatusChangeEvent(testEndpoint,\n        HealthStatus.UNKNOWN, HealthStatus.HEALTHY);\n    capturedListener[0].onStatusChange(correctEvent);\n\n    // Then: Should complete now\n    assertTrue(testLatch.await(1, TimeUnit.SECONDS), \"Should complete with correct endpoint\");\n    assertEquals(HealthStatus.HEALTHY, result[0]);\n  }\n\n  @Test\n  void testWaitForHealthStatus_InterruptHandling() {\n    // Given: Health status is initially UNKNOWN and will stay that way\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.UNKNOWN);\n    when(mockHealthStatusManager.getMaxWaitFor(any())).thenReturn(3000L);\n\n    AtomicReference<String> interruptedThreadName = new AtomicReference<>();\n    AtomicReference<Throwable> thrownException = new AtomicReference<>();\n    AtomicReference<Boolean> isInterrupted = new AtomicReference<>();\n    // When: Interrupt thse waiting thread\n    Thread testThread = new Thread(() -> {\n      try {\n        statusTracker.waitForHealthStatus(testEndpoint);\n        fail(\"Should have thrown JedisConnectionException due to interrupt\");\n      } catch (Exception e) {\n        interruptedThreadName.set(Thread.currentThread().getName());\n        thrownException.set(e);\n        isInterrupted.set(Thread.currentThread().isInterrupted());\n      }\n    });\n\n    testThread.start();\n\n    // Give thread time to start waiting\n    try {\n      Thread.sleep(50);\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n    }\n\n    // Interrupt the waiting thread\n    testThread.interrupt();\n\n    try {\n      testThread.join(1000);\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n    }\n\n    assertFalse(testThread.isAlive(), \"Test thread should have completed\");\n    assertTrue(thrownException.get().getMessage().contains(\"Interrupted while waiting\"));\n    assertTrue(isInterrupted.get(), \"Thread should be interrupted\");\n  }\n\n  @Test\n  void testWaitForHealthStatus_RaceConditionProtection() {\n    // Given: Health status changes between first check and listener registration\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.UNKNOWN) // First\n                                                                                                 // call\n        .thenReturn(HealthStatus.HEALTHY); // Second call after registering listener\n\n    // When: Waiting for health status\n    HealthStatus result = statusTracker.waitForHealthStatus(testEndpoint);\n\n    // Then: Should return the status from the second check without waiting\n    assertEquals(HealthStatus.HEALTHY, result);\n\n    // Verify listener was registered and unregistered\n    verify(mockHealthStatusManager).registerListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n    verify(mockHealthStatusManager).unregisterListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n  }\n\n  @Test\n  void testWaitForHealthStatus_ListenerCleanupOnException() {\n    // Given: Health status is initially UNKNOWN\n    when(mockHealthStatusManager.getHealthStatus(testEndpoint)).thenReturn(HealthStatus.UNKNOWN);\n\n    // Mock registerListener to throw an exception\n    doThrow(new RuntimeException(\"Registration failed\")).when(mockHealthStatusManager)\n        .registerListener(eq(testEndpoint), any(HealthStatusListener.class));\n\n    // When: Waiting for health status\n    assertThrows(RuntimeException.class, () -> {\n      statusTracker.waitForHealthStatus(testEndpoint);\n    });\n\n    // Then: Should still attempt to unregister (cleanup in finally block)\n    verify(mockHealthStatusManager).registerListener(eq(testEndpoint),\n      any(HealthStatusListener.class));\n    // Note: unregisterListener might not be called if registerListener fails,\n    // but the finally block should handle this gracefully\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mcf/TestHealthCheckStrategy.java",
    "content": "package redis.clients.jedis.mcf;\n\nimport java.util.function.Function;\n\nimport redis.clients.jedis.Endpoint;\n\npublic class TestHealthCheckStrategy implements HealthCheckStrategy {\n\n  private int interval;\n  private int timeout;\n  private int probes;\n  private int delay;\n  private Function<Endpoint, HealthStatus> healthCheck;\n  private ProbingPolicy policy;\n\n  public TestHealthCheckStrategy(int interval, int timeout, int probes, ProbingPolicy policy,\n      int delay, Function<Endpoint, HealthStatus> healthCheck) {\n    this.interval = interval;\n    this.timeout = timeout;\n    this.probes = probes;\n    this.delay = delay;\n    this.healthCheck = healthCheck;\n    this.policy = policy;\n  }\n\n  public TestHealthCheckStrategy(HealthCheckStrategy.Config config,\n      Function<Endpoint, HealthStatus> healthCheck) {\n    this(config.getInterval(), config.getTimeout(), config.getNumProbes(), config.getPolicy(),\n        config.getDelayInBetweenProbes(), healthCheck);\n  }\n\n  public TestHealthCheckStrategy(Function<Endpoint, HealthStatus> healthCheck) {\n    this(HealthCheckStrategy.Config.create(), healthCheck);\n  }\n\n  @Override\n  public int getInterval() {\n    return interval;\n  }\n\n  @Override\n  public int getTimeout() {\n    return timeout;\n  }\n\n  @Override\n  public int getNumProbes() {\n    return probes;\n  }\n\n  @Override\n  public ProbingPolicy getPolicy() {\n    return policy;\n  }\n\n  @Override\n  public int getDelayInBetweenProbes() {\n    return delay;\n  }\n\n  @Override\n  public HealthStatus doHealthCheck(Endpoint endpoint) {\n    return healthCheck.apply(endpoint);\n  }\n\n};"
  },
  {
    "path": "src/test/java/redis/clients/jedis/misc/AutomaticFailoverTest.java",
    "content": "package redis.clients.jedis.misc;\n\nimport java.net.UnknownHostException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.exceptions.JedisAccessControlException;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.mcf.DatabaseSwitchEvent;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.mcf.MultiDbConnectionProviderHelper;\nimport redis.clients.jedis.mcf.SwitchReason;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.IOUtils;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@Tag(\"failover\")\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class AutomaticFailoverTest {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  private static final Logger log = LoggerFactory.getLogger(AutomaticFailoverTest.class);\n\n  private static HostAndPort hostPortWithFailure;\n  private static EndpointConfig endpointForAuthFailure;\n  private static EndpointConfig workingEndpoint;\n\n  private final JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().build();\n\n  private Jedis jedis2;\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    endpointForAuthFailure = Endpoints.getRedisEndpoint(\"standalone0\");\n    workingEndpoint = Endpoints.getRedisEndpoint(\"standalone7-with-lfu-policy\");\n    hostPortWithFailure = new HostAndPort(endpointForAuthFailure.getHost(), 6378);\n  }\n\n  private List<MultiDbConfig.DatabaseConfig> getDatabaseConfigs(\n      JedisClientConfig clientConfig, HostAndPort... hostPorts) {\n    return Arrays.stream(hostPorts)\n        .map(hp -> DatabaseConfig.builder(hp, clientConfig).healthCheckEnabled(false).build())\n        .collect(Collectors.toList());\n  }\n\n  @BeforeEach\n  public void setUp() {\n    jedis2 = new Jedis(workingEndpoint.getHostAndPort(),\n        workingEndpoint.getClientConfigBuilder().build());\n    jedis2.flushAll();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    IOUtils.closeQuietly(jedis2);\n  }\n\n  @Test\n  public void pipelineWithSwitch() {\n    MultiDbConnectionProvider provider = new MultiDbConnectionProvider(\n        new MultiDbConfig.Builder(\n            getDatabaseConfigs(clientConfig, hostPortWithFailure, workingEndpoint.getHostAndPort()))\n                .build());\n\n    try (MultiDbClient client = MultiDbClient.builder().connectionProvider(provider).build()) {\n      AbstractPipeline pipe = client.pipelined();\n      pipe.set(\"pstr\", \"foobar\");\n      pipe.hset(\"phash\", \"foo\", \"bar\");\n      MultiDbConnectionProviderHelper.switchToHealthyDatabase(provider,\n        SwitchReason.HEALTH_CHECK, provider.getDatabase());\n      pipe.sync();\n    }\n\n    assertEquals(\"foobar\", jedis2.get(\"pstr\"));\n    assertEquals(\"bar\", jedis2.hget(\"phash\", \"foo\"));\n  }\n\n  @Test\n  public void transactionWithSwitch() {\n    MultiDbConnectionProvider provider = new MultiDbConnectionProvider(\n        new MultiDbConfig.Builder(\n            getDatabaseConfigs(clientConfig, hostPortWithFailure, workingEndpoint.getHostAndPort()))\n                .build());\n\n    try (MultiDbClient client = MultiDbClient.builder().connectionProvider(provider).build()) {\n      AbstractTransaction tx = client.multi();\n      tx.set(\"tstr\", \"foobar\");\n      tx.hset(\"thash\", \"foo\", \"bar\");\n      MultiDbConnectionProviderHelper.switchToHealthyDatabase(provider,\n        SwitchReason.HEALTH_CHECK, provider.getDatabase());\n      assertEquals(Arrays.asList(\"OK\", 1L), tx.exec());\n    }\n\n    assertEquals(\"foobar\", jedis2.get(\"tstr\"));\n    assertEquals(\"bar\", jedis2.hget(\"thash\", \"foo\"));\n  }\n\n  @Test\n  public void commandFailoverUnresolvableHost() {\n    int slidingWindowMinFails = 2;\n    int slidingWindowSize = 2;\n\n    HostAndPort unresolvableHostAndPort = new HostAndPort(\"unresolvable\", 6379);\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(\n        getDatabaseConfigs(clientConfig, unresolvableHostAndPort, workingEndpoint.getHostAndPort()))\n            .commandRetry(MultiDbConfig.RetryConfig.builder().waitDuration(1).maxAttempts(1).build())\n            .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n                .slidingWindowSize(slidingWindowSize)\n                .minNumOfFailures(slidingWindowMinFails)\n                .build());\n\n    RedisFailoverReporter failoverReporter = new RedisFailoverReporter();\n    MultiDbConnectionProvider connectionProvider = new MultiDbConnectionProvider(\n        builder.build());\n    connectionProvider.setDatabaseSwitchListener(failoverReporter);\n\n    MultiDbClient jedis = MultiDbClient.builder().connectionProvider(connectionProvider).build();\n\n    String key = \"hash-\" + System.nanoTime();\n    log.info(\"Starting calls to Redis\");\n    assertFalse(failoverReporter.failedOver);\n\n    for (int attempt = 0; attempt < slidingWindowMinFails; attempt++) {\n      assertFalse(failoverReporter.failedOver);\n      Throwable thrown = assertThrows(JedisConnectionException.class,\n        () -> jedis.hset(key, \"f1\", \"v1\"));\n      assertThat(thrown.getCause(), instanceOf(UnknownHostException.class));\n    }\n\n    // already failed over now\n    assertTrue(failoverReporter.failedOver);\n    jedis.hset(key, \"f1\", \"v1\");\n\n    assertEquals(Collections.singletonMap(\"f1\", \"v1\"), jedis.hgetAll(key));\n    jedis.flushAll();\n\n    jedis.close();\n  }\n\n  @Test\n  public void commandFailover() {\n    int slidingWindowMinFails = 6;\n    int slidingWindowSize = 6;\n    int retryMaxAttempts = 3;\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(\n        getDatabaseConfigs(clientConfig, hostPortWithFailure, workingEndpoint.getHostAndPort()))\n            .commandRetry(MultiDbConfig.RetryConfig.builder().maxAttempts(retryMaxAttempts).build())\n            .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n                .failureRateThreshold(50)\n                .minNumOfFailures(slidingWindowMinFails)\n                .slidingWindowSize(slidingWindowSize)\n                .build());\n\n    RedisFailoverReporter failoverReporter = new RedisFailoverReporter();\n    MultiDbConnectionProvider connectionProvider = new MultiDbConnectionProvider(\n        builder.build());\n    connectionProvider.setDatabaseSwitchListener(failoverReporter);\n\n    MultiDbClient jedis = MultiDbClient.builder().connectionProvider(connectionProvider).build();\n\n    String key = \"hash-\" + System.nanoTime();\n    log.info(\"Starting calls to Redis\");\n    assertFalse(failoverReporter.failedOver);\n    // First call fails - will be retried 3 times\n    // this will increase the CircuitBreaker failure count to 3\n    assertThrows(JedisConnectionException.class, () -> jedis.hset(key, \"c1\", \"v1\"));\n\n    // Second call fails - will be retried 3 times\n    // this will increase the CircuitBreaker failure count to 6\n    // should failover now\n    assertThrows(JedisConnectionException.class, () -> jedis.hset(key, \"c2\", \"v1\"));\n\n    // CB is in OPEN state now, next call should cause failover\n    assertEquals(1L, jedis.hset(key, \"c3\", \"v1\"));\n    assertTrue(failoverReporter.failedOver);\n\n    assertEquals(Collections.singletonMap(\"c3\", \"v1\"), jedis.hgetAll(key));\n    jedis.flushAll();\n\n    jedis.close();\n  }\n\n  @Test\n  public void pipelineFailover() {\n    int slidingWindowSize = 10;\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(\n        getDatabaseConfigs(clientConfig, hostPortWithFailure, workingEndpoint.getHostAndPort()))\n            .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n                .slidingWindowSize(slidingWindowSize)\n                .build())\n            .fallbackExceptionList(Collections.singletonList(JedisConnectionException.class));\n\n    RedisFailoverReporter failoverReporter = new RedisFailoverReporter();\n    MultiDbConnectionProvider cacheProvider = new MultiDbConnectionProvider(\n        builder.build());\n    cacheProvider.setDatabaseSwitchListener(failoverReporter);\n\n    MultiDbClient jedis = MultiDbClient.builder().connectionProvider(cacheProvider).build();\n\n    String key = \"hash-\" + System.nanoTime();\n    log.info(\"Starting calls to Redis\");\n    assertFalse(failoverReporter.failedOver);\n    AbstractPipeline pipe = jedis.pipelined();\n    assertFalse(failoverReporter.failedOver);\n    pipe.hset(key, \"f1\", \"v1\");\n    assertFalse(failoverReporter.failedOver);\n    pipe.sync();\n    assertTrue(failoverReporter.failedOver);\n\n    assertEquals(Collections.singletonMap(\"f1\", \"v1\"), jedis.hgetAll(key));\n    jedis.flushAll();\n\n    jedis.close();\n  }\n\n  @Test\n  public void failoverFromAuthError() {\n    int slidingWindowSize = 10;\n\n    MultiDbConfig.Builder builder = new MultiDbConfig.Builder(\n        getDatabaseConfigs(clientConfig, endpointForAuthFailure.getHostAndPort(),\n          workingEndpoint.getHostAndPort()))\n              .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()\n                  .slidingWindowSize(slidingWindowSize)\n                  .build())\n              .fallbackExceptionList(Collections.singletonList(JedisAccessControlException.class));\n\n    RedisFailoverReporter failoverReporter = new RedisFailoverReporter();\n    MultiDbConnectionProvider cacheProvider = new MultiDbConnectionProvider(\n        builder.build());\n    cacheProvider.setDatabaseSwitchListener(failoverReporter);\n\n    MultiDbClient jedis = MultiDbClient.builder().connectionProvider(cacheProvider).build();\n\n    String key = \"hash-\" + System.nanoTime();\n    log.info(\"Starting calls to Redis\");\n    assertFalse(failoverReporter.failedOver);\n    jedis.hset(key, \"f1\", \"v1\");\n    assertTrue(failoverReporter.failedOver);\n\n    assertEquals(Collections.singletonMap(\"f1\", \"v1\"), jedis.hgetAll(key));\n    jedis.flushAll();\n\n    jedis.close();\n  }\n\n  static class RedisFailoverReporter implements Consumer<DatabaseSwitchEvent> {\n\n    boolean failedOver = false;\n\n    @Override\n    public void accept(DatabaseSwitchEvent e) {\n      log.info(\"Jedis fail over to cluster: \" + e.getDatabaseName());\n      failedOver = true;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/misc/ClientSetInfoConfigTest.java",
    "content": "package redis.clients.jedis.misc;\n\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.ClientSetInfoConfig;\nimport redis.clients.jedis.DriverInfo;\nimport redis.clients.jedis.exceptions.JedisValidationException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class ClientSetInfoConfigTest {\n\n  @Test\n  public void defaultConfig() {\n    ClientSetInfoConfig config = ClientSetInfoConfig.DEFAULT;\n    assertFalse(config.isDisabled());\n    assertEquals(\"\", config.getUpstreamDrivers());\n    assertNotNull(config.getDriverInfo());\n    assertEquals(\"jedis\", config.getDriverInfo().getName());\n    assertEquals(\"jedis\", config.getDriverInfo().getFormattedName());\n  }\n\n  @Test\n  public void disabledConfig() {\n    ClientSetInfoConfig config = ClientSetInfoConfig.DISABLED;\n    assertTrue(config.isDisabled());\n    assertEquals(\"\", config.getUpstreamDrivers());\n  }\n\n  @Test\n  public void constructorWithNullDriverInfoThrows() {\n    assertThrows(JedisValidationException.class, () -> new ClientSetInfoConfig((DriverInfo) null));\n  }\n\n  @Test\n  public void withLibNameSuffixFormatsCorrectly() {\n    ClientSetInfoConfig config = ClientSetInfoConfig.withLibNameSuffix(\"my-suffix\");\n    assertEquals(\"my-suffix\", config.getUpstreamDrivers());\n    assertEquals(\"jedis(my-suffix)\", config.getDriverInfo().getFormattedName());\n  }\n\n  @Test\n  public void withLibNameSuffixThenAddUpstreamDriverPrepends() {\n    // Start with legacy suffix\n    ClientSetInfoConfig config = ClientSetInfoConfig.withLibNameSuffix(\"my-suffix\");\n    assertEquals(\"jedis(my-suffix)\", config.getDriverInfo().getFormattedName());\n\n    // Add upstream driver - should prepend to the suffix\n    DriverInfo driverInfo = DriverInfo.builder(config.getDriverInfo())\n        .addUpstreamDriver(\"spring-data-redis\", \"3.2.0\").build();\n    config = new ClientSetInfoConfig(driverInfo);\n    assertEquals(\"spring-data-redis_v3.2.0;my-suffix\", config.getUpstreamDrivers());\n    assertEquals(\"jedis(spring-data-redis_v3.2.0;my-suffix)\",\n      config.getDriverInfo().getFormattedName());\n  }\n\n  @Test\n  public void defaultNameWithUpstreamDriversKeepsJedisName() {\n    // When using default name, adding upstream drivers should keep \"jedis\" as the base name\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"3.2.0\")\n        .build();\n    assertEquals(\"jedis\", driverInfo.getName());\n    assertEquals(\"jedis(spring-data-redis_v3.2.0)\", driverInfo.getFormattedName());\n  }\n\n  @Test\n  public void chainingDriverInfoFromExistingConfig() {\n    // First library (e.g., spring-data-redis) creates its config\n    DriverInfo firstDriverInfo = DriverInfo.builder()\n        .addUpstreamDriver(\"spring-data-redis\", \"3.2.0\").build();\n    ClientSetInfoConfig firstConfig = new ClientSetInfoConfig(firstDriverInfo);\n    assertEquals(\"jedis(spring-data-redis_v3.2.0)\", firstConfig.getDriverInfo().getFormattedName());\n\n    // Second library builds on top of the first config\n    DriverInfo secondDriverInfo = DriverInfo.builder(firstConfig.getDriverInfo())\n        .addUpstreamDriver(\"upstream-library\", \"1.0.0\").build();\n    ClientSetInfoConfig secondConfig = new ClientSetInfoConfig(secondDriverInfo);\n    assertEquals(\"upstream-library_v1.0.0;spring-data-redis_v3.2.0\",\n      secondConfig.getUpstreamDrivers());\n    assertEquals(\"jedis(upstream-library_v1.0.0;spring-data-redis_v3.2.0)\",\n      secondConfig.getDriverInfo().getFormattedName());\n  }\n\n  @Test\n  public void withLibNameSuffixErrorForBraces() {\n    Arrays.asList('(', ')', '[', ']', '{', '}')\n        .forEach(brace -> assertThrows(JedisValidationException.class,\n          () -> ClientSetInfoConfig.withLibNameSuffix(\"\" + brace)));\n  }\n\n  @Test\n  public void builderWithNullDriverInfoThrows() {\n    assertThrows(JedisValidationException.class, () -> DriverInfo.builder(null));\n  }\n\n  @Test\n  public void builderNameNullThrows() {\n    assertThrows(JedisValidationException.class, () -> DriverInfo.builder().name(null));\n  }\n\n  @Test\n  public void builderCustomName() {\n    DriverInfo driverInfo = DriverInfo.builder().name(\"my-custom-client\").build();\n    assertEquals(\"my-custom-client\", driverInfo.getName());\n    assertEquals(\"my-custom-client\", driverInfo.getFormattedName());\n    assertEquals(\"\", driverInfo.getUpstreamDrivers());\n  }\n\n  @Test\n  public void builderCustomNameWithUpstreamDrivers() {\n    DriverInfo driverInfo = DriverInfo.builder().name(\"my-custom-client\")\n        .addUpstreamDriver(\"spring-data-redis\", \"3.2.0\").build();\n    assertEquals(\"my-custom-client\", driverInfo.getName());\n    assertEquals(\"my-custom-client(spring-data-redis_v3.2.0)\", driverInfo.getFormattedName());\n    assertEquals(\"spring-data-redis_v3.2.0\", driverInfo.getUpstreamDrivers());\n  }\n\n  @Test\n  public void builderCopiesExistingDriverInfo() {\n    DriverInfo original = DriverInfo.builder().name(\"custom-name\")\n        .addUpstreamDriver(\"driver1\", \"1.0.0\").build();\n\n    DriverInfo copied = DriverInfo.builder(original).addUpstreamDriver(\"driver2\", \"2.0.0\").build();\n\n    assertEquals(\"custom-name\", copied.getName());\n    assertEquals(\"driver2_v2.0.0;driver1_v1.0.0\", copied.getUpstreamDrivers());\n  }\n\n  @Test\n  public void addUpstreamDriverSingle() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"3.2.0\")\n        .build();\n    ClientSetInfoConfig config = new ClientSetInfoConfig(driverInfo);\n    assertEquals(\"spring-data-redis_v3.2.0\", config.getUpstreamDrivers());\n  }\n\n  @Test\n  public void addUpstreamDriverMultiple() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"driver1\", \"1.0.0\")\n        .addUpstreamDriver(\"driver2\", \"2.0.0\").addUpstreamDriver(\"driver3\", \"3.0.0\").build();\n    ClientSetInfoConfig config = new ClientSetInfoConfig(driverInfo);\n    assertEquals(\"driver3_v3.0.0;driver2_v2.0.0;driver1_v1.0.0\", config.getUpstreamDrivers());\n  }\n\n  @Test\n  public void addUpstreamDriverPrepends() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"first\", \"1.0.0\").build();\n    ClientSetInfoConfig config = new ClientSetInfoConfig(driverInfo);\n    assertEquals(\"first_v1.0.0\", config.getUpstreamDrivers());\n\n    driverInfo = DriverInfo.builder(config.getDriverInfo()).addUpstreamDriver(\"second\", \"2.0.0\")\n        .build();\n    config = new ClientSetInfoConfig(driverInfo);\n    assertEquals(\"second_v2.0.0;first_v1.0.0\", config.getUpstreamDrivers());\n\n    driverInfo = DriverInfo.builder(config.getDriverInfo()).addUpstreamDriver(\"third\", \"3.0.0\")\n        .build();\n    config = new ClientSetInfoConfig(driverInfo);\n    assertEquals(\"third_v3.0.0;second_v2.0.0;first_v1.0.0\", config.getUpstreamDrivers());\n  }\n\n  @Test\n  public void formattedNameWithNoUpstreamDrivers() {\n    DriverInfo driverInfo = DriverInfo.builder().build();\n    assertEquals(\"jedis\", driverInfo.getFormattedName());\n  }\n\n  @Test\n  public void formattedNameWithSingleUpstreamDriver() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"3.2.0\")\n        .build();\n    assertEquals(\"jedis(spring-data-redis_v3.2.0)\", driverInfo.getFormattedName());\n  }\n\n  @Test\n  public void formattedNameWithMultipleUpstreamDrivers() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"driver1\", \"1.0.0\")\n        .addUpstreamDriver(\"driver2\", \"2.0.0\").build();\n    assertEquals(\"jedis(driver2_v2.0.0;driver1_v1.0.0)\", driverInfo.getFormattedName());\n  }\n\n  @Test\n  public void toStringReturnsFormattedName() {\n    DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"3.2.0\")\n        .build();\n    assertEquals(driverInfo.getFormattedName(), driverInfo.toString());\n  }\n\n  @Test\n  public void driverNameValidation() {\n    // Valid names\n    DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"lettuce-core\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"akka-redis_2.13\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"jedis\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"redis-client\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"my_driver\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"driver123\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"Spring-Data\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"123driver\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"driver@name\", \"1.0.0\");\n    DriverInfo.builder().addUpstreamDriver(\"driver.name\", \"1.0.0\");\n\n    assertThrows(JedisValidationException.class,\n      () -> DriverInfo.builder().addUpstreamDriver(\"driver name\", \"1.0.0\")); // space\n  }\n\n  @Test\n  public void driverNameNullOrEmpty() {\n    assertThrows(JedisValidationException.class,\n      () -> DriverInfo.builder().addUpstreamDriver(null, \"3.2.0\"));\n    assertThrows(JedisValidationException.class,\n      () -> DriverInfo.builder().addUpstreamDriver(\"\", \"3.2.0\"));\n  }\n\n  @Test\n  public void driverVersionNullOrEmpty() {\n    assertThrows(JedisValidationException.class,\n      () -> DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", null));\n    assertThrows(JedisValidationException.class,\n      () -> DriverInfo.builder().addUpstreamDriver(\"spring-data-redis\", \"\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/misc/ClusterInitErrorTest.java",
    "content": "package redis.clients.jedis.misc;\n\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisClusterClient;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\n\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\npublic class ClusterInitErrorTest {\n\n  private static final String INIT_NO_ERROR_PROPERTY = \"jedis.cluster.initNoError\";\n\n  @AfterEach\n  public void cleanUp() {\n    System.getProperties().remove(INIT_NO_ERROR_PROPERTY);\n  }\n\n  @Test\n  public void initError() {\n    assertNull(System.getProperty(INIT_NO_ERROR_PROPERTY));\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n    assertThrows(JedisClusterOperationException.class, () -> {\n      try (RedisClusterClient cluster = RedisClusterClient.builder()\n          .nodes(Collections.singleton(endpoint.getHostAndPort()))\n          .clientConfig(endpoint.getClientConfigBuilder().build())\n          .build()) {\n        // Intentionally left empty because the exception is expected\n      }\n    });\n  }\n\n  @Test\n  public void initNoError() {\n    System.setProperty(INIT_NO_ERROR_PROPERTY, \"\");\n    EndpointConfig endpoint = Endpoints.getRedisEndpoint(\"standalone0\");\n    try (RedisClusterClient cluster = RedisClusterClient.builder()\n        .nodes(Collections.singleton(endpoint.getHostAndPort()))\n        .clientConfig(endpoint.getClientConfigBuilder().build())\n        .build()) {\n      assertThrows(JedisClusterOperationException.class, () -> cluster.get(\"foo\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/misc/ResponsesToStringTest.java",
    "content": "package redis.clients.jedis.misc;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class ResponsesToStringTest {\n\n  @Test\n  public void GeoRadiusResponse() {\n    byte[] member = {0x01, 0x02, 0x03, 0x04};\n\n    GeoRadiusResponse response = new GeoRadiusResponse(member);\n    response.setDistance(5);\n    response.setCoordinate(new GeoCoordinate(2, 3));\n    response.setRawScore(10);\n\n    GeoRadiusResponse response_copy = new GeoRadiusResponse(member);\n    response_copy.setDistance(5);\n    response_copy.setCoordinate(new GeoCoordinate(2, 3));\n    response_copy.setRawScore(10);\n\n    assertTrue(response.equals(response));\n    assertEquals(response, response_copy);\n    assertNotEquals(response, new Object());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/misc/TupleTest.java",
    "content": "package redis.clients.jedis.misc;\n\nimport java.util.HashSet;\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.resps.Tuple;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class TupleTest {\n\n  @Test\n  public void compareSameObject() {\n    Tuple t1 = new Tuple(\"foo\", 1d);\n    assertTrue(t1.equals(t1));\n  }\n\n  @Test\n  public void compareEqual() {\n    Tuple t1 = new Tuple(\"foo\", 1d);\n    Tuple t2 = new Tuple(\"foo\", 1d);\n\n    assertEquals(0, t1.compareTo(t2));\n    assertEquals(0, t2.compareTo(t1));\n    assertTrue(t1.equals(t2));\n    assertTrue(t2.equals(t1));\n  }\n\n  @Test\n  public void compareSameScore() {\n    Tuple t1 = new Tuple(\"foo\", 1d);\n    Tuple t2 = new Tuple(\"bar\", 1d);\n\n    assertEquals(1, t1.compareTo(t2));\n    assertEquals(-1, t2.compareTo(t1));\n    assertFalse(t1.equals(t2));\n    assertFalse(t2.equals(t1));\n  }\n\n  @Test\n  public void compareSameScoreObject() {\n    Double score = 1d;\n    Tuple t1 = new Tuple(\"foo\", score);\n    Tuple t2 = new Tuple(\"bar\", score);\n\n    assertEquals(1, t1.compareTo(t2));\n    assertEquals(-1, t2.compareTo(t1));\n    assertFalse(t1.equals(t2));\n    assertFalse(t2.equals(t1));\n  }\n\n  @Test\n  public void compareNoMatch() {\n    Tuple t1 = new Tuple(\"foo\", 1d);\n    Tuple t2 = new Tuple(\"bar\", 2d);\n\n    assertEquals(-1, t1.compareTo(t2));\n    assertEquals(1, t2.compareTo(t1));\n    assertFalse(t1.equals(t2));\n    assertFalse(t2.equals(t1));\n  }\n\n  @Test\n  public void compareDifferentType() {\n    Tuple t1 = new Tuple(\"foo\", 1d);\n\n    Object anyObject = new Object();\n    assertFalse(t1.equals(anyObject));\n\n    Object nullObject = null;\n    assertFalse(t1.equals(nullObject));\n  }\n\n  @Test\n  public void testToString() {\n    Tuple t1 = new Tuple(\"key-name\", 1d);\n    String toStringResult = t1.toString();\n    MatcherAssert.assertThat(toStringResult, Matchers.containsString(\"key-name\"));\n    MatcherAssert.assertThat(toStringResult, Matchers.containsString(\"1\"));\n  }\n\n  @Test\n  public void testSameElement() {\n    Tuple t1 = new Tuple(\"user1\", 10.0);\n    Tuple t2 = new Tuple(\"user1\", 5.0);\n\n    // Intentionally skipping compareTo.\n    assertFalse(t1.equals(t2));\n    assertFalse(t2.equals(t1));\n\n    HashSet<Tuple> hashSet = new HashSet<>();\n    hashSet.add(t1);\n    hashSet.add(t2);\n    assertEquals(2, hashSet.size());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/MockedCommandObjectsTestBase.java",
    "content": "package redis.clients.jedis.mocked;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.json.JSONArray;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.resps.*;\nimport redis.clients.jedis.search.ProfilingInfo;\nimport redis.clients.jedis.search.SearchResult;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.timeseries.*;\nimport redis.clients.jedis.util.KeyValue;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Provides an exhaustive list of mocked {@link redis.clients.jedis.CommandObject}s for use in unit tests.\n */\n@ExtendWith(MockitoExtension.class)\npublic abstract class MockedCommandObjectsTestBase {\n\n  /**\n   * Used for JSON related tests. The fields are not used actually, given that tests are mocked.\n   */\n  @SuppressWarnings(\"unused\")\n  public final static class MyBean {\n    String field1;\n    String field2;\n  }\n\n\n  // Below follows a list of mocked CommandObjects, one per type. This is the cleanest way to create\n  // mocks, given that CommandObject is a generic class. Using {@code Mockito.mock(...)} yields too\n  // many warnings related to generics.\n  // To make the code more readable, try to keep the list sorted alphabetically, and without automatic\n  // reformatting.\n\n  // @formatter:off\n  @Mock protected CommandObject<AggregationResult> aggregationResultCommandObject;\n  @Mock protected CommandObject<Boolean> booleanCommandObject;\n  @Mock protected CommandObject<Class<?>> classCommandObject;\n  @Mock protected CommandObject<Double> doubleCommandObject;\n  @Mock protected CommandObject<FunctionStats> functionStatsCommandObject;\n  @Mock protected CommandObject<KeyValue<Long, Double>> keyValueLongDoubleCommandObject;\n  @Mock protected CommandObject<KeyValue<Long, Long>> keyValueLongLongCommandObject;\n  @Mock protected CommandObject<KeyValue<String, List<String>>> keyValueStringListStringCommandObject;\n  @Mock protected CommandObject<KeyValue<String, List<Tuple>>> keyValueStringListTupleCommandObject;\n  @Mock protected CommandObject<KeyValue<String, String>> keyValueStringStringCommandObject;\n  @Mock protected CommandObject<KeyValue<String, Tuple>> keyValueStringTupleCommandObject;\n  @Mock protected CommandObject<KeyValue<byte[], List<Tuple>>> keyValueBytesListTupleCommandObject;\n  @Mock protected CommandObject<KeyValue<byte[], List<byte[]>>> keyValueBytesListBytesCommandObject;\n  @Mock protected CommandObject<KeyValue<byte[], Tuple>> keyValueBytesTupleCommandObject;\n  @Mock protected CommandObject<KeyValue<byte[], byte[]>> keyValueBytesBytesCommandObject;\n  @Mock protected CommandObject<LCSMatchResult> lcsMatchResultCommandObject;\n  @Mock protected CommandObject<List<Boolean>> listBooleanCommandObject;\n  @Mock protected CommandObject<List<Class<?>>> listClassCommandObject;\n  @Mock protected CommandObject<List<Double>> listDoubleCommandObject;\n  @Mock protected CommandObject<List<GeoCoordinate>> listGeoCoordinateCommandObject;\n  @Mock protected CommandObject<List<GeoRadiusResponse>> listGeoRadiusResponseCommandObject;\n  @Mock protected CommandObject<List<JSONArray>> listJsonArrayCommandObject;\n  @Mock protected CommandObject<List<LibraryInfo>> listLibraryInfoCommandObject;\n  @Mock protected CommandObject<List<List<Object>>> listListObjectCommandObject;\n  @Mock protected CommandObject<List<List<String>>> listListStringCommandObject;\n  @Mock protected CommandObject<List<Long>> listLongCommandObject;\n  @Mock protected CommandObject<List<Map.Entry<String, List<StreamEntry>>>> listEntryStringListStreamEntryCommandObject;\n  @Mock protected CommandObject<List<Map.Entry<String, String>>> listEntryStringStringCommandObject;\n  @Mock protected CommandObject<List<Map.Entry<byte[], byte[]>>> listEntryBytesBytesCommandObject;\n  @Mock protected CommandObject<List<MyBean>> listMyBeanCommandObject;\n  @Mock protected CommandObject<List<Object>> listObjectCommandObject;\n  @Mock protected CommandObject<List<StreamConsumerInfo>> listStreamConsumerInfoCommandObject;\n  @Mock protected CommandObject<List<StreamConsumersInfo>> listStreamConsumersInfoCommandObject;\n  @Mock protected CommandObject<List<StreamEntry>> listStreamEntryCommandObject;\n  @Mock protected CommandObject<List<StreamEntryID>> listStreamEntryIdCommandObject;\n  @Mock protected CommandObject<List<StreamGroupInfo>> listStreamGroupInfoCommandObject;\n  @Mock protected CommandObject<List<StreamPendingEntry>> listStreamPendingEntryCommandObject;\n  @Mock protected CommandObject<List<String>> listStringCommandObject;\n  @Mock protected CommandObject<List<TSElement>> listTsElementCommandObject;\n  @Mock protected CommandObject<List<Tuple>> listTupleCommandObject;\n  @Mock protected CommandObject<List<byte[]>> listBytesCommandObject;\n  @Mock protected CommandObject<Long> longCommandObject;\n  @Mock protected CommandObject<Map.Entry<AggregationResult, ProfilingInfo>> entryAggregationResultMapStringObjectCommandObject;\n  @Mock protected CommandObject<Map.Entry<Long, byte[]>> entryLongBytesCommandObject;\n  @Mock protected CommandObject<Map.Entry<SearchResult, ProfilingInfo>> entrySearchResultMapStringObjectCommandObject;\n  @Mock protected CommandObject<Map.Entry<StreamEntryID, List<StreamEntry>>> entryStreamEntryIdListStreamEntryCommandObject;\n  @Mock protected CommandObject<Map.Entry<StreamEntryID, List<StreamEntryID>>> entryStreamEntryIdListStreamEntryIdCommandObject;\n  @Mock protected CommandObject<Map<String, List<String>>> mapStringListStringCommandObject;\n  @Mock protected CommandObject<Map<String, List<StreamEntry>>> mapStringListStreamEntryCommandObject;\n  @Mock protected CommandObject<Map<String, Long>> mapStringLongCommandObject;\n  @Mock protected CommandObject<Map<String, Map<String, Double>>> mapStringMapStringDoubleCommandObject;\n  @Mock protected CommandObject<Map<String, Object>> mapStringObjectCommandObject;\n  @Mock protected CommandObject<Map<String, String>> mapStringStringCommandObject;\n  @Mock protected CommandObject<Map<String, TSMGetElement>> mapStringTsmGetElementCommandObject;\n  @Mock protected CommandObject<Map<String, TSMRangeElements>> mapStringTsmRangeElementsCommandObject;\n  @Mock protected CommandObject<Map<byte[], byte[]>> mapBytesBytesCommandObject;\n  @Mock protected CommandObject<MyBean> myBeanCommandObject;\n  @Mock protected CommandObject<Object> objectCommandObject;\n  @Mock protected CommandObject<ScanResult<Map.Entry<String, String>>> scanResultEntryStringStringCommandObject;\n  @Mock protected CommandObject<ScanResult<Map.Entry<byte[], byte[]>>> scanResultEntryBytesBytesCommandObject;\n  @Mock protected CommandObject<ScanResult<String>> scanResultStringCommandObject;\n  @Mock protected CommandObject<ScanResult<Tuple>> scanResultTupleCommandObject;\n  @Mock protected CommandObject<ScanResult<byte[]>> scanResultBytesCommandObject;\n  @Mock protected CommandObject<SearchResult> searchResultCommandObject;\n  @Mock protected CommandObject<Set<String>> setStringCommandObject;\n  @Mock protected CommandObject<Set<byte[]>> setBytesCommandObject;\n  @Mock protected CommandObject<StreamEntryID> streamEntryIdCommandObject;\n  @Mock protected CommandObject<StreamFullInfo> streamFullInfoCommandObject;\n  @Mock protected CommandObject<StreamInfo> streamInfoCommandObject;\n  @Mock protected CommandObject<StreamPendingSummary> streamPendingSummaryCommandObject;\n  @Mock protected CommandObject<String> stringCommandObject;\n  @Mock protected CommandObject<TSElement> tsElementCommandObject;\n  @Mock protected CommandObject<TSInfo> tsInfoCommandObject;\n  @Mock protected CommandObject<Tuple> tupleCommandObject;\n  @Mock protected CommandObject<byte[]> bytesCommandObject;\n  // @formatter:on\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseBitmapCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\n\npublic class PipeliningBaseBitmapCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testBitcount() {\n    when(commandObjects.bitcount(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitcount(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitcountBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.bitcount(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitcount(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitcountRange() {\n    when(commandObjects.bitcount(\"key\", 0, 10)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitcount(\"key\", 0, 10);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitcountRangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long start = 0L;\n    long end = 10L;\n\n    when(commandObjects.bitcount(key, start, end)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitcount(key, start, end);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitcountRangeOption() {\n    BitCountOption option = BitCountOption.BYTE;\n\n    when(commandObjects.bitcount(\"key\", 0, 10, option)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitcount(\"key\", 0, 10, option);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitcountRangeOptionBinary() {\n    byte[] key = \"key\".getBytes();\n    long start = 0L;\n    long end = 10L;\n    BitCountOption option = BitCountOption.BYTE;\n\n    when(commandObjects.bitcount(key, start, end, option)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitcount(key, start, end, option);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitfield() {\n    String[] arguments = { \"INCRBY\", \"mykey\", \"2\", \"1\" };\n\n    when(commandObjects.bitfield(\"key\", arguments)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.bitfield(\"key\", arguments);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitfieldBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[][] arguments = { \"INCRBY\".getBytes(), \"mykey\".getBytes(), \"2\".getBytes(), \"1\".getBytes() };\n\n    when(commandObjects.bitfield(key, arguments)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.bitfield(key, arguments);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitfieldReadonly() {\n    String[] arguments = { \"GET\", \"u4\", \"0\" };\n\n    when(commandObjects.bitfieldReadonly(\"key\", arguments)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.bitfieldReadonly(\"key\", arguments);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitfieldReadonlyBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[][] arguments = { \"GET\".getBytes(), \"u4\".getBytes(), \"0\".getBytes() };\n\n    when(commandObjects.bitfieldReadonly(key, arguments)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.bitfieldReadonly(key, arguments);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitop() {\n    BitOP op = BitOP.AND;\n\n    when(commandObjects.bitop(op, \"destKey\", \"srckey1\", \"srckey2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitop(op, \"destKey\", \"srckey1\", \"srckey2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitopBinary() {\n    BitOP op = BitOP.AND;\n    byte[] destKey = \"destKey\".getBytes();\n    byte[] srcKey1 = \"srcKey1\".getBytes();\n    byte[] srcKey2 = \"srcKey2\".getBytes();\n\n    when(commandObjects.bitop(op, destKey, srcKey1, srcKey2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitop(op, destKey, srcKey1, srcKey2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitpos() {\n    when(commandObjects.bitpos(\"key\", true)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitpos(\"key\", true);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitposBinary() {\n    byte[] key = \"key\".getBytes();\n    boolean value = true;\n\n    when(commandObjects.bitpos(key, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitpos(key, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitposParams() {\n    BitPosParams params = new BitPosParams(0, -1);\n\n    when(commandObjects.bitpos(\"key\", true, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitpos(\"key\", true, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBitposParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    boolean value = true;\n    BitPosParams params = new BitPosParams(0);\n\n    when(commandObjects.bitpos(key, value, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bitpos(key, value, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetbit() {\n    when(commandObjects.getbit(\"key\", 100)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.getbit(\"key\", 100);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetbitBinary() {\n    byte[] key = \"key\".getBytes();\n    long offset = 10L;\n\n    when(commandObjects.getbit(key, offset)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.getbit(key, offset);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetbit() {\n    when(commandObjects.setbit(\"key\", 100, true)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.setbit(\"key\", 100, true);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetbitBinary() {\n    byte[] key = \"key\".getBytes();\n    long offset = 10L;\n    boolean value = true;\n\n    when(commandObjects.setbit(key, offset, value)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.setbit(key, offset, value);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseBloomFilterCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.bloom.BFInsertParams;\nimport redis.clients.jedis.bloom.BFReserveParams;\n\npublic class PipeliningBaseBloomFilterCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testBfAdd() {\n    when(commandObjects.bfAdd(\"myBloomFilter\", \"item1\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.bfAdd(\"myBloomFilter\", \"item1\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfCard() {\n    when(commandObjects.bfCard(\"myBloomFilter\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.bfCard(\"myBloomFilter\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfExists() {\n    when(commandObjects.bfExists(\"myBloomFilter\", \"item1\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.bfExists(\"myBloomFilter\", \"item1\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfInfo() {\n    when(commandObjects.bfInfo(\"myBloomFilter\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.bfInfo(\"myBloomFilter\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfInsert() {\n    when(commandObjects.bfInsert(\"myBloomFilter\", \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.bfInsert(\"myBloomFilter\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfInsertWithParams() {\n    BFInsertParams insertParams = new BFInsertParams().capacity(10000L).error(0.01);\n\n    when(commandObjects.bfInsert(\"myBloomFilter\", insertParams, \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.bfInsert(\"myBloomFilter\", insertParams, \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfLoadChunk() {\n    byte[] data = { 1, 2, 3, 4 };\n\n    when(commandObjects.bfLoadChunk(\"myBloomFilter\", 0L, data)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.bfLoadChunk(\"myBloomFilter\", 0L, data);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfMAdd() {\n    when(commandObjects.bfMAdd(\"myBloomFilter\", \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.bfMAdd(\"myBloomFilter\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfMExists() {\n    when(commandObjects.bfMExists(\"myBloomFilter\", \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.bfMExists(\"myBloomFilter\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfReserve() {\n    double errorRate = 0.01;\n    long capacity = 10000L;\n\n    when(commandObjects.bfReserve(\"myBloomFilter\", errorRate, capacity)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.bfReserve(\"myBloomFilter\", errorRate, capacity);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfReserveWithParams() {\n    double errorRate = 0.01;\n    long capacity = 10000L;\n\n    BFReserveParams reserveParams = new BFReserveParams().expansion(2);\n    when(commandObjects.bfReserve(\"myBloomFilter\", errorRate, capacity, reserveParams)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.bfReserve(\"myBloomFilter\", errorRate, capacity, reserveParams);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBfScanDump() {\n    when(commandObjects.bfScanDump(\"myBloomFilter\", 0L)).thenReturn(entryLongBytesCommandObject);\n\n    Response<Map.Entry<Long, byte[]>> response = pipeliningBase.bfScanDump(\"myBloomFilter\", 0L);\n\n    assertThat(commands, contains(entryLongBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseCountMinSketchCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\n\npublic class PipeliningBaseCountMinSketchCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testCmsIncrBy() {\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"item1\", 1L);\n    itemIncrements.put(\"item2\", 2L);\n\n    when(commandObjects.cmsIncrBy(\"myCountMinSketch\", itemIncrements)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.cmsIncrBy(\"myCountMinSketch\", itemIncrements);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCmsInfo() {\n    when(commandObjects.cmsInfo(\"myCountMinSketch\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.cmsInfo(\"myCountMinSketch\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCmsInitByDim() {\n    when(commandObjects.cmsInitByDim(\"myCountMinSketch\", 1000L, 5L)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cmsInitByDim(\"myCountMinSketch\", 1000L, 5L);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCmsInitByProb() {\n    double error = 0.01;\n    double probability = 0.99;\n\n    when(commandObjects.cmsInitByProb(\"myCountMinSketch\", error, probability)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cmsInitByProb(\"myCountMinSketch\", error, probability);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCmsMerge() {\n    when(commandObjects.cmsMerge(\"mergedCountMinSketch\", \"cms1\", \"cms2\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cmsMerge(\"mergedCountMinSketch\", \"cms1\", \"cms2\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCmsMergeWithWeights() {\n    Map<String, Long> keysAndWeights = new HashMap<>();\n    keysAndWeights.put(\"cms1\", 1L);\n    keysAndWeights.put(\"cms2\", 2L);\n\n    when(commandObjects.cmsMerge(\"mergedCountMinSketch\", keysAndWeights)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cmsMerge(\"mergedCountMinSketch\", keysAndWeights);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCmsQuery() {\n    when(commandObjects.cmsQuery(\"myCountMinSketch\", \"item1\", \"item2\")).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.cmsQuery(\"myCountMinSketch\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseCuckooFilterCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.bloom.CFInsertParams;\nimport redis.clients.jedis.bloom.CFReserveParams;\n\npublic class PipeliningBaseCuckooFilterCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testCfAdd() {\n    when(commandObjects.cfAdd(\"myCuckooFilter\", \"item1\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.cfAdd(\"myCuckooFilter\", \"item1\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfAddNx() {\n    when(commandObjects.cfAddNx(\"myCuckooFilter\", \"item1\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.cfAddNx(\"myCuckooFilter\", \"item1\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfCount() {\n    when(commandObjects.cfCount(\"myCuckooFilter\", \"item1\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.cfCount(\"myCuckooFilter\", \"item1\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfDel() {\n    when(commandObjects.cfDel(\"myCuckooFilter\", \"item1\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.cfDel(\"myCuckooFilter\", \"item1\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfExists() {\n    when(commandObjects.cfExists(\"myCuckooFilter\", \"item1\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.cfExists(\"myCuckooFilter\", \"item1\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfInfo() {\n    when(commandObjects.cfInfo(\"myCuckooFilter\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.cfInfo(\"myCuckooFilter\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfInsert() {\n    when(commandObjects.cfInsert(\"myCuckooFilter\", \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.cfInsert(\"myCuckooFilter\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfInsertWithParams() {\n    CFInsertParams insertParams = new CFInsertParams().capacity(10000L).noCreate();\n\n    when(commandObjects.cfInsert(\"myCuckooFilter\", insertParams, \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.cfInsert(\"myCuckooFilter\", insertParams, \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfInsertNx() {\n    when(commandObjects.cfInsertNx(\"myCuckooFilter\", \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.cfInsertNx(\"myCuckooFilter\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfInsertNxWithParams() {\n    CFInsertParams insertParams = new CFInsertParams().capacity(10000L).noCreate();\n\n    when(commandObjects.cfInsertNx(\"myCuckooFilter\", insertParams, \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.cfInsertNx(\"myCuckooFilter\", insertParams, \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfLoadChunk() {\n    byte[] data = { 1, 2, 3, 4 };\n\n    when(commandObjects.cfLoadChunk(\"myCuckooFilter\", 0L, data)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cfLoadChunk(\"myCuckooFilter\", 0L, data);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfMExists() {\n    when(commandObjects.cfMExists(\"myCuckooFilter\", \"item1\", \"item2\", \"item3\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.cfMExists(\"myCuckooFilter\", \"item1\", \"item2\", \"item3\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfReserve() {\n    when(commandObjects.cfReserve(\"myCuckooFilter\", 10000L)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cfReserve(\"myCuckooFilter\", 10000L);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfReserveWithParams() {\n    CFReserveParams reserveParams = new CFReserveParams().bucketSize(2).maxIterations(500).expansion(2);\n\n    when(commandObjects.cfReserve(\"myCuckooFilter\", 10000L, reserveParams)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.cfReserve(\"myCuckooFilter\", 10000L, reserveParams);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCfScanDump() {\n    when(commandObjects.cfScanDump(\"myCuckooFilter\", 0L)).thenReturn(entryLongBytesCommandObject);\n\n    Response<Map.Entry<Long, byte[]>> response = pipeliningBase.cfScanDump(\"myCuckooFilter\", 0L);\n\n    assertThat(commands, contains(entryLongBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGenericCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.CompareCondition;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class PipeliningBaseGenericCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testCopy() {\n    when(commandObjects.copy(\"srcKey\", \"dstKey\", true)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.copy(\"srcKey\", \"dstKey\", true);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testCopyBinary() {\n    byte[] srcKey = \"sourceKey\".getBytes();\n    byte[] dstKey = \"destinationKey\".getBytes();\n    boolean replace = true;\n\n    when(commandObjects.copy(srcKey, dstKey, replace)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.copy(srcKey, dstKey, replace);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDel() {\n    when(commandObjects.del(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.del(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDelBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.del(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.del(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDelMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.del(keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.del(keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDelMultipleKeysBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.del(key1, key2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.del(key1, key2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDelexBinary() {\n    byte[] key = \"key\".getBytes();\n    CompareCondition condition = CompareCondition.valueEq(\"value\");\n\n    when(commandObjects.delex(key, condition)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.delex(key, condition);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDelex() {\n    String key = \"key\";\n    CompareCondition condition = CompareCondition.valueEq(\"value\");\n\n    when(commandObjects.delex(key, condition)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.delex(key, condition);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDigestKeyBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.digestKey(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.digestKey(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDigestKey() {\n    String key = \"key\";\n\n    when(commandObjects.digestKey(key)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.digestKey(key);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDump() {\n    when(commandObjects.dump(\"key\")).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.dump(\"key\");\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDumpBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.dump(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.dump(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExists() {\n    when(commandObjects.exists(\"key\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> result = pipeliningBase.exists(\"key\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(result, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExistsBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.exists(key)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.exists(key);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExistsMultipleKeys() {\n    when(commandObjects.exists(\"key1\", \"key2\", \"key3\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.exists(\"key1\", \"key2\", \"key3\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExistsMultipleKeysBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.exists(key1, key2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.exists(key1, key2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpire() {\n    when(commandObjects.expire(\"key\", 60)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expire(\"key\", 60);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireBinary() {\n    byte[] key = \"key\".getBytes();\n    long seconds = 60L;\n\n    when(commandObjects.expire(key, seconds)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expire(key, seconds);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireWithExpiryOption() {\n    when(commandObjects.expire(\"key\", 60, ExpiryOption.NX)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expire(\"key\", 60, ExpiryOption.NX);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireWithExpiryOptionBinary() {\n    byte[] key = \"key\".getBytes();\n    long seconds = 60L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.expire(key, seconds, expiryOption)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expire(key, seconds, expiryOption);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireAt() {\n    int unixTime = 1609459200;\n\n    when(commandObjects.expireAt(\"key\", unixTime)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expireAt(\"key\", unixTime);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireAtBinary() {\n    byte[] key = \"key\".getBytes();\n    long unixTime = 1625097600L;\n\n    when(commandObjects.expireAt(key, unixTime)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expireAt(key, unixTime);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireAtWithExpiryOption() {\n    int unixTime = 1609459200;\n\n    when(commandObjects.expireAt(\"key\", unixTime, ExpiryOption.NX)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expireAt(\"key\", unixTime, ExpiryOption.NX);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireAtWithExpiryOptionBinary() {\n    byte[] key = \"key\".getBytes();\n    long unixTime = 1625097600L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.expireAt(key, unixTime, expiryOption)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expireAt(key, unixTime, expiryOption);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireTime() {\n    when(commandObjects.expireTime(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expireTime(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExpireTimeBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.expireTime(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.expireTime(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testKeys() {\n    when(commandObjects.keys(\"pattern\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.keys(\"pattern\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testKeysBinary() {\n    byte[] pattern = \"*\".getBytes();\n\n    when(commandObjects.keys(pattern)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.keys(pattern);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMigrate() {\n    when(commandObjects.migrate(\"host\", 6379, \"key\", 5000)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.migrate(\"host\", 6379, \"key\", 5000);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMigrateBinary() {\n    String host = \"localhost\";\n    int port = 6379;\n    byte[] key = \"key\".getBytes();\n    int timeout = 1000;\n\n    when(commandObjects.migrate(host, port, key, timeout)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.migrate(host, port, key, timeout);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMigrateMultipleKeys() {\n    MigrateParams params = new MigrateParams();\n    String[] keys = { \"key1\", \"key2\" };\n\n    when(commandObjects.migrate(\"host\", 6379, 5000, params, keys)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.migrate(\"host\", 6379, 5000, params, keys);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMigrateMultipleKeysBinary() {\n    String host = \"localhost\";\n    int port = 6379;\n    int timeout = 1000;\n    MigrateParams params = MigrateParams.migrateParams().copy().replace();\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.migrate(host, port, timeout, params, key1, key2)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.migrate(host, port, timeout, params, key1, key2);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectEncoding() {\n    when(commandObjects.objectEncoding(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.objectEncoding(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectEncodingBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.objectEncoding(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.objectEncoding(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectFreq() {\n    when(commandObjects.objectFreq(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.objectFreq(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectFreqBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.objectFreq(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.objectFreq(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectIdletime() {\n    when(commandObjects.objectIdletime(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.objectIdletime(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectIdletimeBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.objectIdletime(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.objectIdletime(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectRefcount() {\n    when(commandObjects.objectRefcount(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.objectRefcount(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testObjectRefcountBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.objectRefcount(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.objectRefcount(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPersist() {\n    when(commandObjects.persist(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.persist(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPersistBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.persist(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.persist(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpire() {\n    when(commandObjects.pexpire(\"key\", 100000)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpire(\"key\", 100000);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireBinary() {\n    byte[] key = \"key\".getBytes();\n    long milliseconds = 60000L;\n\n    when(commandObjects.pexpire(key, milliseconds)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpire(key, milliseconds);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireWithExpiryOption() {\n    when(commandObjects.pexpire(\"key\", 100000, ExpiryOption.NX)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpire(\"key\", 100000, ExpiryOption.NX);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireWithExpiryOptionBinary() {\n    byte[] key = \"key\".getBytes();\n    long milliseconds = 60000L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.pexpire(key, milliseconds, expiryOption)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpire(key, milliseconds, expiryOption);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireAt() {\n    long millisecondsTimestamp = 1609459200000L;\n\n    when(commandObjects.pexpireAt(\"key\", millisecondsTimestamp)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpireAt(\"key\", millisecondsTimestamp);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireAtBinary() {\n    byte[] key = \"key\".getBytes();\n    long millisecondsTimestamp = 1625097600000L;\n\n    when(commandObjects.pexpireAt(key, millisecondsTimestamp)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpireAt(key, millisecondsTimestamp);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireAtWithExpiryOption() {\n    long millisecondsTimestamp = 1609459200000L;\n\n    when(commandObjects.pexpireAt(\"key\", millisecondsTimestamp, ExpiryOption.NX)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpireAt(\"key\", millisecondsTimestamp, ExpiryOption.NX);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireAtWithExpiryOptionBinary() {\n    byte[] key = \"key\".getBytes();\n    long millisecondsTimestamp = 1625097600000L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpireAt(key, millisecondsTimestamp, expiryOption);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireTime() {\n    when(commandObjects.pexpireTime(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpireTime(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPexpireTimeBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.pexpireTime(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pexpireTime(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPttl() {\n    when(commandObjects.pttl(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pttl(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPttlBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.pttl(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pttl(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRandomKey() {\n    when(commandObjects.randomKey()).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.randomKey();\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRandomBinaryKey() {\n    when(commandObjects.randomBinaryKey()).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.randomBinaryKey();\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRename() {\n    when(commandObjects.rename(\"oldkey\", \"newkey\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.rename(\"oldkey\", \"newkey\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRenameBinary() {\n    byte[] oldkey = \"oldKey\".getBytes();\n    byte[] newkey = \"newKey\".getBytes();\n\n    when(commandObjects.rename(oldkey, newkey)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.rename(oldkey, newkey);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRenamenx() {\n    when(commandObjects.renamenx(\"oldkey\", \"newkey\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.renamenx(\"oldkey\", \"newkey\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRenamenxBinary() {\n    byte[] oldkey = \"oldKey\".getBytes();\n    byte[] newkey = \"newKey\".getBytes();\n\n    when(commandObjects.renamenx(oldkey, newkey)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.renamenx(oldkey, newkey);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRestore() {\n    byte[] serializedValue = new byte[]{ 1, 2, 3 };\n    long ttl = 1000L;\n\n    when(commandObjects.restore(\"key\", ttl, serializedValue)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.restore(\"key\", ttl, serializedValue);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRestoreBinary() {\n    byte[] key = \"key\".getBytes();\n    long ttl = 0L;\n    byte[] serializedValue = \"serialized\".getBytes();\n\n    when(commandObjects.restore(key, ttl, serializedValue)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.restore(key, ttl, serializedValue);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRestoreWithParams() {\n    byte[] serializedValue = new byte[]{ 1, 2, 3 };\n    long ttl = 1000L;\n    RestoreParams params = new RestoreParams();\n\n    when(commandObjects.restore(\"key\", ttl, serializedValue, params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.restore(\"key\", ttl, serializedValue, params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRestoreWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    long ttl = 0L;\n    byte[] serializedValue = \"serialized\".getBytes();\n    RestoreParams params = RestoreParams.restoreParams().replace();\n\n    when(commandObjects.restore(key, ttl, serializedValue, params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.restore(key, ttl, serializedValue, params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScan() {\n    when(commandObjects.scan(\"0\")).thenReturn(scanResultStringCommandObject);\n\n    Response<ScanResult<String>> response = pipeliningBase.scan(\"0\");\n\n    assertThat(commands, contains(scanResultStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScanBinary() {\n    byte[] cursor = \"0\".getBytes();\n\n    when(commandObjects.scan(cursor)).thenReturn(scanResultBytesCommandObject);\n\n    Response<ScanResult<byte[]>> response = pipeliningBase.scan(cursor);\n\n    assertThat(commands, contains(scanResultBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScanWithParams() {\n    ScanParams scanParams = new ScanParams();\n\n    when(commandObjects.scan(\"0\", scanParams)).thenReturn(scanResultStringCommandObject);\n\n    Response<ScanResult<String>> response = pipeliningBase.scan(\"0\", scanParams);\n\n    assertThat(commands, contains(scanResultStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScanWithParamsBinary() {\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n\n    when(commandObjects.scan(cursor, params)).thenReturn(scanResultBytesCommandObject);\n\n    Response<ScanResult<byte[]>> response = pipeliningBase.scan(cursor, params);\n\n    assertThat(commands, contains(scanResultBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScanWithType() {\n    ScanParams scanParams = new ScanParams();\n\n    when(commandObjects.scan(\"0\", scanParams, \"type\")).thenReturn(scanResultStringCommandObject);\n\n    Response<ScanResult<String>> response = pipeliningBase.scan(\"0\", scanParams, \"type\");\n\n    assertThat(commands, contains(scanResultStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScanWithTypeBinary() {\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    byte[] type = \"string\".getBytes();\n\n    when(commandObjects.scan(cursor, params, type)).thenReturn(scanResultBytesCommandObject);\n\n    Response<ScanResult<byte[]>> response = pipeliningBase.scan(cursor, params, type);\n\n    assertThat(commands, contains(scanResultBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSort() {\n    when(commandObjects.sort(\"key\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.sort(\"key\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.sort(key)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.sort(key);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortWithParams() {\n    SortingParams sortingParams = new SortingParams();\n\n    when(commandObjects.sort(\"key\", sortingParams)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.sort(\"key\", sortingParams);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 10);\n\n    when(commandObjects.sort(key, sortingParams)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.sort(key, sortingParams);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortStore() {\n    when(commandObjects.sort(\"key\", \"dstKey\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sort(\"key\", \"dstKey\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortStoreBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] dstkey = \"dstkey\".getBytes();\n\n    when(commandObjects.sort(key, dstkey)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sort(key, dstkey);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortStoreWithParams() {\n    SortingParams sortingParams = new SortingParams();\n\n    when(commandObjects.sort(\"key\", sortingParams, \"dstKey\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sort(\"key\", sortingParams, \"dstKey\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortStoreWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] dstkey = \"dstkey\".getBytes();\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 10);\n\n    when(commandObjects.sort(key, sortingParams, dstkey)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sort(key, sortingParams, dstkey);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortReadonly() {\n    SortingParams sortingParams = new SortingParams();\n\n    when(commandObjects.sortReadonly(\"key\", sortingParams)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.sortReadonly(\"key\", sortingParams);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSortReadonlyBinary() {\n    byte[] key = \"key\".getBytes();\n    SortingParams sortingParams = new SortingParams().alpha().limit(0, 10);\n\n    when(commandObjects.sortReadonly(key, sortingParams)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.sortReadonly(key, sortingParams);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTouch() {\n    when(commandObjects.touch(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.touch(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTouchBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.touch(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.touch(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTouchMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.touch(keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.touch(keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTouchMultipleKeysBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.touch(key1, key2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.touch(key1, key2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTtl() {\n    when(commandObjects.ttl(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ttl(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTtlBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.ttl(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ttl(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testType() {\n    when(commandObjects.type(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.type(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTypeBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.type(key)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.type(key);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testUnlink() {\n    when(commandObjects.unlink(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.unlink(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testUnlinkBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.unlink(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.unlink(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testUnlinkMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.unlink(keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.unlink(keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testUnlinkMultipleKeysBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.unlink(key1, key2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.unlink(key1, key2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testWaitReplicas() {\n    int replicas = 2;\n    long timeout = 1000L;\n\n    when(commandObjects.waitReplicas(\"key\", replicas, timeout)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.waitReplicas(\"key\", replicas, timeout);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testWaitReplicasBinary() {\n    byte[] sampleKey = \"sampleKey\".getBytes();\n    int replicas = 1;\n    long timeout = 1000;\n\n    when(commandObjects.waitReplicas(sampleKey, replicas, timeout)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.waitReplicas(sampleKey, replicas, timeout);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testWaitAOF() {\n    long numLocal = 1L;\n    long numReplicas = 1L;\n    long timeout = 1000L;\n\n    when(commandObjects.waitAOF(\"key\", numLocal, numReplicas, timeout)).thenReturn(keyValueLongLongCommandObject);\n\n    Response<KeyValue<Long, Long>> response = pipeliningBase.waitAOF(\"key\", numLocal, numReplicas, timeout);\n\n    assertThat(commands, contains(keyValueLongLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testWaitAOFBinary() {\n    byte[] sampleKey = \"sampleKey\".getBytes();\n    long numLocal = 1;\n    long numReplicas = 1;\n    long timeout = 1000;\n\n    when(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout)).thenReturn(keyValueLongLongCommandObject);\n\n    Response<KeyValue<Long, Long>> response = pipeliningBase.waitAOF(sampleKey, numLocal, numReplicas, timeout);\n\n    assertThat(commands, contains(keyValueLongLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGeospatialCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic class PipeliningBaseGeospatialCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testGeoadd() {\n    when(commandObjects.geoadd(\"key\", 13.361389, 38.115556, \"member\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geoadd(\"key\", 13.361389, 38.115556, \"member\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoaddBinary() {\n    byte[] key = \"location\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    byte[] member = \"Sicily\".getBytes();\n\n    when(commandObjects.geoadd(key, longitude, latitude, member)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geoadd(key, longitude, latitude, member);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoaddMap() {\n    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"member\", new GeoCoordinate(13.361389, 38.115556));\n\n    when(commandObjects.geoadd(\"key\", memberCoordinateMap)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geoadd(\"key\", memberCoordinateMap);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoaddMapBinary() {\n    byte[] key = \"location\".getBytes();\n\n    Map<byte[], GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"Palermo\".getBytes(), new GeoCoordinate(13.361389, 38.115556));\n\n    when(commandObjects.geoadd(key, memberCoordinateMap)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geoadd(key, memberCoordinateMap);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoaddMapWithParams() {\n    GeoAddParams params = new GeoAddParams();\n\n    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"member\", new GeoCoordinate(13.361389, 38.115556));\n\n    when(commandObjects.geoadd(\"key\", params, memberCoordinateMap)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geoadd(\"key\", params, memberCoordinateMap);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoaddMapWithParamsBinary() {\n    byte[] key = \"location\".getBytes();\n    GeoAddParams params = GeoAddParams.geoAddParams();\n\n    Map<byte[], GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"Palermo\".getBytes(), new GeoCoordinate(13.361389, 38.115556));\n\n    when(commandObjects.geoadd(key, params, memberCoordinateMap)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geoadd(key, params, memberCoordinateMap);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeodist() {\n    when(commandObjects.geodist(\"key\", \"member1\", \"member2\")).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.geodist(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeodistBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member1 = \"Palermo\".getBytes();\n    byte[] member2 = \"Catania\".getBytes();\n\n    when(commandObjects.geodist(key, member1, member2)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.geodist(key, member1, member2);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeodistWithUnit() {\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geodist(\"key\", \"member1\", \"member2\", unit)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.geodist(\"key\", \"member1\", \"member2\", unit);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeodistWithUnitBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member1 = \"Palermo\".getBytes();\n    byte[] member2 = \"Catania\".getBytes();\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geodist(key, member1, member2, unit)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.geodist(key, member1, member2, unit);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeohash() {\n    when(commandObjects.geohash(\"key\", \"member1\", \"member2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.geohash(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeohashBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n\n    when(commandObjects.geohash(key, member)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.geohash(key, member);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeopos() {\n    when(commandObjects.geopos(\"key\", \"member1\", \"member2\")).thenReturn(listGeoCoordinateCommandObject);\n\n    Response<List<GeoCoordinate>> response = pipeliningBase.geopos(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(listGeoCoordinateCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoposBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n\n    when(commandObjects.geopos(key, member)).thenReturn(listGeoCoordinateCommandObject);\n\n    Response<List<GeoCoordinate>> response = pipeliningBase.geopos(key, member);\n\n    assertThat(commands, contains(listGeoCoordinateCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradius() {\n    when(commandObjects.georadius(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response =\n        pipeliningBase.georadius(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusBinary() {\n    byte[] key = \"location\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.georadius(key, longitude, latitude, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadius(key, longitude, latitude, radius, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusReadonly() {\n    when(commandObjects.georadiusReadonly(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response =\n        pipeliningBase.georadiusReadonly(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusReadonlyBinary() {\n    byte[] key = \"location\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadiusReadonly(key, longitude, latitude, radius, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusWithParam() {\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadius(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM, param))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response =\n        pipeliningBase.georadius(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusWithParamBinary() {\n    byte[] key = \"location\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadius(key, longitude, latitude, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadius(key, longitude, latitude, radius, unit, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusReadonlyWithParam() {\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadiusReadonly(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM, param))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response =\n        pipeliningBase.georadiusReadonly(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusReadonlyWithParamBinary() {\n    byte[] key = \"location\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadiusReadonly(key, longitude, latitude, radius, unit, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMember() {\n    when(commandObjects.georadiusByMember(\"key\", \"member\", 100.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response =\n        pipeliningBase.georadiusByMember(\"key\", \"member\", 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.georadiusByMember(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadiusByMember(key, member, radius, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonly() {\n    when(commandObjects.georadiusByMemberReadonly(\"key\", \"member\", 100.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response =\n        pipeliningBase.georadiusByMemberReadonly(\"key\", \"member\", 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonlyBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadiusByMemberReadonly(key, member, radius, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberWithParam() {\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadiusByMember(\"key\", \"member\", 100.0, GeoUnit.KM, param))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase\n        .georadiusByMember(\"key\", \"member\", 100.0, GeoUnit.KM, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberWithParamBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadiusByMember(key, member, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadiusByMember(key, member, radius, unit, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonlyWithParam() {\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadiusByMemberReadonly(\"key\", \"member\", 100.0, GeoUnit.KM, param))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase\n        .georadiusByMemberReadonly(\"key\", \"member\", 100.0, GeoUnit.KM, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonlyWithParamBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n\n    when(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.georadiusByMemberReadonly(key, member, radius, unit, param);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusStore() {\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam().store(\"storeKey\");\n\n    when(commandObjects.georadiusStore(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM, param, storeParam))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase\n        .georadiusStore(\"key\", 15.0, 37.0, 100.0, GeoUnit.KM, param, storeParam);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusStoreBinary() {\n    byte[] key = \"location\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam().store(\"storeKey\");\n\n    when(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberStore() {\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam().store(\"storeKey\");\n\n    when(commandObjects.georadiusByMemberStore(\"key\", \"member\", 100.0, GeoUnit.KM, param, storeParam))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase\n        .georadiusByMemberStore(\"key\", \"member\", 100.0, GeoUnit.KM, param, storeParam);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeoradiusByMemberStoreBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam().store(\"storeKey\");\n\n    when(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.georadiusByMemberStore(key, member, radius, unit, param, storeParam);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByMemberRadius() {\n    when(commandObjects.geosearch(\"key\", \"member\", 100.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase\n        .geosearch(\"key\", \"member\", 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByMemberRadiusBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearch(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(key, member, radius, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByCoordRadius() {\n    GeoCoordinate coord = new GeoCoordinate(15.0, 37.0);\n\n    when(commandObjects.geosearch(\"key\", coord, 100.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(\"key\", coord, 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByCoordRadiusBinary() {\n    byte[] key = \"location\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(13.361389, 38.115556);\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearch(key, coord, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(key, coord, radius, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByMemberBox() {\n    when(commandObjects.geosearch(\"key\", \"member\", 50.0, 50.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase\n        .geosearch(\"key\", \"member\", 50.0, 50.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByMemberBoxBinary() {\n    byte[] key = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double width = 200;\n    double height = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearch(key, member, width, height, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(key, member, width, height, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByCoordBox() {\n    GeoCoordinate coord = new GeoCoordinate(15.0, 37.0);\n\n    when(commandObjects.geosearch(\"key\", coord, 50.0, 50.0, GeoUnit.KM))\n        .thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase\n        .geosearch(\"key\", coord, 50.0, 50.0, GeoUnit.KM);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchByCoordBoxBinary() {\n    byte[] key = \"location\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(13.361389, 38.115556);\n    double width = 200;\n    double height = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearch(key, coord, width, height, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(key, coord, width, height, unit);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchWithParams() {\n    GeoSearchParam params = new GeoSearchParam();\n\n    when(commandObjects.geosearch(\"key\", params)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(\"key\", params);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchWithParamsBinary() {\n    byte[] key = \"location\".getBytes();\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM);\n\n    when(commandObjects.geosearch(key, params)).thenReturn(listGeoRadiusResponseCommandObject);\n\n    Response<List<GeoRadiusResponse>> response = pipeliningBase.geosearch(key, params);\n\n    assertThat(commands, contains(listGeoRadiusResponseCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberRadius() {\n    when(commandObjects.geosearchStore(\"dest\", \"src\", \"member\", 100.0, GeoUnit.KM))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase\n        .geosearchStore(\"dest\", \"src\", \"member\", 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberRadiusBinary() {\n    byte[] dest = \"destination\".getBytes();\n    byte[] src = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearchStore(dest, src, member, radius, unit)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(dest, src, member, radius, unit);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordRadius() {\n    GeoCoordinate coord = new GeoCoordinate(15.0, 37.0);\n\n    when(commandObjects.geosearchStore(\"dest\", \"src\", coord, 100.0, GeoUnit.KM))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(\"dest\", \"src\", coord, 100.0, GeoUnit.KM);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordRadiusBinary() {\n    byte[] dest = \"destination\".getBytes();\n    byte[] src = \"location\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(13.361389, 38.115556);\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearchStore(dest, src, coord, radius, unit)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(dest, src, coord, radius, unit);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberBox() {\n    when(commandObjects.geosearchStore(\"dest\", \"src\", \"member\", 50.0, 50.0, GeoUnit.KM))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase\n        .geosearchStore(\"dest\", \"src\", \"member\", 50.0, 50.0, GeoUnit.KM);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberBoxBinary() {\n    byte[] dest = \"destination\".getBytes();\n    byte[] src = \"location\".getBytes();\n    byte[] member = \"Palermo\".getBytes();\n    double width = 200;\n    double height = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearchStore(dest, src, member, width, height, unit)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(dest, src, member, width, height, unit);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordBox() {\n    GeoCoordinate coord = new GeoCoordinate(15.0, 37.0);\n\n    when(commandObjects.geosearchStore(\"dest\", \"src\", coord, 50.0, 50.0, GeoUnit.KM))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase\n        .geosearchStore(\"dest\", \"src\", coord, 50.0, 50.0, GeoUnit.KM);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordBoxBinary() {\n    byte[] dest = \"destination\".getBytes();\n    byte[] src = \"location\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(13.361389, 38.115556);\n    double width = 200;\n    double height = 100;\n    GeoUnit unit = GeoUnit.KM;\n\n    when(commandObjects.geosearchStore(dest, src, coord, width, height, unit)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(dest, src, coord, width, height, unit);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreWithParams() {\n    GeoSearchParam params = new GeoSearchParam();\n\n    when(commandObjects.geosearchStore(\"dest\", \"src\", params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(\"dest\", \"src\", params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreWithParamsBinary() {\n    byte[] dest = \"destination\".getBytes();\n    byte[] src = \"location\".getBytes();\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM);\n\n    when(commandObjects.geosearchStore(dest, src, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStore(dest, src, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreStoreDist() {\n    GeoSearchParam params = new GeoSearchParam();\n\n    when(commandObjects.geosearchStoreStoreDist(\"dest\", \"src\", params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStoreStoreDist(\"dest\", \"src\", params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGeosearchStoreStoreDistBinary() {\n    byte[] dest = \"destination\".getBytes();\n    byte[] src = \"location\".getBytes();\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM);\n\n    when(commandObjects.geosearchStoreStoreDist(dest, src, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.geosearchStoreStoreDist(dest, src, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseHashCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.in;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic class PipeliningBaseHashCommandsTest extends PipeliningBaseMockedTestBase {\n\n  private final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n\n  private final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  private final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  private final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n\n  @Test\n  public void testHdel() {\n    when(commandObjects.hdel(\"key\", \"field1\", \"field2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hdel(\"key\", \"field1\", \"field2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHdelBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field1 = \"field1\".getBytes();\n    byte[] field2 = \"field2\".getBytes();\n\n    when(commandObjects.hdel(key, field1, field2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hdel(key, field1, field2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHexists() {\n    when(commandObjects.hexists(\"key\", \"field\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.hexists(\"key\", \"field\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHexistsBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n\n    when(commandObjects.hexists(key, field)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.hexists(key, field);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHget() {\n    when(commandObjects.hget(\"key\", \"field\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.hget(\"key\", \"field\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHgetBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n\n    when(commandObjects.hget(key, field)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.hget(key, field);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHgetAll() {\n    when(commandObjects.hgetAll(\"key\")).thenReturn(mapStringStringCommandObject);\n\n    Response<Map<String, String>> response = pipeliningBase.hgetAll(\"key\");\n\n    assertThat(commands, contains(mapStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHgetAllBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    when(commandObjects.hgetAll(key)).thenReturn(mapBytesBytesCommandObject);\n\n    Response<Map<byte[], byte[]>> response = pipeliningBase.hgetAll(key);\n\n    assertThat(commands, contains(mapBytesBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHincrBy() {\n    when(commandObjects.hincrBy(\"key\", \"field\", 1L)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hincrBy(\"key\", \"field\", 1L);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHincrByBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    long increment = 2L;\n\n    when(commandObjects.hincrBy(key, field, increment)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hincrBy(key, field, increment);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHincrByFloat() {\n    when(commandObjects.hincrByFloat(\"key\", \"field\", 1.0)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.hincrByFloat(\"key\", \"field\", 1.0);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHincrByFloatBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    double increment = 2.5;\n\n    when(commandObjects.hincrByFloat(key, field, increment)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.hincrByFloat(key, field, increment);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHkeys() {\n    when(commandObjects.hkeys(\"key\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.hkeys(\"key\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHkeysBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    when(commandObjects.hkeys(key)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.hkeys(key);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHlen() {\n    when(commandObjects.hlen(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hlen(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHlenBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    when(commandObjects.hlen(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hlen(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHmget() {\n    when(commandObjects.hmget(\"key\", \"field1\", \"field2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.hmget(\"key\", \"field1\", \"field2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHmgetBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field1 = \"field1\".getBytes();\n    byte[] field2 = \"field2\".getBytes();\n\n    when(commandObjects.hmget(key, field1, field2)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.hmget(key, field1, field2);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHmset() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n\n    when(commandObjects.hmset(\"key\", hash)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.hmset(\"key\", hash);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHmsetBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.hmset(key, hash)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.hmset(key, hash);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHrandfield() {\n    when(commandObjects.hrandfield(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.hrandfield(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHrandfieldBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    when(commandObjects.hrandfield(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.hrandfield(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHrandfieldCount() {\n    long count = 2;\n\n    when(commandObjects.hrandfield(\"key\", count)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.hrandfield(\"key\", count);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHrandfieldCountBinary() {\n    byte[] key = \"hash\".getBytes();\n    long count = 2;\n\n    when(commandObjects.hrandfield(key, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.hrandfield(key, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHrandfieldWithValues() {\n    long count = 2;\n\n    when(commandObjects.hrandfieldWithValues(\"key\", count)).thenReturn(listEntryStringStringCommandObject);\n\n    Response<List<Map.Entry<String, String>>> response = pipeliningBase.hrandfieldWithValues(\"key\", count);\n\n    assertThat(commands, contains(listEntryStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHrandfieldWithValuesBinary() {\n    byte[] key = \"hash\".getBytes();\n    long count = 2;\n\n    when(commandObjects.hrandfieldWithValues(key, count)).thenReturn(listEntryBytesBytesCommandObject);\n\n    Response<List<Map.Entry<byte[], byte[]>>> response = pipeliningBase.hrandfieldWithValues(key, count);\n\n    assertThat(commands, contains(listEntryBytesBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHscan() {\n    String cursor = \"0\";\n    ScanParams params = new ScanParams();\n\n    when(commandObjects.hscan(\"key\", cursor, params)).thenReturn(scanResultEntryStringStringCommandObject);\n\n    Response<ScanResult<Map.Entry<String, String>>> response = pipeliningBase.hscan(\"key\", cursor, params);\n\n    assertThat(commands, contains(scanResultEntryStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHscanBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n\n    when(commandObjects.hscan(key, cursor, params)).thenReturn(scanResultEntryBytesBytesCommandObject);\n\n    Response<ScanResult<Map.Entry<byte[], byte[]>>> response = pipeliningBase.hscan(key, cursor, params);\n\n    assertThat(commands, contains(scanResultEntryBytesBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHscanNoValues() {\n    String cursor = \"0\";\n    ScanParams params = new ScanParams();\n\n    when(commandObjects.hscanNoValues(\"key\", cursor, params)).thenReturn(scanResultStringCommandObject);\n\n    Response<ScanResult<String>> response = pipeliningBase.hscanNoValues(\"key\", cursor, params);\n\n    assertThat(commands, contains(scanResultStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHscanNoValuesBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n\n    when(commandObjects.hscanNoValues(key, cursor, params)).thenReturn(scanResultBytesCommandObject);\n\n    Response<ScanResult<byte[]>> response = pipeliningBase.hscanNoValues(key, cursor, params);\n\n    assertThat(commands, contains(scanResultBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHset() {\n    when(commandObjects.hset(\"key\", \"field\", \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hset(\"key\", \"field\", \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHsetBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    byte[] value = \"value1\".getBytes();\n\n    when(commandObjects.hset(key, field, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hset(key, field, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHsetMap() {\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n\n    when(commandObjects.hset(\"key\", hash)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hset(\"key\", hash);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHsetMapBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.hset(key, hash)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hset(key, hash);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHsetnx() {\n    when(commandObjects.hsetnx(\"key\", \"field\", \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hsetnx(\"key\", \"field\", \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHsetnxBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    byte[] value = \"value1\".getBytes();\n\n    when(commandObjects.hsetnx(key, field, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hsetnx(key, field, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHstrlen() {\n    when(commandObjects.hstrlen(\"key\", \"field\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hstrlen(\"key\", \"field\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHstrlenBinary() {\n    byte[] key = \"hash\".getBytes();\n    byte[] field = \"field1\".getBytes();\n\n    when(commandObjects.hstrlen(key, field)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.hstrlen(key, field);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHvals() {\n    when(commandObjects.hvals(\"key\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.hvals(\"key\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testHvalsBinary() {\n    byte[] key = \"hash\".getBytes();\n\n    when(commandObjects.hvals(key)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.hvals(key);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void hexpire() {\n    String key = \"hash\";\n    long seconds = 100;\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hexpire(key, seconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpire(key, seconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireCondition() {\n    String key = \"hash\";\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hexpire(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpire(key, seconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpire() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpexpire(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpire(key, milliseconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireCondition() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpexpire(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpire(key, milliseconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireAt() {\n    String key = \"hash\";\n    long seconds = 100;\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hexpireAt(key, seconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpireAt(key, seconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireAtCondition() {\n    String key = \"hash\";\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hexpireAt(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpireAt(key, seconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireAt() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpexpireAt(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpireAt(key, milliseconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireAtCondition() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpexpireAt(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpireAt(key, milliseconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireTime() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hexpireTime(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpireTime(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireTime() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpexpireTime(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpireTime(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void httl() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.httl(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.httl(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpttl() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpttl(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpttl(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpersist() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.hpersist(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpersist(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hexpire(key, seconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpire(key, seconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireConditionBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hexpire(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpire(key, seconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpexpire(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpire(key, milliseconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireConditionBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpexpire(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpire(key, milliseconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireAtBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hexpireAt(key, seconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpireAt(key, seconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireAtConditionBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hexpireAt(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpireAt(key, seconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireAtBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpexpireAt(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpireAt(key, milliseconds, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireAtConditionBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpexpireAt(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpireAt(key, milliseconds, condition, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hexpireTimeBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hexpireTime(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hexpireTime(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpexpireTimeBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpexpireTime(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpexpireTime(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void httlBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.httl(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.httl(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpttlBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpttl(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpttl(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n  @Test\n  public void hpersistBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n\n    when(commandObjects.hpersist(key, fields)).thenReturn(listLongCommandObject);\n\n    assertThat(pipeliningBase.hpersist(key, fields), is(predefinedResponse));\n    assertThat(listLongCommandObject, in(commands));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseHyperloglogCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\n\npublic class PipeliningBaseHyperloglogCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testPfadd() {\n    when(commandObjects.pfadd(\"key\", \"element1\", \"element2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pfadd(\"key\", \"element1\", \"element2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfaddBinary() {\n    byte[] key = \"hll\".getBytes();\n    byte[] element1 = \"element1\".getBytes();\n    byte[] element2 = \"element2\".getBytes();\n\n    when(commandObjects.pfadd(key, element1, element2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pfadd(key, element1, element2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfcount() {\n    when(commandObjects.pfcount(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pfcount(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfcountBinary() {\n    byte[] key = \"hll\".getBytes();\n\n    when(commandObjects.pfcount(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pfcount(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfcountMultipleKeys() {\n    when(commandObjects.pfcount(\"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pfcount(\"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfcountMultipleKeysBinary() {\n    byte[] key1 = \"hll1\".getBytes();\n    byte[] key2 = \"hll2\".getBytes();\n\n    when(commandObjects.pfcount(key1, key2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.pfcount(key1, key2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfmerge() {\n    when(commandObjects.pfmerge(\"destkey\", \"sourcekey1\", \"sourcekey2\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.pfmerge(\"destkey\", \"sourcekey1\", \"sourcekey2\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPfmergeBinary() {\n    byte[] destkey = \"hll_dest\".getBytes();\n    byte[] sourcekey1 = \"hll1\".getBytes();\n    byte[] sourcekey2 = \"hll2\".getBytes();\n\n    when(commandObjects.pfmerge(destkey, sourcekey1, sourcekey2)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.pfmerge(destkey, sourcekey1, sourcekey2);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseJsonCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\n\nimport com.google.gson.JsonObject;\nimport org.json.JSONArray;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.Path2;\n\npublic class PipeliningBaseJsonCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testJsonArrAppendWithPath() {\n    Path path = new Path(\"$.array\");\n    Object[] objects = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.jsonArrAppend(\"myJson\", path, objects)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonArrAppend(\"myJson\", path, objects);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrAppendWithPath2() {\n    Path2 path = Path2.of(\"$.array\");\n    Object[] objects = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.jsonArrAppend(\"myJson\", path, objects)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrAppend(\"myJson\", path, objects);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrAppendWithPath2WithEscape() {\n    Path2 path = Path2.of(\"$.array\");\n    Object[] objects = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.jsonArrAppendWithEscape(\"myJson\", path, objects)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrAppendWithEscape(\"myJson\", path, objects);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrIndexWithPath() {\n    Path path = new Path(\"$.array\");\n    Object scalar = \"two\";\n\n    when(commandObjects.jsonArrIndex(\"myJson\", path, scalar)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonArrIndex(\"myJson\", path, scalar);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrIndexWithPath2() {\n    Path2 path = Path2.of(\"$.array\");\n    Object scalar = \"two\";\n\n    when(commandObjects.jsonArrIndex(\"myJson\", path, scalar)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrIndex(\"myJson\", path, scalar);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrIndexWithPath2WithEscape() {\n    Path2 path = Path2.of(\"$.array\");\n    Object scalar = \"two\";\n\n    when(commandObjects.jsonArrIndexWithEscape(\"myJson\", path, scalar)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrIndexWithEscape(\"myJson\", path, scalar);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrInsertWithPath() {\n    Path path = new Path(\"$.array\");\n    Object[] pojos = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.jsonArrInsert(\"myJson\", path, 1, pojos)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonArrInsert(\"myJson\", path, 1, pojos);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrInsertWithPath2() {\n    Path2 path = Path2.of(\"$.array\");\n    Object[] objects = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.jsonArrInsert(\"myJson\", path, 1, objects)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrInsert(\"myJson\", path, 1, objects);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrInsertWithPath2WithEscape() {\n    Path2 path = Path2.of(\"$.array\");\n    Object[] objects = { \"one\", \"two\", \"three\" };\n\n    when(commandObjects.jsonArrInsertWithEscape(\"myJson\", path, 1, objects)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrInsertWithEscape(\"myJson\", path, 1, objects);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrLen() {\n    when(commandObjects.jsonArrLen(\"myJson\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonArrLen(\"myJson\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrLenWithPath() {\n    Path path = new Path(\"$.array\");\n\n    when(commandObjects.jsonArrLen(\"myJson\", path)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonArrLen(\"myJson\", path);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrLenWithPath2() {\n    Path2 path = Path2.of(\"$.array\");\n\n    when(commandObjects.jsonArrLen(\"myJson\", path)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrLen(\"myJson\", path);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPop() {\n    when(commandObjects.jsonArrPop(\"myJson\")).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonArrPop(\"myJson\");\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithPath() {\n    Path path = new Path(\"$.array\");\n\n    when(commandObjects.jsonArrPop(\"myJson\", path)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonArrPop(\"myJson\", path);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithPathAndIndex() {\n    Path path = new Path(\"$.array\");\n\n    when(commandObjects.jsonArrPop(\"myJson\", path, 1)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonArrPop(\"myJson\", path, 1);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithClassAndPath() {\n    Path path = new Path(\"$.array\");\n\n    when(commandObjects.jsonArrPop(\"myJson\", MyBean.class, path)).thenReturn(myBeanCommandObject);\n\n    Response<MyBean> response = pipeliningBase.jsonArrPop(\"myJson\", MyBean.class, path);\n\n    assertThat(commands, contains(myBeanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithClassPathAndIndex() {\n    Path path = new Path(\"$.array\");\n\n    when(commandObjects.jsonArrPop(\"myJson\", MyBean.class, path, 1)).thenReturn(myBeanCommandObject);\n\n    Response<MyBean> response = pipeliningBase.jsonArrPop(\"myJson\", MyBean.class, path, 1);\n\n    assertThat(commands, contains(myBeanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithPath2() {\n    Path2 path = Path2.of(\"$.array\");\n\n    when(commandObjects.jsonArrPop(\"myJson\", path)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.jsonArrPop(\"myJson\", path);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithPath2AndIndex() {\n    Path2 path = Path2.of(\"$.array\");\n\n    when(commandObjects.jsonArrPop(\"myJson\", path, 1)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.jsonArrPop(\"myJson\", path, 1);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrPopWithClass() {\n    when(commandObjects.jsonArrPop(\"myJson\", MyBean.class)).thenReturn(myBeanCommandObject);\n\n    Response<MyBean> response = pipeliningBase.jsonArrPop(\"myJson\", MyBean.class);\n\n    assertThat(commands, contains(myBeanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrTrimWithPath() {\n    Path path = new Path(\"$.array\");\n\n    when(commandObjects.jsonArrTrim(\"myJson\", path, 1, 2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonArrTrim(\"myJson\", path, 1, 2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonArrTrimWithPath2() {\n    Path2 path = Path2.of(\"$.array\");\n\n    when(commandObjects.jsonArrTrim(\"myJson\", path, 1, 2)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonArrTrim(\"myJson\", path, 1, 2);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonClear() {\n    when(commandObjects.jsonClear(\"myJson\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonClear(\"myJson\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonClearWithPath() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonClear(\"myJson\", path)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonClear(\"myJson\", path);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonClearWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonClear(\"myJson\", path)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonClear(\"myJson\", path);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonDel() {\n    when(commandObjects.jsonDel(\"myJson\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonDel(\"myJson\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonDelWithPath() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonDel(\"myJson\", path)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonDel(\"myJson\", path);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonDelWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonDel(\"myJson\", path)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonDel(\"myJson\", path);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonGet() {\n    when(commandObjects.jsonGet(\"myJson\")).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonGet(\"myJson\");\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonGetWithClass() {\n    when(commandObjects.jsonGet(\"myJson\", MyBean.class)).thenReturn(myBeanCommandObject);\n\n    Response<MyBean> response = pipeliningBase.jsonGet(\"myJson\", MyBean.class);\n\n    assertThat(commands, contains(myBeanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonGetWithPath() {\n    Path[] paths = { new Path(\"$.field1\"), new Path(\"$.field2\") };\n\n    when(commandObjects.jsonGet(\"myJson\", paths)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonGet(\"myJson\", paths);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonGetWithPath2() {\n    Path2[] paths = { Path2.of(\"$.field1\"), Path2.of(\"$.field2\") };\n\n    when(commandObjects.jsonGet(\"myJson\", paths)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonGet(\"myJson\", paths);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonGetWithClassAndPath() {\n    Path[] paths = { new Path(\"$.field1\"), new Path(\"$.field2\") };\n\n    when(commandObjects.jsonGet(\"myJson\", MyBean.class, paths)).thenReturn(myBeanCommandObject);\n\n    Response<MyBean> response = pipeliningBase.jsonGet(\"myJson\", MyBean.class, paths);\n\n    assertThat(commands, contains(myBeanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonMergeWithPath() {\n    Path path = new Path(\"$.field\");\n    Object object = new JsonObject();\n\n    when(commandObjects.jsonMerge(\"myJson\", path, object)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonMerge(\"myJson\", path, object);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonMergeWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n    Object object = new JsonObject();\n\n    when(commandObjects.jsonMerge(\"myJson\", path, object)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonMerge(\"myJson\", path, object);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonMGetWithPathAndClass() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonMGet(path, MyBean.class, \"key1\", \"key2\")).thenReturn(listMyBeanCommandObject);\n\n    Response<List<MyBean>> response = pipeliningBase.jsonMGet(path, MyBean.class, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listMyBeanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonMGetWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonMGet(path, \"key1\", \"key2\")).thenReturn(listJsonArrayCommandObject);\n\n    Response<List<JSONArray>> response = pipeliningBase.jsonMGet(path, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listJsonArrayCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonNumIncrByWithPath() {\n    Path path = new Path(\"$.number\");\n\n    when(commandObjects.jsonNumIncrBy(\"myJson\", path, 42.0)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.jsonNumIncrBy(\"myJson\", path, 42.0);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonNumIncrByWithPath2() {\n    Path2 path = Path2.of(\"$.number\");\n\n    when(commandObjects.jsonNumIncrBy(\"myJson\", path, 42.0)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.jsonNumIncrBy(\"myJson\", path, 42.0);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonSetWithPath() {\n    Path path = Path.of(\"$.field\");\n    Object object = new JsonObject();\n\n    when(commandObjects.jsonSet(\"myJson\", path, object)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonSet(\"myJson\", path, object);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonSetWithPathAndParams() {\n    Path path = new Path(\"$.field\");\n    Object object = new JsonObject();\n    JsonSetParams params = new JsonSetParams().nx();\n\n    when(commandObjects.jsonSet(\"myJson\", path, object, params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonSet(\"myJson\", path, object, params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonSetWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n    Object object = new JsonObject();\n\n    when(commandObjects.jsonSet(\"myJson\", path, object)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonSet(\"myJson\", path, object);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonSetWithPath2WithEscape() {\n    Path2 path = Path2.of(\"$.field\");\n    Object object = new JsonObject();\n\n    when(commandObjects.jsonSetWithEscape(\"myJson\", path, object)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonSetWithEscape(\"myJson\", path, object);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonSetWithPath2AndParams() {\n    Path2 path = Path2.of(\"$.field\");\n    Object object = new JsonObject();\n    JsonSetParams params = new JsonSetParams().nx();\n\n    when(commandObjects.jsonSet(\"myJson\", path, object, params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonSet(\"myJson\", path, object, params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonSetWithPath2EscapeAndParams() {\n    Path2 path = Path2.of(\"$.field\");\n    Object object = new JsonObject();\n    JsonSetParams params = new JsonSetParams().nx();\n\n    when(commandObjects.jsonSetWithEscape(\"myJson\", path, object, params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonSetWithEscape(\"myJson\", path, object, params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonStrAppend() {\n    when(commandObjects.jsonStrAppend(\"myJson\", \"append\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonStrAppend(\"myJson\", \"append\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonStrAppendWithPath() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonStrAppend(\"myJson\", path, \"append\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonStrAppend(\"myJson\", path, \"append\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonStrAppendWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonStrAppend(\"myJson\", path, \"append\")).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonStrAppend(\"myJson\", path, \"append\");\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonStrLen() {\n    when(commandObjects.jsonStrLen(\"myJson\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonStrLen(\"myJson\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonStrLenWithPath() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonStrLen(\"myJson\", path)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.jsonStrLen(\"myJson\", path);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonStrLenWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonStrLen(\"myJson\", path)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.jsonStrLen(\"myJson\", path);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonToggleWithPath() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonToggle(\"myJson\", path)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.jsonToggle(\"myJson\", path);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonToggleWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonToggle(\"myJson\", path)).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.jsonToggle(\"myJson\", path);\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonType() {\n    when(commandObjects.jsonType(\"myJson\")).thenReturn(classCommandObject);\n\n    Response<Class<?>> response = pipeliningBase.jsonType(\"myJson\");\n\n    assertThat(commands, contains(classCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonTypeWithPath() {\n    Path path = new Path(\"$.field\");\n\n    when(commandObjects.jsonType(\"myJson\", path)).thenReturn(classCommandObject);\n\n    Response<Class<?>> response = pipeliningBase.jsonType(\"myJson\", path);\n\n    assertThat(commands, contains(classCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testJsonTypeWithPath2() {\n    Path2 path = Path2.of(\"$.field\");\n\n    when(commandObjects.jsonType(\"myJson\", path)).thenReturn(listClassCommandObject);\n\n    Response<List<Class<?>>> response = pipeliningBase.jsonType(\"myJson\", path);\n\n    assertThat(commands, contains(listClassCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetJsonObjectMapper() {\n    JsonObjectMapper jsonObjectMapper = mock(JsonObjectMapper.class);\n    doNothing().when(commandObjects).setJsonObjectMapper(jsonObjectMapper);\n\n    pipeliningBase.setJsonObjectMapper(jsonObjectMapper);\n\n    verify(commandObjects).setJsonObjectMapper(jsonObjectMapper);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseListCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class PipeliningBaseListCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testBlmove() {\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n    double timeout = 1.0;\n\n    when(commandObjects.blmove(\"srcKey\", \"dstKey\", from, to, timeout)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.blmove(\"srcKey\", \"dstKey\", from, to, timeout);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlmoveBinary() {\n    byte[] srcKey = \"srcKey\".getBytes();\n    byte[] dstKey = \"dstKey\".getBytes();\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n    double timeout = 10.5;\n\n    when(commandObjects.blmove(srcKey, dstKey, from, to, timeout)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.blmove(srcKey, dstKey, from, to, timeout);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlmpop() {\n    double timeout = 1.0;\n    ListDirection direction = ListDirection.LEFT;\n\n    when(commandObjects.blmpop(timeout, direction, \"key1\", \"key2\")).thenReturn(keyValueStringListStringCommandObject);\n\n    Response<KeyValue<String, List<String>>> response = pipeliningBase.blmpop(timeout, direction, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlmpopBinary() {\n    double timeout = 10.5;\n    ListDirection direction = ListDirection.LEFT;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.blmpop(timeout, direction, key1, key2)).thenReturn(keyValueBytesListBytesCommandObject);\n\n    Response<KeyValue<byte[], List<byte[]>>> response = pipeliningBase.blmpop(timeout, direction, key1, key2);\n\n    assertThat(commands, contains(keyValueBytesListBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlmpopCount() {\n    double timeout = 1.0;\n    ListDirection direction = ListDirection.LEFT;\n    int count = 2;\n\n    when(commandObjects.blmpop(timeout, direction, count, \"key1\", \"key2\")).thenReturn(keyValueStringListStringCommandObject);\n\n    Response<KeyValue<String, List<String>>> response = pipeliningBase.blmpop(timeout, direction, count, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlmpopCountBinary() {\n    double timeout = 10.5;\n    ListDirection direction = ListDirection.LEFT;\n    int count = 2;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.blmpop(timeout, direction, count, key1, key2)).thenReturn(keyValueBytesListBytesCommandObject);\n\n    Response<KeyValue<byte[], List<byte[]>>> response = pipeliningBase.blmpop(timeout, direction, count, key1, key2);\n\n    assertThat(commands, contains(keyValueBytesListBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlpop() {\n    when(commandObjects.blpop(30, \"key\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.blpop(30, \"key\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlpopBinary() {\n    int timeout = 10;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.blpop(timeout, key1, key2)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.blpop(timeout, key1, key2);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlpopDoubleTimeout() {\n    when(commandObjects.blpop(30.0, \"key\")).thenReturn(keyValueStringStringCommandObject);\n\n    Response<KeyValue<String, String>> response = pipeliningBase.blpop(30.0, \"key\");\n\n    assertThat(commands, contains(keyValueStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlpopDoubleTimeoutBinary() {\n    double timeout = 10.5;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.blpop(timeout, key1, key2)).thenReturn(keyValueBytesBytesCommandObject);\n\n    Response<KeyValue<byte[], byte[]>> response = pipeliningBase.blpop(timeout, key1, key2);\n\n    assertThat(commands, contains(keyValueBytesBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlpopMultipleKeys() {\n    when(commandObjects.blpop(30, \"key1\", \"key2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.blpop(30, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBlpopMultipleKeysDoubleTimeout() {\n    when(commandObjects.blpop(30.0, \"key1\", \"key2\")).thenReturn(keyValueStringStringCommandObject);\n\n    Response<KeyValue<String, String>> response = pipeliningBase.blpop(30.0, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpop() {\n    when(commandObjects.brpop(30, \"key\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.brpop(30, \"key\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpopBinary() {\n    int timeout = 10;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.brpop(timeout, key1, key2)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.brpop(timeout, key1, key2);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpopDoubleTimeout() {\n    when(commandObjects.brpop(30.0, \"key\")).thenReturn(keyValueStringStringCommandObject);\n\n    Response<KeyValue<String, String>> response = pipeliningBase.brpop(30.0, \"key\");\n\n    assertThat(commands, contains(keyValueStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpopDoubleTimeoutBinary() {\n    double timeout = 10.5;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.brpop(timeout, key1, key2)).thenReturn(keyValueBytesBytesCommandObject);\n\n    Response<KeyValue<byte[], byte[]>> response = pipeliningBase.brpop(timeout, key1, key2);\n\n    assertThat(commands, contains(keyValueBytesBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpopMultipleKeys() {\n    when(commandObjects.brpop(30, \"key1\", \"key2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.brpop(30, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpopMultipleKeysDoubleTimeout() {\n    when(commandObjects.brpop(30.0, \"key1\", \"key2\")).thenReturn(keyValueStringStringCommandObject);\n\n    Response<KeyValue<String, String>> response = pipeliningBase.brpop(30.0, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpoplpush() {\n    when(commandObjects.brpoplpush(\"source\", \"destination\", 30)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.brpoplpush(\"source\", \"destination\", 30);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBrpoplpushBinary() {\n    byte[] source = \"source\".getBytes();\n    byte[] destination = \"destination\".getBytes();\n    int timeout = 10;\n\n    when(commandObjects.brpoplpush(source, destination, timeout)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.brpoplpush(source, destination, timeout);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLindex() {\n    when(commandObjects.lindex(\"key\", 1)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.lindex(\"key\", 1);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLindexBinary() {\n    byte[] key = \"key\".getBytes();\n    long index = 0;\n\n    when(commandObjects.lindex(key, index)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.lindex(key, index);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLinsert() {\n    ListPosition where = ListPosition.BEFORE;\n\n    when(commandObjects.linsert(\"key\", where, \"pivot\", \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.linsert(\"key\", where, \"pivot\", \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLinsertBinary() {\n    byte[] key = \"key\".getBytes();\n    ListPosition where = ListPosition.BEFORE;\n    byte[] pivot = \"pivot\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.linsert(key, where, pivot, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.linsert(key, where, pivot, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLlen() {\n    when(commandObjects.llen(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.llen(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLlenBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.llen(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.llen(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLmove() {\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n\n    when(commandObjects.lmove(\"srcKey\", \"dstKey\", from, to)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.lmove(\"srcKey\", \"dstKey\", from, to);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLmoveBinary() {\n    byte[] srcKey = \"srcKey\".getBytes();\n    byte[] dstKey = \"dstKey\".getBytes();\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n\n    when(commandObjects.lmove(srcKey, dstKey, from, to)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.lmove(srcKey, dstKey, from, to);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLmpop() {\n    ListDirection direction = ListDirection.LEFT;\n\n    when(commandObjects.lmpop(direction, \"key1\", \"key2\")).thenReturn(keyValueStringListStringCommandObject);\n\n    Response<KeyValue<String, List<String>>> response = pipeliningBase.lmpop(direction, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLmpopBinary() {\n    ListDirection direction = ListDirection.LEFT;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.lmpop(direction, key1, key2)).thenReturn(keyValueBytesListBytesCommandObject);\n\n    Response<KeyValue<byte[], List<byte[]>>> response = pipeliningBase.lmpop(direction, key1, key2);\n\n    assertThat(commands, contains(keyValueBytesListBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLmpopCount() {\n    ListDirection direction = ListDirection.LEFT;\n    int count = 2;\n\n    when(commandObjects.lmpop(direction, count, \"key1\", \"key2\")).thenReturn(keyValueStringListStringCommandObject);\n\n    Response<KeyValue<String, List<String>>> response = pipeliningBase.lmpop(direction, count, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLmpopCountBinary() {\n    ListDirection direction = ListDirection.LEFT;\n    int count = 2;\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.lmpop(direction, count, key1, key2)).thenReturn(keyValueBytesListBytesCommandObject);\n\n    Response<KeyValue<byte[], List<byte[]>>> response = pipeliningBase.lmpop(direction, count, key1, key2);\n\n    assertThat(commands, contains(keyValueBytesListBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpop() {\n    when(commandObjects.lpop(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.lpop(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpopBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.lpop(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.lpop(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpopCount() {\n    when(commandObjects.lpop(\"key\", 2)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.lpop(\"key\", 2);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpopCountBinary() {\n    byte[] key = \"key\".getBytes();\n    int count = 2;\n\n    when(commandObjects.lpop(key, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.lpop(key, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpos() {\n    when(commandObjects.lpos(\"key\", \"element\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpos(\"key\", \"element\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLposBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] element = \"element\".getBytes();\n\n    when(commandObjects.lpos(key, element)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpos(key, element);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLposWithParams() {\n    LPosParams params = new LPosParams();\n\n    when(commandObjects.lpos(\"key\", \"element\", params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpos(\"key\", \"element\", params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLposWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] element = \"element\".getBytes();\n    LPosParams params = new LPosParams().rank(1);\n\n    when(commandObjects.lpos(key, element, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpos(key, element, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLposWithParamsCount() {\n    LPosParams params = new LPosParams();\n\n    when(commandObjects.lpos(\"key\", \"element\", params, 3)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.lpos(\"key\", \"element\", params, 3);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLposWithParamsCountBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] element = \"element\".getBytes();\n    LPosParams params = new LPosParams().rank(1);\n    long count = 2;\n\n    when(commandObjects.lpos(key, element, params, count)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.lpos(key, element, params, count);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpush() {\n    when(commandObjects.lpush(\"key\", \"value1\", \"value2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpush(\"key\", \"value1\", \"value2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpushBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] arg1 = \"value1\".getBytes();\n    byte[] arg2 = \"value2\".getBytes();\n\n    when(commandObjects.lpush(key, arg1, arg2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpush(key, arg1, arg2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpushx() {\n    when(commandObjects.lpushx(\"key\", \"value1\", \"value2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpushx(\"key\", \"value1\", \"value2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLpushxBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] arg = \"value\".getBytes();\n\n    when(commandObjects.lpushx(key, arg)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lpushx(key, arg);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLrange() {\n    when(commandObjects.lrange(\"key\", 0, -1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.lrange(\"key\", 0, -1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLrangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long start = 0;\n    long stop = -1;\n\n    when(commandObjects.lrange(key, start, stop)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.lrange(key, start, stop);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLrem() {\n    when(commandObjects.lrem(\"key\", 2, \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lrem(\"key\", 2, \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLremBinary() {\n    byte[] key = \"key\".getBytes();\n    long count = 1;\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.lrem(key, count, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.lrem(key, count, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLset() {\n    when(commandObjects.lset(\"key\", 1, \"value\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.lset(\"key\", 1, \"value\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLsetBinary() {\n    byte[] key = \"key\".getBytes();\n    long index = 0;\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.lset(key, index, value)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.lset(key, index, value);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLtrim() {\n    when(commandObjects.ltrim(\"key\", 1, -1)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ltrim(\"key\", 1, -1);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLtrimBinary() {\n    byte[] key = \"key\".getBytes();\n    long start = 1;\n    long stop = -1;\n\n    when(commandObjects.ltrim(key, start, stop)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ltrim(key, start, stop);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpop() {\n    when(commandObjects.rpop(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.rpop(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpopBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.rpop(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.rpop(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpopCount() {\n    when(commandObjects.rpop(\"key\", 2)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.rpop(\"key\", 2);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpopCountBinary() {\n    byte[] key = \"key\".getBytes();\n    int count = 2;\n\n    when(commandObjects.rpop(key, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.rpop(key, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpoplpush() {\n    when(commandObjects.rpoplpush(\"srcKey\", \"dstKey\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.rpoplpush(\"srcKey\", \"dstKey\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpoplpushBinary() {\n    byte[] srckey = \"srckey\".getBytes();\n    byte[] dstkey = \"dstkey\".getBytes();\n\n    when(commandObjects.rpoplpush(srckey, dstkey)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.rpoplpush(srckey, dstkey);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpush() {\n    when(commandObjects.rpush(\"key\", \"value1\", \"value2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.rpush(\"key\", \"value1\", \"value2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpushBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] arg1 = \"value1\".getBytes();\n    byte[] arg2 = \"value2\".getBytes();\n\n    when(commandObjects.rpush(key, arg1, arg2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.rpush(key, arg1, arg2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpushx() {\n    when(commandObjects.rpushx(\"key\", \"value1\", \"value2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.rpushx(\"key\", \"value1\", \"value2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testRpushxBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] arg = \"value\".getBytes();\n\n    when(commandObjects.rpushx(key, arg)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.rpushx(key, arg);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseMiscellaneousTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\n\n/**\n * {@link redis.clients.jedis.PipeliningBase} tests that don't really fall into any category of commands.\n */\npublic class PipeliningBaseMiscellaneousTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testSendCommandWithStringArgs() {\n    ProtocolCommand cmd = Protocol.Command.GET;\n    String arg1 = \"key1\";\n    String arg2 = \"key2\";\n\n    Response<Object> response = pipeliningBase.sendCommand(cmd, arg1, arg2);\n\n    assertThat(commands, hasSize(1));\n\n    List<Rawable> arguments = new ArrayList<>();\n    commands.get(0).getArguments().forEach(arguments::add);\n\n    assertThat(arguments.stream().map(Rawable::getRaw).collect(Collectors.toList()), contains(\n        Protocol.Command.GET.getRaw(),\n        arg1.getBytes(),\n        arg2.getBytes()\n    ));\n\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSendCommandWithByteArgs() {\n    ProtocolCommand cmd = Protocol.Command.SET;\n    byte[] arg1 = \"key1\".getBytes();\n    byte[] arg2 = \"value1\".getBytes();\n\n    Response<Object> response = pipeliningBase.sendCommand(cmd, arg1, arg2);\n\n    assertThat(commands, hasSize(1));\n\n    List<Rawable> arguments = new ArrayList<>();\n    commands.get(0).getArguments().forEach(arguments::add);\n\n    assertThat(arguments.stream().map(Rawable::getRaw).collect(Collectors.toList()), contains(\n        Protocol.Command.SET.getRaw(),\n        arg1,\n        arg2\n    ));\n\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testExecuteCommand() {\n    CommandArguments commandArguments = new CommandArguments(Protocol.Command.GET).key(\"key1\");\n    CommandObject<String> commandObject = new CommandObject<>(commandArguments, BuilderFactory.STRING);\n\n    Response<String> response = pipeliningBase.executeCommand(commandObject);\n\n    assertThat(commands, contains(commandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMultipleCommands() {\n    when(commandObjects.exists(\"key1\")).thenReturn(booleanCommandObject);\n    when(commandObjects.exists(\"key2\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> result1 = pipeliningBase.exists(\"key1\");\n    Response<Boolean> result2 = pipeliningBase.exists(\"key2\");\n\n    assertThat(commands, contains(\n        booleanCommandObject,\n        booleanCommandObject\n    ));\n\n    assertThat(result1, is(predefinedResponse));\n    assertThat(result2, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseMockedTestBase.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.mockito.Mock;\n\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.CommandObjects;\nimport redis.clients.jedis.PipeliningBase;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.mocked.MockedCommandObjectsTestBase;\n\n/**\n * Base class for unit tests for {@link PipeliningBase}, using Mockito. Given that {@link PipeliningBase}\n * is, essentially, only requesting commands from a {@link CommandObjects} instance and sending them\n * to its subclasses, and given that it has many methods, using mocks is the most convenient and\n * reliable way to completely test it.\n */\npublic abstract class PipeliningBaseMockedTestBase extends MockedCommandObjectsTestBase {\n\n  /**\n   * A concrete implementation of {@link PipeliningBase} that collects all commands\n   * in a list (so that asserts can be run on the content of the list), and always returns a\n   * predefined response (so that the response can be asserted).\n   */\n  private static class TestPipeliningBase extends PipeliningBase {\n\n    private final Response<?> predefinedResponse;\n    private final List<CommandObject<?>> commands;\n\n    public TestPipeliningBase(CommandObjects commandObjects,\n                              Response<?> predefinedResponse,\n                              List<CommandObject<?>> commands) {\n      super(commandObjects);\n      this.predefinedResponse = predefinedResponse;\n      this.commands = commands;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    protected <T> Response<T> appendCommand(CommandObject<T> commandObject) {\n      // Collect the command in the list.\n      commands.add(commandObject);\n      // Return a well known response, that can be asserted in the test cases.\n      return (Response<T>) predefinedResponse;\n    }\n  }\n\n  /**\n   * {@link PipeliningBase} under-test. Given that it is an abstract class, an in-place implementation\n   * is used, that collects commands in a list.\n   */\n  protected PipeliningBase pipeliningBase;\n\n  /**\n   * Accumulates commands sent by the {@link PipeliningBase} under-test to its subclass.\n   */\n  protected final List<CommandObject<?>> commands = new ArrayList<>();\n\n  /**\n   * {@link CommandObjects} instance used by the {@link PipeliningBase} under-test. Depending on\n   * the test case, it is trained to return one of the mock {@link CommandObject} instances below.\n   */\n  @Mock\n  protected CommandObjects commandObjects;\n\n  /**\n   * Mock {@link Response} that is returned by {@link PipeliningBase} from each method.\n   */\n  @Mock\n  protected Response<?> predefinedResponse;\n\n  @BeforeEach\n  public void setUp() {\n    pipeliningBase = new TestPipeliningBase(commandObjects, predefinedResponse, commands);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseScriptingAndFunctionsCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\n\npublic class PipeliningBaseScriptingAndFunctionsCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testEval() {\n    String script = \"return 'Hello, world!'\";\n    when(commandObjects.eval(script)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalBinary() {\n    byte[] script = \"return 'Hello, world!'\".getBytes();\n\n    when(commandObjects.eval(script)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalWithParams() {\n    String script = \"return KEYS[1] .. ARGV[1]\";\n    int keyCount = 1;\n\n    when(commandObjects.eval(script, keyCount, \"key\", \"arg\")).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script, keyCount, \"key\", \"arg\");\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalWithParamsBinary() {\n    byte[] script = \"return KEYS[1]\".getBytes();\n    int keyCount = 1;\n    byte[] param1 = \"key1\".getBytes();\n\n    when(commandObjects.eval(script, keyCount, param1)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script, keyCount, param1);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalWithLists() {\n    String script = \"return KEYS[1] .. ARGV[1]\";\n    List<String> keys = Collections.singletonList(\"key\");\n    List<String> args = Collections.singletonList(\"arg\");\n\n    when(commandObjects.eval(script, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalWithListsBinary() {\n    byte[] script = \"return {KEYS[1], ARGV[1]}\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg1\".getBytes());\n\n    when(commandObjects.eval(script, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalWithSampleKey() {\n    String script = \"return 'Hello, world!'\";\n\n    when(commandObjects.eval(script, \"key\")).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script, \"key\");\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalWithSampleKeyBinary() {\n    byte[] script = \"return 'Hello, world!'\".getBytes();\n    byte[] sampleKey = \"sampleKey\".getBytes();\n\n    when(commandObjects.eval(script, sampleKey)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.eval(script, sampleKey);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalReadonly() {\n    String script = \"return KEYS[1] .. ARGV[1]\";\n    List<String> keys = Collections.singletonList(\"key\");\n    List<String> args = Collections.singletonList(\"arg\");\n\n    when(commandObjects.evalReadonly(script, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalReadonly(script, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalReadonlyBinary() {\n    byte[] script = \"return {KEYS[1], ARGV[1]}\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg1\".getBytes());\n\n    when(commandObjects.evalReadonly(script, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalReadonly(script, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalsha() {\n    String sha1 = \"somehash\";\n\n    when(commandObjects.evalsha(sha1)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaBinary() {\n    byte[] sha1 = \"abcdef1234567890\".getBytes();\n\n    when(commandObjects.evalsha(sha1)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaWithParams() {\n    String sha1 = \"somehash\";\n    int keyCount = 1;\n\n    when(commandObjects.evalsha(sha1, keyCount, \"key\", \"arg\")).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1, keyCount, \"key\", \"arg\");\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaWithParamsBinary() {\n    byte[] sha1 = \"abcdef1234567890\".getBytes();\n    int keyCount = 1;\n    byte[] param1 = \"key1\".getBytes();\n\n    when(commandObjects.evalsha(sha1, keyCount, param1)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1, keyCount, param1);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaWithLists() {\n    String sha1 = \"somehash\";\n    List<String> keys = Collections.singletonList(\"key\");\n    List<String> args = Collections.singletonList(\"arg\");\n\n    when(commandObjects.evalsha(sha1, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaWithListsBinary() {\n    byte[] sha1 = \"abcdef1234567890\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg1\".getBytes());\n\n    when(commandObjects.evalsha(sha1, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaWithSampleKey() {\n    String sha1 = \"somehash\";\n\n    when(commandObjects.evalsha(sha1, \"key\")).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1, \"key\");\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaWithSampleKeyBinary() {\n    byte[] sha1 = \"abcdef1234567890\".getBytes();\n    byte[] sampleKey = \"sampleKey\".getBytes();\n\n    when(commandObjects.evalsha(sha1, sampleKey)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalsha(sha1, sampleKey);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaReadonly() {\n    String sha1 = \"somehash\";\n    List<String> keys = Collections.singletonList(\"key\");\n    List<String> args = Collections.singletonList(\"arg\");\n\n    when(commandObjects.evalshaReadonly(sha1, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalshaReadonly(sha1, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testEvalshaReadonlyBinary() {\n    byte[] sha1 = \"abcdef1234567890\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg1\".getBytes());\n\n    when(commandObjects.evalshaReadonly(sha1, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.evalshaReadonly(sha1, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFcall() {\n    String name = \"functionName\";\n    List<String> keys = Collections.singletonList(\"key\");\n    List<String> args = Collections.singletonList(\"arg\");\n\n    when(commandObjects.fcall(name, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.fcall(name, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFcallBinary() {\n    byte[] name = \"functionName\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg\".getBytes());\n\n    when(commandObjects.fcall(name, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.fcall(name, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFcallReadonly() {\n    String name = \"functionName\";\n    List<String> keys = Collections.singletonList(\"key\");\n    List<String> args = Collections.singletonList(\"arg\");\n\n    when(commandObjects.fcallReadonly(name, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.fcallReadonly(name, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFcallReadonlyBinary() {\n    byte[] name = \"functionName\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg\".getBytes());\n\n    when(commandObjects.fcallReadonly(name, keys, args)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.fcallReadonly(name, keys, args);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionDelete() {\n    String libraryName = \"libraryName\";\n\n    when(commandObjects.functionDelete(libraryName)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionDelete(libraryName);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionDeleteBinary() {\n    byte[] libraryName = \"libraryName\".getBytes();\n\n    when(commandObjects.functionDelete(libraryName)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionDelete(libraryName);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionDump() {\n    when(commandObjects.functionDump()).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.functionDump();\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionFlush() {\n    when(commandObjects.functionFlush()).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionFlush();\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionFlushWithMode() {\n    FlushMode mode = FlushMode.SYNC;\n\n    when(commandObjects.functionFlush(mode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionFlush(mode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionKill() {\n    when(commandObjects.functionKill()).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionKill();\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionList() {\n    when(commandObjects.functionList()).thenReturn(listLibraryInfoCommandObject);\n\n    Response<List<LibraryInfo>> response = pipeliningBase.functionList();\n\n    assertThat(commands, contains(listLibraryInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListBinary() {\n    when(commandObjects.functionListBinary()).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.functionListBinary();\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListWithPattern() {\n    String libraryNamePattern = \"lib*\";\n\n    when(commandObjects.functionList(libraryNamePattern)).thenReturn(listLibraryInfoCommandObject);\n\n    Response<List<LibraryInfo>> response = pipeliningBase.functionList(libraryNamePattern);\n\n    assertThat(commands, contains(listLibraryInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListWithPatternBinary() {\n    byte[] libraryNamePattern = \"lib*\".getBytes();\n\n    when(commandObjects.functionList(libraryNamePattern)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.functionList(libraryNamePattern);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListWithCode() {\n    when(commandObjects.functionListWithCode()).thenReturn(listLibraryInfoCommandObject);\n\n    Response<List<LibraryInfo>> response = pipeliningBase.functionListWithCode();\n\n    assertThat(commands, contains(listLibraryInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListWithCodeBinary() {\n    when(commandObjects.functionListWithCodeBinary()).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.functionListWithCodeBinary();\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListWithCodeAndPattern() {\n    String libraryNamePattern = \"lib*\";\n\n    when(commandObjects.functionListWithCode(libraryNamePattern)).thenReturn(listLibraryInfoCommandObject);\n\n    Response<List<LibraryInfo>> response = pipeliningBase.functionListWithCode(libraryNamePattern);\n\n    assertThat(commands, contains(listLibraryInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionListWithCodeAndPatternBinary() {\n    byte[] libraryNamePattern = \"lib*\".getBytes();\n\n    when(commandObjects.functionListWithCode(libraryNamePattern)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.functionListWithCode(libraryNamePattern);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionLoad() {\n    String functionCode = \"return 'Hello, world!'\";\n\n    when(commandObjects.functionLoad(functionCode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionLoad(functionCode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionLoadBinary() {\n    byte[] functionCode = \"return 'Hello, world!'\".getBytes();\n\n    when(commandObjects.functionLoad(functionCode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionLoad(functionCode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionLoadReplace() {\n    String functionCode = \"return 'Hello, world!'\";\n\n    when(commandObjects.functionLoadReplace(functionCode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionLoadReplace(functionCode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionLoadReplaceBinary() {\n    byte[] functionCode = \"return 'Hello, world!'\".getBytes();\n\n    when(commandObjects.functionLoadReplace(functionCode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionLoadReplace(functionCode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionRestore() {\n    byte[] serializedValue = \"serialized\".getBytes();\n\n    when(commandObjects.functionRestore(serializedValue)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionRestore(serializedValue);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionRestoreWithPolicy() {\n    byte[] serializedValue = \"serialized\".getBytes();\n    FunctionRestorePolicy policy = FunctionRestorePolicy.FLUSH;\n\n    when(commandObjects.functionRestore(serializedValue, policy)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.functionRestore(serializedValue, policy);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionStats() {\n    when(commandObjects.functionStats()).thenReturn(functionStatsCommandObject);\n\n    Response<FunctionStats> response = pipeliningBase.functionStats();\n\n    assertThat(commands, contains(functionStatsCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFunctionStatsBinary() {\n    when(commandObjects.functionStatsBinary()).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.functionStatsBinary();\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptExistsWithKeyAndSha1s() {\n    String[] sha1 = { \"somehash1\", \"somehash2\" };\n\n    when(commandObjects.scriptExists(\"key\", sha1)).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.scriptExists(\"key\", sha1);\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptExistsWithKeyAndSha1sBinary() {\n    byte[] sampleKey = \"sampleKey\".getBytes();\n    byte[] sha1 = \"abcdef1234567890\".getBytes();\n\n    when(commandObjects.scriptExists(sampleKey, sha1)).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.scriptExists(sampleKey, sha1);\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptFlush() {\n    when(commandObjects.scriptFlush(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptFlush(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptFlushBinary() {\n    byte[] sampleKey = \"sampleKey\".getBytes();\n\n    when(commandObjects.scriptFlush(sampleKey)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptFlush(sampleKey);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptFlushWithMode() {\n    FlushMode flushMode = FlushMode.SYNC;\n\n    when(commandObjects.scriptFlush(\"key\", flushMode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptFlush(\"key\", flushMode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptFlushWithModeBinary() {\n    byte[] sampleKey = \"sampleKey\".getBytes();\n    FlushMode flushMode = FlushMode.SYNC;\n\n    when(commandObjects.scriptFlush(sampleKey, flushMode)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptFlush(sampleKey, flushMode);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptKill() {\n    when(commandObjects.scriptKill(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptKill(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptKillBinary() {\n    byte[] sampleKey = \"sampleKey\".getBytes();\n\n    when(commandObjects.scriptKill(sampleKey)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptKill(sampleKey);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptLoad() {\n    String script = \"return 'Hello, world!'\";\n\n    when(commandObjects.scriptLoad(script, \"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.scriptLoad(script, \"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScriptLoadBinary() {\n    byte[] script = \"return 'Hello, world!'\".getBytes();\n    byte[] sampleKey = \"sampleKey\".getBytes();\n\n    when(commandObjects.scriptLoad(script, sampleKey)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.scriptLoad(script, sampleKey);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseSearchAndQueryCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.search.FTCreateParams;\nimport redis.clients.jedis.search.FTSearchParams;\nimport redis.clients.jedis.search.FTSpellCheckParams;\nimport redis.clients.jedis.search.IndexOptions;\nimport redis.clients.jedis.search.Query;\nimport redis.clients.jedis.search.Schema;\nimport redis.clients.jedis.search.SearchResult;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.schemafields.SchemaField;\nimport redis.clients.jedis.search.schemafields.TextField;\n\npublic class PipeliningBaseSearchAndQueryCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testFtAggregate() {\n    AggregationBuilder aggr = new AggregationBuilder().groupBy(\"@field\");\n\n    when(commandObjects.ftAggregate(\"myIndex\", aggr)).thenReturn(aggregationResultCommandObject);\n\n    Response<AggregationResult> response = pipeliningBase.ftAggregate(\"myIndex\", aggr);\n\n    assertThat(commands, contains(aggregationResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtAliasAdd() {\n    when(commandObjects.ftAliasAdd(\"myAlias\", \"myIndex\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftAliasAdd(\"myAlias\", \"myIndex\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtAliasDel() {\n    when(commandObjects.ftAliasDel(\"myAlias\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftAliasDel(\"myAlias\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtAliasUpdate() {\n    when(commandObjects.ftAliasUpdate(\"myAlias\", \"myIndex\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftAliasUpdate(\"myAlias\", \"myIndex\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtAlterWithSchema() {\n    Schema schema = new Schema().addField(new Schema.Field(\"newField\", Schema.FieldType.TEXT));\n\n    when(commandObjects.ftAlter(\"myIndex\", schema)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftAlter(\"myIndex\", schema);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtAlterWithSchemaFields() {\n    Iterable<SchemaField> schemaFields = Collections.singletonList(new TextField(\"newField\"));\n\n    when(commandObjects.ftAlter(\"myIndex\", schemaFields)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftAlter(\"myIndex\", schemaFields);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtConfigGet() {\n    when(commandObjects.ftConfigGet(\"TIMEOUT\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.ftConfigGet(\"TIMEOUT\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtConfigGetWithIndexName() {\n    when(commandObjects.ftConfigGet(\"myIndex\", \"TIMEOUT\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.ftConfigGet(\"myIndex\", \"TIMEOUT\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtConfigSet() {\n    when(commandObjects.ftConfigSet(\"TIMEOUT\", \"100\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftConfigSet(\"TIMEOUT\", \"100\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtConfigSetWithIndexName() {\n    when(commandObjects.ftConfigSet(\"myIndex\", \"TIMEOUT\", \"100\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftConfigSet(\"myIndex\", \"TIMEOUT\", \"100\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtCreateWithOptionsAndSchema() {\n    IndexOptions indexOptions = IndexOptions.defaultOptions();\n    Schema schema = new Schema().addField(new Schema.Field(\"myField\", Schema.FieldType.TEXT));\n\n    when(commandObjects.ftCreate(\"myIndex\", indexOptions, schema)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftCreate(\"myIndex\", indexOptions, schema);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtCreateWithCreateParamsAndSchemaFields() {\n    FTCreateParams createParams = FTCreateParams.createParams();\n    Iterable<SchemaField> schemaFields = Collections.singletonList(new TextField(\"myField\"));\n\n    when(commandObjects.ftCreate(\"myIndex\", createParams, schemaFields)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftCreate(\"myIndex\", createParams, schemaFields);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDictAdd() {\n    String[] terms = { \"term1\", \"term2\" };\n\n    when(commandObjects.ftDictAdd(\"myDict\", terms)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftDictAdd(\"myDict\", terms);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDictAddBySampleKey() {\n    String[] terms = { \"term1\", \"term2\" };\n\n    when(commandObjects.ftDictAddBySampleKey(\"myIndex\", \"myDict\", terms)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftDictAddBySampleKey(\"myIndex\", \"myDict\", terms);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDictDel() {\n    String[] terms = { \"term1\", \"term2\" };\n\n    when(commandObjects.ftDictDel(\"myDict\", terms)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftDictDel(\"myDict\", terms);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDictDelBySampleKey() {\n    String[] terms = { \"term1\", \"term2\" };\n\n    when(commandObjects.ftDictDelBySampleKey(\"myIndex\", \"myDict\", terms)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftDictDelBySampleKey(\"myIndex\", \"myDict\", terms);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDictDump() {\n    when(commandObjects.ftDictDump(\"myDict\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.ftDictDump(\"myDict\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDictDumpBySampleKey() {\n    when(commandObjects.ftDictDumpBySampleKey(\"myIndex\", \"myDict\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.ftDictDumpBySampleKey(\"myIndex\", \"myDict\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDropIndex() {\n    when(commandObjects.ftDropIndex(\"myIndex\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftDropIndex(\"myIndex\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtDropIndexDD() {\n    when(commandObjects.ftDropIndexDD(\"myIndex\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftDropIndexDD(\"myIndex\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtExplain() {\n    Query query = new Query(\"hello world\");\n\n    when(commandObjects.ftExplain(\"myIndex\", query)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftExplain(\"myIndex\", query);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtExplainCLI() {\n    Query query = new Query(\"hello world\");\n\n    when(commandObjects.ftExplainCLI(\"myIndex\", query)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.ftExplainCLI(\"myIndex\", query);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtInfo() {\n    when(commandObjects.ftInfo(\"myIndex\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.ftInfo(\"myIndex\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSearch() {\n    String query = \"hello world\";\n\n    when(commandObjects.ftSearch(\"myIndex\", query)).thenReturn(searchResultCommandObject);\n\n    Response<SearchResult> response = pipeliningBase.ftSearch(\"myIndex\", query);\n\n    assertThat(commands, contains(searchResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSearchWithParams() {\n    String query = \"hello world\";\n    FTSearchParams searchParams = FTSearchParams.searchParams().limit(0, 10);\n\n    when(commandObjects.ftSearch(\"myIndex\", query, searchParams)).thenReturn(searchResultCommandObject);\n\n    Response<SearchResult> response = pipeliningBase.ftSearch(\"myIndex\", query, searchParams);\n\n    assertThat(commands, contains(searchResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSearchWithQueryObject() {\n    Query query = new Query(\"hello world\").limit(0, 10);\n\n    when(commandObjects.ftSearch(\"myIndex\", query)).thenReturn(searchResultCommandObject);\n\n    Response<SearchResult> response = pipeliningBase.ftSearch(\"myIndex\", query);\n\n    assertThat(commands, contains(searchResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSearchWithQueryObjectBinary() {\n    byte[] indexName = \"myIndex\".getBytes();\n    Query query = new Query(\"hello world\").limit(0, 10);\n\n    when(commandObjects.ftSearch(indexName, query)).thenReturn(searchResultCommandObject);\n\n    Response<SearchResult> response = pipeliningBase.ftSearch(indexName, query);\n\n    assertThat(commands, contains(searchResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSpellCheck() {\n    String query = \"hello world\";\n\n    when(commandObjects.ftSpellCheck(\"myIndex\", query)).thenReturn(mapStringMapStringDoubleCommandObject);\n\n    Response<Map<String, Map<String, Double>>> response = pipeliningBase.ftSpellCheck(\"myIndex\", query);\n\n    assertThat(commands, contains(mapStringMapStringDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSpellCheckWithParams() {\n    String query = \"hello world\";\n    FTSpellCheckParams spellCheckParams = new FTSpellCheckParams().distance(1);\n\n    when(commandObjects.ftSpellCheck(\"myIndex\", query, spellCheckParams)).thenReturn(mapStringMapStringDoubleCommandObject);\n\n    Response<Map<String, Map<String, Double>>> response = pipeliningBase.ftSpellCheck(\"myIndex\", query, spellCheckParams);\n\n    assertThat(commands, contains(mapStringMapStringDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSynDump() {\n    when(commandObjects.ftSynDump(\"myIndex\")).thenReturn(mapStringListStringCommandObject);\n\n    Response<Map<String, List<String>>> response = pipeliningBase.ftSynDump(\"myIndex\");\n\n    assertThat(commands, contains(mapStringListStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSynUpdate() {\n    String synonymGroupId = \"group1\";\n    String[] terms = { \"term1\", \"term2\" };\n\n    when(commandObjects.ftSynUpdate(\"myIndex\", synonymGroupId, terms)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.ftSynUpdate(\"myIndex\", synonymGroupId, terms);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtTagVals() {\n    when(commandObjects.ftTagVals(\"myIndex\", \"myField\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.ftTagVals(\"myIndex\", \"myField\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugAdd() {\n    when(commandObjects.ftSugAdd(\"mySug\", \"hello\", 1.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftSugAdd(\"mySug\", \"hello\", 1.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugAddIncr() {\n    when(commandObjects.ftSugAddIncr(\"mySug\", \"hello\", 1.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftSugAddIncr(\"mySug\", \"hello\", 1.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugDel() {\n    when(commandObjects.ftSugDel(\"mySug\", \"hello\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.ftSugDel(\"mySug\", \"hello\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugGet() {\n    when(commandObjects.ftSugGet(\"mySug\", \"he\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.ftSugGet(\"mySug\", \"he\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugGetWithFuzzyAndMax() {\n    when(commandObjects.ftSugGet(\"mySug\", \"he\", true, 10)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.ftSugGet(\"mySug\", \"he\", true, 10);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugGetWithScores() {\n    when(commandObjects.ftSugGetWithScores(\"mySug\", \"he\")).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.ftSugGetWithScores(\"mySug\", \"he\");\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugGetWithScoresFuzzyMax() {\n    when(commandObjects.ftSugGetWithScores(\"mySug\", \"he\", true, 10)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.ftSugGetWithScores(\"mySug\", \"he\", true, 10);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testFtSugLen() {\n    when(commandObjects.ftSugLen(\"mySug\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.ftSugLen(\"mySug\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseServerManagementCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\n\npublic class PipeliningBaseServerManagementCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testMemoryUsage() {\n    when(commandObjects.memoryUsage(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.memoryUsage(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMemoryUsageBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.memoryUsage(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.memoryUsage(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMemoryUsageWithSamples() {\n    when(commandObjects.memoryUsage(\"key\", 10)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.memoryUsage(\"key\", 10);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMemoryUsageWithSamplesBinary() {\n    byte[] key = \"key\".getBytes();\n    int samples = 5;\n\n    when(commandObjects.memoryUsage(key, samples)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.memoryUsage(key, samples);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseSetCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic class PipeliningBaseSetCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testSadd() {\n    when(commandObjects.sadd(\"key\", \"member1\", \"member2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sadd(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSaddBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n\n    when(commandObjects.sadd(key, member1, member2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sadd(key, member1, member2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScard() {\n    when(commandObjects.scard(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.scard(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testScardBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.scard(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.scard(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSdiff() {\n    when(commandObjects.sdiff(\"key1\", \"key2\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.sdiff(\"key1\", \"key2\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSdiffBinary() {\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes() };\n\n    when(commandObjects.sdiff(keys)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.sdiff(keys);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSdiffstore() {\n    when(commandObjects.sdiffstore(\"dstKey\", \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sdiffstore(\"dstKey\", \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSdiffstoreBinary() {\n    byte[] dstkey = \"destination\".getBytes();\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sdiffstore(dstkey, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sdiffstore(dstkey, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSinter() {\n    when(commandObjects.sinter(\"key1\", \"key2\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.sinter(\"key1\", \"key2\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSinterBinary() {\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sinter(keys)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.sinter(keys);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSintercard() {\n    when(commandObjects.sintercard(\"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sintercard(\"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSintercardBinary() {\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sintercard(keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sintercard(keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSintercardWithLimit() {\n    int limit = 1;\n\n    when(commandObjects.sintercard(limit, \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sintercard(limit, \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSintercardWithLimitBinary() {\n    int limit = 2;\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sintercard(limit, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sintercard(limit, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSinterstore() {\n    when(commandObjects.sinterstore(\"dstKey\", \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sinterstore(\"dstKey\", \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSinterstoreBinary() {\n    byte[] dstkey = \"destination\".getBytes();\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sinterstore(dstkey, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sinterstore(dstkey, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSismember() {\n    when(commandObjects.sismember(\"key\", \"member\")).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.sismember(\"key\", \"member\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSismemberBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.sismember(key, member)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.sismember(key, member);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSmembers() {\n    when(commandObjects.smembers(\"key\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.smembers(\"key\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSmembersBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.smembers(key)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.smembers(key);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSmismember() {\n    when(commandObjects.smismember(\"key\", \"member1\", \"member2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.smismember(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSmismemberBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n\n    when(commandObjects.smismember(key, member1, member2)).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.smismember(key, member1, member2);\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSmove() {\n    when(commandObjects.smove(\"srcKey\", \"dstKey\", \"member\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.smove(\"srcKey\", \"dstKey\", \"member\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSmoveBinary() {\n    byte[] srckey = \"source\".getBytes();\n    byte[] dstkey = \"destination\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.smove(srckey, dstkey, member)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.smove(srckey, dstkey, member);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSpop() {\n    when(commandObjects.spop(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.spop(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSpopBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.spop(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.spop(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSpopCount() {\n    long count = 2;\n\n    when(commandObjects.spop(\"key\", count)).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.spop(\"key\", count);\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSpopCountBinary() {\n    byte[] key = \"key\".getBytes();\n    long count = 2;\n\n    when(commandObjects.spop(key, count)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.spop(key, count);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSrandmember() {\n    when(commandObjects.srandmember(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.srandmember(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSrandmemberBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.srandmember(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.srandmember(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSrandmemberCount() {\n    int count = 2;\n\n    when(commandObjects.srandmember(\"key\", count)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.srandmember(\"key\", count);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSrandmemberCountBinary() {\n    byte[] key = \"key\".getBytes();\n    int count = 2;\n\n    when(commandObjects.srandmember(key, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.srandmember(key, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSrem() {\n    when(commandObjects.srem(\"key\", \"member1\", \"member2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.srem(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSremBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] member1 = \"member1\".getBytes();\n    byte[] member2 = \"member2\".getBytes();\n\n    when(commandObjects.srem(key, member1, member2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.srem(key, member1, member2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSscan() {\n    String cursor = \"0\";\n    ScanParams params = new ScanParams();\n\n    when(commandObjects.sscan(\"key\", cursor, params)).thenReturn(scanResultStringCommandObject);\n\n    Response<ScanResult<String>> response = pipeliningBase.sscan(\"key\", cursor, params);\n\n    assertThat(commands, contains(scanResultStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSscanBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"pattern*\").count(10);\n\n    when(commandObjects.sscan(key, cursor, params)).thenReturn(scanResultBytesCommandObject);\n\n    Response<ScanResult<byte[]>> response = pipeliningBase.sscan(key, cursor, params);\n\n    assertThat(commands, contains(scanResultBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSunion() {\n    when(commandObjects.sunion(\"key1\", \"key2\")).thenReturn(setStringCommandObject);\n\n    Response<Set<String>> response = pipeliningBase.sunion(\"key1\", \"key2\");\n\n    assertThat(commands, contains(setStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSunionBinary() {\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sunion(keys)).thenReturn(setBytesCommandObject);\n\n    Response<Set<byte[]>> response = pipeliningBase.sunion(keys);\n\n    assertThat(commands, contains(setBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSunionstore() {\n    when(commandObjects.sunionstore(\"dstKey\", \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sunionstore(\"dstKey\", \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSunionstoreBinary() {\n    byte[] dstkey = \"destination\".getBytes();\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n\n    when(commandObjects.sunionstore(dstkey, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.sunionstore(dstkey, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseSortedSetCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\nimport redis.clients.jedis.params.ZParams;\nimport redis.clients.jedis.params.ZRangeParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class PipeliningBaseSortedSetCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testBzmpop() {\n    SortedSetOption option = SortedSetOption.MAX;\n\n    when(commandObjects.bzmpop(1.0, option, \"key1\", \"key2\")).thenReturn(keyValueStringListTupleCommandObject);\n\n    Response<KeyValue<String, List<Tuple>>> response = pipeliningBase.bzmpop(1.0, option, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzmpopBinary() {\n    double timeout = 1.0;\n    SortedSetOption option = SortedSetOption.MAX;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.bzmpop(timeout, option, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n\n    Response<KeyValue<byte[], List<Tuple>>> response = pipeliningBase.bzmpop(timeout, option, keys);\n\n    assertThat(commands, contains(keyValueBytesListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzmpopWithCount() {\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n\n    when(commandObjects.bzmpop(1.0, option, count, \"key1\", \"key2\")).thenReturn(keyValueStringListTupleCommandObject);\n\n    Response<KeyValue<String, List<Tuple>>> response = pipeliningBase.bzmpop(1.0, option, count, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzmpopWithCountBinary() {\n    double timeout = 1.0;\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.bzmpop(timeout, option, count, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n\n    Response<KeyValue<byte[], List<Tuple>>> response = pipeliningBase.bzmpop(timeout, option, count, keys);\n\n    assertThat(commands, contains(keyValueBytesListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzpopmax() {\n    when(commandObjects.bzpopmax(1.0, \"key1\", \"key2\")).thenReturn(keyValueStringTupleCommandObject);\n\n    Response<KeyValue<String, Tuple>> response = pipeliningBase.bzpopmax(1.0, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzpopmaxBinary() {\n    double timeout = 1.0;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.bzpopmax(timeout, keys)).thenReturn(keyValueBytesTupleCommandObject);\n\n    Response<KeyValue<byte[], Tuple>> response = pipeliningBase.bzpopmax(timeout, keys);\n\n    assertThat(commands, contains(keyValueBytesTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzpopmin() {\n    when(commandObjects.bzpopmin(1.0, \"key1\", \"key2\")).thenReturn(keyValueStringTupleCommandObject);\n\n    Response<KeyValue<String, Tuple>> response = pipeliningBase.bzpopmin(1.0, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testBzpopminBinary() {\n    double timeout = 1.0;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.bzpopmin(timeout, keys)).thenReturn(keyValueBytesTupleCommandObject);\n\n    Response<KeyValue<byte[], Tuple>> response = pipeliningBase.bzpopmin(timeout, keys);\n\n    assertThat(commands, contains(keyValueBytesTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZadd() {\n    when(commandObjects.zadd(\"key\", 1.0, \"member\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(\"key\", 1.0, \"member\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddBinary() {\n    byte[] key = \"zset\".getBytes();\n    double score = 1.0;\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zadd(key, score, member)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(key, score, member);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddWithParams() {\n    ZAddParams params = new ZAddParams();\n\n    when(commandObjects.zadd(\"key\", 1.0, \"member\", params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(\"key\", 1.0, \"member\", params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddWithParamsBinary() {\n    byte[] key = \"zset\".getBytes();\n    double score = 1.0;\n    byte[] member = \"member\".getBytes();\n    ZAddParams params = ZAddParams.zAddParams().nx();\n\n    when(commandObjects.zadd(key, score, member, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(key, score, member, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddMultiple() {\n    Map<String, Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\", 1.0);\n    scoreMembers.put(\"member2\", 2.0);\n\n    when(commandObjects.zadd(\"key\", scoreMembers)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(\"key\", scoreMembers);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddMultipleBinary() {\n    byte[] key = \"zset\".getBytes();\n\n    Map<byte[], Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\".getBytes(), 1.0);\n    scoreMembers.put(\"member2\".getBytes(), 2.0);\n\n    when(commandObjects.zadd(key, scoreMembers)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(key, scoreMembers);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddMultipleWithParams() {\n    Map<String, Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\", 1.0);\n    scoreMembers.put(\"member2\", 2.0);\n\n    ZAddParams params = new ZAddParams();\n\n    when(commandObjects.zadd(\"key\", scoreMembers, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(\"key\", scoreMembers, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddMultipleWithParamsBinary() {\n    byte[] key = \"zset\".getBytes();\n\n    Map<byte[], Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\".getBytes(), 1.0);\n    scoreMembers.put(\"member2\".getBytes(), 2.0);\n\n    ZAddParams params = ZAddParams.zAddParams().nx();\n\n    when(commandObjects.zadd(key, scoreMembers, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zadd(key, scoreMembers, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddIncr() {\n    ZAddParams params = new ZAddParams();\n\n    when(commandObjects.zaddIncr(\"key\", 1.0, \"member\", params)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zaddIncr(\"key\", 1.0, \"member\", params);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZaddIncrBinary() {\n    byte[] key = \"zset\".getBytes();\n    double score = 1.0;\n    byte[] member = \"member\".getBytes();\n    ZAddParams params = ZAddParams.zAddParams().xx();\n\n    when(commandObjects.zaddIncr(key, score, member, params)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zaddIncr(key, score, member, params);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZcard() {\n    when(commandObjects.zcard(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zcard(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZcardBinary() {\n    byte[] key = \"zset\".getBytes();\n\n    when(commandObjects.zcard(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zcard(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZcount() {\n    when(commandObjects.zcount(\"key\", \"1\", \"2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zcount(\"key\", \"1\", \"2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZcountBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"min\".getBytes();\n    byte[] max = \"max\".getBytes();\n\n    when(commandObjects.zcount(key, min, max)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zcount(key, min, max);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZcountDouble() {\n    when(commandObjects.zcount(\"key\", 1.0, 2.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zcount(\"key\", 1.0, 2.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZcountDoubleBinary() {\n    byte[] key = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n\n    when(commandObjects.zcount(key, min, max)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zcount(key, min, max);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiff() {\n    when(commandObjects.zdiff(\"key1\", \"key2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zdiff(\"key1\", \"key2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zdiff(keys)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zdiff(keys);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffWithScores() {\n    when(commandObjects.zdiffWithScores(\"key1\", \"key2\")).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zdiffWithScores(\"key1\", \"key2\");\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffWithScoresBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zdiffWithScores(keys)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zdiffWithScores(keys);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffStore() {\n    when(commandObjects.zdiffStore(\"dstKey\", \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zdiffStore(\"dstKey\", \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffStoreBinary() {\n    byte[] dstkey = \"destZset\".getBytes();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zdiffStore(dstkey, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zdiffStore(dstkey, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffstore() {\n    when(commandObjects.zdiffstore(\"dstKey\", \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zdiffstore(\"dstKey\", \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZdiffstoreBinary() {\n    byte[] dstkey = \"destZset\".getBytes();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zdiffstore(dstkey, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zdiffstore(dstkey, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZincrby() {\n    when(commandObjects.zincrby(\"key\", 1.0, \"member\")).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zincrby(\"key\", 1.0, \"member\");\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZincrbyBinary() {\n    byte[] key = \"zset\".getBytes();\n    double increment = 2.0;\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zincrby(key, increment, member)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zincrby(key, increment, member);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZincrbyWithParams() {\n    ZIncrByParams params = new ZIncrByParams();\n\n    when(commandObjects.zincrby(\"key\", 1.0, \"member\", params)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zincrby(\"key\", 1.0, \"member\", params);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZincrbyWithParamsBinary() {\n    byte[] key = \"zset\".getBytes();\n    double increment = 2.0;\n    byte[] member = \"member\".getBytes();\n    ZIncrByParams params = ZIncrByParams.zIncrByParams().xx();\n\n    when(commandObjects.zincrby(key, increment, member, params)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zincrby(key, increment, member, params);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinter() {\n    ZParams params = new ZParams();\n\n    when(commandObjects.zinter(params, \"key1\", \"key2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zinter(params, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterBinary() {\n    ZParams params = new ZParams();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zinter(params, keys)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zinter(params, keys);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterWithScores() {\n    ZParams params = new ZParams();\n\n    when(commandObjects.zinterWithScores(params, \"key1\", \"key2\")).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zinterWithScores(params, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterWithScoresBinary() {\n    ZParams params = new ZParams();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zinterWithScores(params, keys)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zinterWithScores(params, keys);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZintercard() {\n    when(commandObjects.zintercard(\"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zintercard(\"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZintercardBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zintercard(keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zintercard(keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZintercardWithLimit() {\n    long limit = 2;\n\n    when(commandObjects.zintercard(limit, \"key1\", \"key2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zintercard(limit, \"key1\", \"key2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZintercardWithLimitBinary() {\n    long limit = 2;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zintercard(limit, keys)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zintercard(limit, keys);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterstore() {\n    when(commandObjects.zinterstore(\"dstKey\", \"set1\", \"set2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zinterstore(\"dstKey\", \"set1\", \"set2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterstoreBinary() {\n    byte[] dstkey = \"destZset\".getBytes();\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zinterstore(dstkey, sets)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zinterstore(dstkey, sets);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterstoreWithParams() {\n    ZParams params = new ZParams();\n\n    when(commandObjects.zinterstore(\"dstKey\", params, \"set1\", \"set2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zinterstore(\"dstKey\", params, \"set1\", \"set2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZinterstoreWithParamsBinary() {\n    byte[] dstkey = \"destZset\".getBytes();\n    ZParams params = new ZParams();\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zinterstore(dstkey, params, sets)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zinterstore(dstkey, params, sets);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZlexcount() {\n    when(commandObjects.zlexcount(\"key\", \"[a\", \"[z\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zlexcount(\"key\", \"[a\", \"[z\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZlexcountBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[z\".getBytes();\n\n    when(commandObjects.zlexcount(key, min, max)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zlexcount(key, min, max);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZmpop() {\n    SortedSetOption option = SortedSetOption.MAX;\n\n    when(commandObjects.zmpop(option, \"key1\", \"key2\")).thenReturn(keyValueStringListTupleCommandObject);\n\n    Response<KeyValue<String, List<Tuple>>> response = pipeliningBase.zmpop(option, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZmpopBinary() {\n    SortedSetOption option = SortedSetOption.MAX;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zmpop(option, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n\n    Response<KeyValue<byte[], List<Tuple>>> response = pipeliningBase.zmpop(option, keys);\n\n    assertThat(commands, contains(keyValueBytesListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZmpopWithCount() {\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n\n    when(commandObjects.zmpop(option, count, \"key1\", \"key2\")).thenReturn(keyValueStringListTupleCommandObject);\n\n    Response<KeyValue<String, List<Tuple>>> response = pipeliningBase.zmpop(option, count, \"key1\", \"key2\");\n\n    assertThat(commands, contains(keyValueStringListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZmpopWithCountBinary() {\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zmpop(option, count, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n\n    Response<KeyValue<byte[], List<Tuple>>> response = pipeliningBase.zmpop(option, count, keys);\n\n    assertThat(commands, contains(keyValueBytesListTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZmscore() {\n    when(commandObjects.zmscore(\"key\", \"member1\", \"member2\")).thenReturn(listDoubleCommandObject);\n\n    Response<List<Double>> response = pipeliningBase.zmscore(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(listDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZmscoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes() };\n\n    when(commandObjects.zmscore(key, members)).thenReturn(listDoubleCommandObject);\n\n    Response<List<Double>> response = pipeliningBase.zmscore(key, members);\n\n    assertThat(commands, contains(listDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopmax() {\n    when(commandObjects.zpopmax(\"key\")).thenReturn(tupleCommandObject);\n\n    Response<Tuple> response = pipeliningBase.zpopmax(\"key\");\n\n    assertThat(commands, contains(tupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopmaxBinary() {\n    byte[] key = \"zset\".getBytes();\n\n    when(commandObjects.zpopmax(key)).thenReturn(tupleCommandObject);\n\n    Response<Tuple> response = pipeliningBase.zpopmax(key);\n\n    assertThat(commands, contains(tupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopmaxCount() {\n    int count = 2;\n\n    when(commandObjects.zpopmax(\"key\", count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zpopmax(\"key\", count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopmaxCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    int count = 2;\n\n    when(commandObjects.zpopmax(key, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zpopmax(key, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopmin() {\n    when(commandObjects.zpopmin(\"key\")).thenReturn(tupleCommandObject);\n\n    Response<Tuple> response = pipeliningBase.zpopmin(\"key\");\n\n    assertThat(commands, contains(tupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopminBinary() {\n    byte[] key = \"zset\".getBytes();\n\n    when(commandObjects.zpopmin(key)).thenReturn(tupleCommandObject);\n\n    Response<Tuple> response = pipeliningBase.zpopmin(key);\n\n    assertThat(commands, contains(tupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopminCount() {\n    int count = 2;\n\n    when(commandObjects.zpopmin(\"key\", count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zpopmin(\"key\", count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZpopminCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    int count = 2;\n\n    when(commandObjects.zpopmin(key, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zpopmin(key, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrandmember() {\n    when(commandObjects.zrandmember(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.zrandmember(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrandmemberBinary() {\n    byte[] key = \"zset\".getBytes();\n\n    when(commandObjects.zrandmember(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.zrandmember(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrandmemberCount() {\n    long count = 2;\n\n    when(commandObjects.zrandmember(\"key\", count)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrandmember(\"key\", count);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrandmemberCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    long count = 2;\n\n    when(commandObjects.zrandmember(key, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrandmember(key, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrandmemberWithScores() {\n    long count = 2;\n\n    when(commandObjects.zrandmemberWithScores(\"key\", count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrandmemberWithScores(\"key\", count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrandmemberWithScoresBinary() {\n    byte[] key = \"zset\".getBytes();\n    long count = 2;\n\n    when(commandObjects.zrandmemberWithScores(key, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrandmemberWithScores(key, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrange() {\n    when(commandObjects.zrange(\"key\", 0, -1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrange(\"key\", 0, -1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeBinary() {\n    byte[] key = \"zset\".getBytes();\n    long start = 0;\n    long stop = 1;\n\n    when(commandObjects.zrange(key, start, stop)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrange(key, start, stop);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeWithScores() {\n    when(commandObjects.zrangeWithScores(\"key\", 0, -1)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeWithScores(\"key\", 0, -1);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeWithScoresBinary() {\n    byte[] key = \"zset\".getBytes();\n    long start = 0;\n    long stop = 1;\n\n    when(commandObjects.zrangeWithScores(key, start, stop)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeWithScores(key, start, stop);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeWithZRangeParams() {\n    ZRangeParams zRangeParams = new ZRangeParams(1, 2);\n\n    when(commandObjects.zrange(\"key\", zRangeParams)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrange(\"key\", zRangeParams);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeWithZRangeParamsBinary() {\n    byte[] key = \"zset\".getBytes();\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(0, 1);\n\n    when(commandObjects.zrange(key, zRangeParams)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrange(key, zRangeParams);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeWithScoresWithZRangeParams() {\n    ZRangeParams zRangeParams = new ZRangeParams(1, 2);\n\n    when(commandObjects.zrangeWithScores(\"key\", zRangeParams)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeWithScores(\"key\", zRangeParams);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeWithScoresWithZRangeParamsBinary() {\n    byte[] key = \"zset\".getBytes();\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(0, 1);\n\n    when(commandObjects.zrangeWithScores(key, zRangeParams)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeWithScores(key, zRangeParams);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByLex() {\n    when(commandObjects.zrangeByLex(\"key\", \"[a\", \"[z\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrangeByLex(\"key\", \"[a\", \"[z\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByLexBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[z\".getBytes();\n\n    when(commandObjects.zrangeByLex(key, min, max)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrangeByLex(key, min, max);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByLexWithOffsetCount() {\n    when(commandObjects.zrangeByLex(\"key\", \"[a\", \"[z\", 0, 10)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrangeByLex(\"key\", \"[a\", \"[z\", 0, 10);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByLexWithOffsetCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[z\".getBytes();\n    int offset = 0;\n    int count = 10;\n\n    when(commandObjects.zrangeByLex(key, min, max, offset, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrangeByLex(key, min, max, offset, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScore() {\n    when(commandObjects.zrangeByScore(\"key\", \"1\", \"2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrangeByScore(\"key\", \"1\", \"2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"2\".getBytes();\n\n    when(commandObjects.zrangeByScore(key, min, max)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrangeByScore(key, min, max);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreDouble() {\n    when(commandObjects.zrangeByScore(\"key\", 1.0, 2.0)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrangeByScore(\"key\", 1.0, 2.0);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreDoubleBinary() {\n    byte[] key = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n\n    when(commandObjects.zrangeByScore(key, min, max)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrangeByScore(key, min, max);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithOffsetCount() {\n    when(commandObjects.zrangeByScore(\"key\", \"1\", \"2\", 0, 1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrangeByScore(\"key\", \"1\", \"2\", 0, 1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithOffsetCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"2\".getBytes();\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrangeByScore(key, min, max, offset, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrangeByScore(key, min, max, offset, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreDoubleWithOffsetCount() {\n    when(commandObjects.zrangeByScore(\"key\", 1.0, 2.0, 0, 1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrangeByScore(\"key\", 1.0, 2.0, 0, 1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreDoubleWithOffsetCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrangeByScore(key, min, max, offset, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrangeByScore(key, min, max, offset, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScores() {\n    when(commandObjects.zrangeByScoreWithScores(\"key\", \"1\", \"2\")).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(\"key\", \"1\", \"2\");\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"2\".getBytes();\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(key, min, max);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDouble() {\n    when(commandObjects.zrangeByScoreWithScores(\"key\", 1.0, 2.0)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(\"key\", 1.0, 2.0);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDoubleBinary() {\n    byte[] key = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(key, min, max);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresWithOffsetCount() {\n    when(commandObjects.zrangeByScoreWithScores(\"key\", \"1\", \"2\", 0, 1)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(\"key\", \"1\", \"2\", 0, 1);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresWithOffsetCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"2\".getBytes();\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(key, min, max, offset, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDoubleWithOffsetCount() {\n    when(commandObjects.zrangeByScoreWithScores(\"key\", 1.0, 2.0, 0, 1)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(\"key\", 1.0, 2.0, 0, 1);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDoubleWithOffsetCountBinary() {\n    byte[] key = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrangeByScoreWithScores(key, min, max, offset, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangestore() {\n    ZRangeParams zRangeParams = new ZRangeParams(1, 2);\n\n    when(commandObjects.zrangestore(\"dest\", \"src\", zRangeParams)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrangestore(\"dest\", \"src\", zRangeParams);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrangestoreBinary() {\n    byte[] dest = \"destZset\".getBytes();\n    byte[] src = \"srcZset\".getBytes();\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(0, 1);\n\n    when(commandObjects.zrangestore(dest, src, zRangeParams)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrangestore(dest, src, zRangeParams);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrank() {\n    when(commandObjects.zrank(\"key\", \"member\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrank(\"key\", \"member\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrankBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zrank(key, member)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrank(key, member);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrankWithScore() {\n    when(commandObjects.zrankWithScore(\"key\", \"member\")).thenReturn(keyValueLongDoubleCommandObject);\n\n    Response<KeyValue<Long, Double>> response = pipeliningBase.zrankWithScore(\"key\", \"member\");\n\n    assertThat(commands, contains(keyValueLongDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrankWithScoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zrankWithScore(key, member)).thenReturn(keyValueLongDoubleCommandObject);\n\n    Response<KeyValue<Long, Double>> response = pipeliningBase.zrankWithScore(key, member);\n\n    assertThat(commands, contains(keyValueLongDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrem() {\n    when(commandObjects.zrem(\"key\", \"member1\", \"member2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrem(\"key\", \"member1\", \"member2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes() };\n\n    when(commandObjects.zrem(key, members)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrem(key, members);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByLex() {\n    when(commandObjects.zremrangeByLex(\"key\", \"[a\", \"[z\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByLex(\"key\", \"[a\", \"[z\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByLexBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[z\".getBytes();\n\n    when(commandObjects.zremrangeByLex(key, min, max)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByLex(key, min, max);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByRank() {\n    when(commandObjects.zremrangeByRank(\"key\", 0, 1)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByRank(\"key\", 0, 1);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByRankBinary() {\n    byte[] key = \"zset\".getBytes();\n    long start = 0;\n    long stop = 1;\n\n    when(commandObjects.zremrangeByRank(key, start, stop)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByRank(key, start, stop);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByScore() {\n    when(commandObjects.zremrangeByScore(\"key\", \"1\", \"2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByScore(\"key\", \"1\", \"2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByScoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"2\".getBytes();\n\n    when(commandObjects.zremrangeByScore(key, min, max)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByScore(key, min, max);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByScoreDouble() {\n    when(commandObjects.zremrangeByScore(\"key\", 1.0, 2.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByScore(\"key\", 1.0, 2.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZremrangeByScoreDoubleBinary() {\n    byte[] key = \"zset\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n\n    when(commandObjects.zremrangeByScore(key, min, max)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zremrangeByScore(key, min, max);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrange() {\n    when(commandObjects.zrevrange(\"key\", 0, -1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrange(\"key\", 0, -1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeBinary() {\n    byte[] key = \"zset\".getBytes();\n    long start = 0;\n    long stop = 1;\n\n    when(commandObjects.zrevrange(key, start, stop)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrange(key, start, stop);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeWithScores() {\n    when(commandObjects.zrevrangeWithScores(\"key\", 0, -1)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeWithScores(\"key\", 0, -1);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeWithScoresBinary() {\n    byte[] key = \"zset\".getBytes();\n    long start = 0;\n    long stop = 1;\n\n    when(commandObjects.zrevrangeWithScores(key, start, stop)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeWithScores(key, start, stop);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByLex() {\n    when(commandObjects.zrevrangeByLex(\"key\", \"[z\", \"[a\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrangeByLex(\"key\", \"[z\", \"[a\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByLexBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] max = \"[z\".getBytes();\n    byte[] min = \"[a\".getBytes();\n\n    when(commandObjects.zrevrangeByLex(key, max, min)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrangeByLex(key, max, min);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByLexWithLimit() {\n    when(commandObjects.zrevrangeByLex(\"key\", \"[z\", \"[a\", 0, 10)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrangeByLex(\"key\", \"[z\", \"[a\", 0, 10);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByLexWithLimitBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] max = \"[z\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    int offset = 0;\n    int count = 10;\n\n    when(commandObjects.zrevrangeByLex(key, max, min, offset, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrangeByLex(key, max, min, offset, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScore() {\n    when(commandObjects.zrevrangeByScore(\"key\", \"2\", \"1\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrangeByScore(\"key\", \"2\", \"1\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] max = \"2\".getBytes();\n    byte[] min = \"1\".getBytes();\n\n    when(commandObjects.zrevrangeByScore(key, max, min)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrangeByScore(key, max, min);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreDouble() {\n    when(commandObjects.zrevrangeByScore(\"key\", 2.0, 1.0)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrangeByScore(\"key\", 2.0, 1.0);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreDoubleBinary() {\n    byte[] key = \"zset\".getBytes();\n    double max = 2.0;\n    double min = 1.0;\n\n    when(commandObjects.zrevrangeByScore(key, max, min)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrangeByScore(key, max, min);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithLimit() {\n    when(commandObjects.zrevrangeByScore(\"key\", \"2\", \"1\", 0, 1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrangeByScore(\"key\", \"2\", \"1\", 0, 1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithLimitBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] max = \"2\".getBytes();\n    byte[] min = \"1\".getBytes();\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrevrangeByScore(key, max, min, offset, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrangeByScore(key, max, min, offset, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreDoubleWithLimit() {\n    when(commandObjects.zrevrangeByScore(\"key\", 2.0, 1.0, 0, 1)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zrevrangeByScore(\"key\", 2.0, 1.0, 0, 1);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreDoubleWithLimitBinary() {\n    byte[] key = \"zset\".getBytes();\n    double max = 2.0;\n    double min = 1.0;\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrevrangeByScore(key, max, min, offset, count)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zrevrangeByScore(key, max, min, offset, count);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScores() {\n    when(commandObjects.zrevrangeByScoreWithScores(\"key\", \"2\", \"1\")).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(\"key\", \"2\", \"1\");\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] max = \"2\".getBytes();\n    byte[] min = \"1\".getBytes();\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(key, max, min);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDouble() {\n    when(commandObjects.zrevrangeByScoreWithScores(\"key\", 2.0, 1.0)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(\"key\", 2.0, 1.0);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDoubleBinary() {\n    byte[] key = \"zset\".getBytes();\n    double max = 2.0;\n    double min = 1.0;\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(key, max, min);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresWithLimit() {\n    when(commandObjects.zrevrangeByScoreWithScores(\"key\", \"2\", \"1\", 0, 1)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(\"key\", \"2\", \"1\", 0, 1);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresWithLimitBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] max = \"2\".getBytes();\n    byte[] min = \"1\".getBytes();\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(key, max, min, offset, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDoubleWithLimit() {\n    when(commandObjects.zrevrangeByScoreWithScores(\"key\", 2.0, 1.0, 0, 1)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(\"key\", 2.0, 1.0, 0, 1);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDoubleWithLimitBinary() {\n    byte[] key = \"zset\".getBytes();\n    double max = 2.0;\n    double min = 1.0;\n    int offset = 0;\n    int count = 2;\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zrevrangeByScoreWithScores(key, max, min, offset, count);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrank() {\n    when(commandObjects.zrevrank(\"key\", \"member\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrevrank(\"key\", \"member\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrankBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zrevrank(key, member)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zrevrank(key, member);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrankWithScore() {\n    when(commandObjects.zrevrankWithScore(\"key\", \"member\")).thenReturn(keyValueLongDoubleCommandObject);\n\n    Response<KeyValue<Long, Double>> response = pipeliningBase.zrevrankWithScore(\"key\", \"member\");\n\n    assertThat(commands, contains(keyValueLongDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZrevrankWithScoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zrevrankWithScore(key, member)).thenReturn(keyValueLongDoubleCommandObject);\n\n    Response<KeyValue<Long, Double>> response = pipeliningBase.zrevrankWithScore(key, member);\n\n    assertThat(commands, contains(keyValueLongDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZscan() {\n    ScanParams params = new ScanParams();\n\n    when(commandObjects.zscan(\"key\", \"0\", params)).thenReturn(scanResultTupleCommandObject);\n\n    Response<ScanResult<Tuple>> response = pipeliningBase.zscan(\"key\", \"0\", params);\n\n    assertThat(commands, contains(scanResultTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZscanBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams();\n\n    when(commandObjects.zscan(key, cursor, params)).thenReturn(scanResultTupleCommandObject);\n\n    Response<ScanResult<Tuple>> response = pipeliningBase.zscan(key, cursor, params);\n\n    assertThat(commands, contains(scanResultTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZscore() {\n    when(commandObjects.zscore(\"key\", \"member\")).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zscore(\"key\", \"member\");\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZscoreBinary() {\n    byte[] key = \"zset\".getBytes();\n    byte[] member = \"member\".getBytes();\n\n    when(commandObjects.zscore(key, member)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.zscore(key, member);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunion() {\n    ZParams params = new ZParams();\n\n    when(commandObjects.zunion(params, \"key1\", \"key2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.zunion(params, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionBinary() {\n    ZParams params = new ZParams();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zunion(params, keys)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.zunion(params, keys);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionWithScores() {\n    ZParams params = new ZParams();\n\n    when(commandObjects.zunionWithScores(params, \"key1\", \"key2\")).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zunionWithScores(params, \"key1\", \"key2\");\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionWithScoresBinary() {\n    ZParams params = new ZParams();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zunionWithScores(params, keys)).thenReturn(listTupleCommandObject);\n\n    Response<List<Tuple>> response = pipeliningBase.zunionWithScores(params, keys);\n\n    assertThat(commands, contains(listTupleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionstore() {\n    when(commandObjects.zunionstore(\"dstKey\", \"set1\", \"set2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zunionstore(\"dstKey\", \"set1\", \"set2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionstoreBinary() {\n    byte[] dstkey = \"destZset\".getBytes();\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zunionstore(dstkey, sets)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zunionstore(dstkey, sets);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionstoreWithParams() {\n    ZParams params = new ZParams();\n\n    when(commandObjects.zunionstore(\"dstKey\", params, \"set1\", \"set2\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zunionstore(\"dstKey\", params, \"set1\", \"set2\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testZunionstoreWithParamsBinary() {\n    byte[] dstkey = \"destZset\".getBytes();\n    ZParams params = new ZParams();\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n\n    when(commandObjects.zunionstore(dstkey, params, sets)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.zunionstore(dstkey, params, sets);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseStreamCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XAutoClaimParams;\nimport redis.clients.jedis.params.XClaimParams;\nimport redis.clients.jedis.params.XPendingParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.params.XTrimParams;\nimport redis.clients.jedis.resps.StreamConsumerInfo;\nimport redis.clients.jedis.resps.StreamConsumersInfo;\nimport redis.clients.jedis.resps.StreamEntry;\nimport redis.clients.jedis.resps.StreamFullInfo;\nimport redis.clients.jedis.resps.StreamGroupInfo;\nimport redis.clients.jedis.resps.StreamInfo;\nimport redis.clients.jedis.resps.StreamPendingEntry;\nimport redis.clients.jedis.resps.StreamPendingSummary;\n\npublic class PipeliningBaseStreamCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testXack() {\n    StreamEntryID[] ids = { new StreamEntryID(\"1526999352406-0\"), new StreamEntryID(\"1526999352406-1\") };\n\n    when(commandObjects.xack(\"key\", \"group\", ids)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xack(\"key\", \"group\", ids);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXackBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] group = \"group\".getBytes();\n    byte[] id1 = \"id1\".getBytes();\n    byte[] id2 = \"id2\".getBytes();\n\n    when(commandObjects.xack(key, group, id1, id2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xack(key, group, id1, id2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXadd() {\n    StreamEntryID id = new StreamEntryID();\n\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n\n    when(commandObjects.xadd(\"key\", id, hash)).thenReturn(streamEntryIdCommandObject);\n\n    Response<StreamEntryID> response = pipeliningBase.xadd(\"key\", id, hash);\n\n    assertThat(commands, contains(streamEntryIdCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXaddBinary() {\n    byte[] key = \"stream\".getBytes();\n    XAddParams params = new XAddParams();\n\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.xadd(key, params, hash)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.xadd(key, params, hash);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXaddWithParams() {\n    XAddParams params = new XAddParams();\n\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n\n    when(commandObjects.xadd(\"key\", params, hash)).thenReturn(streamEntryIdCommandObject);\n\n    Response<StreamEntryID> response = pipeliningBase.xadd(\"key\", params, hash);\n\n    assertThat(commands, contains(streamEntryIdCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXautoclaim() {\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    XAutoClaimParams params = new XAutoClaimParams();\n\n    when(commandObjects.xautoclaim(\"key\", \"group\", \"consumerName\", 10000L, start, params))\n        .thenReturn(entryStreamEntryIdListStreamEntryCommandObject);\n\n    Response<Map.Entry<StreamEntryID, List<StreamEntry>>> response = pipeliningBase\n        .xautoclaim(\"key\", \"group\", \"consumerName\", 10000L, start, params);\n\n    assertThat(commands, contains(entryStreamEntryIdListStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXautoclaimBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    byte[] consumerName = \"consumer\".getBytes();\n    long minIdleTime = 10000L;\n    byte[] start = \"startId\".getBytes();\n    XAutoClaimParams params = new XAutoClaimParams();\n\n    when(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xautoclaim(key, groupName, consumerName, minIdleTime, start, params);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXautoclaimJustId() {\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    XAutoClaimParams params = new XAutoClaimParams();\n\n    when(commandObjects.xautoclaimJustId(\"key\", \"group\", \"consumerName\", 10000L, start, params))\n        .thenReturn(entryStreamEntryIdListStreamEntryIdCommandObject);\n\n    Response<Map.Entry<StreamEntryID, List<StreamEntryID>>> response = pipeliningBase\n        .xautoclaimJustId(\"key\", \"group\", \"consumerName\", 10000L, start, params);\n\n    assertThat(commands, contains(entryStreamEntryIdListStreamEntryIdCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXautoclaimJustIdBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    byte[] consumerName = \"consumer\".getBytes();\n    long minIdleTime = 10000L;\n    byte[] start = \"startId\".getBytes();\n    XAutoClaimParams params = new XAutoClaimParams();\n\n    when(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXclaim() {\n    StreamEntryID[] ids = { new StreamEntryID(\"1526999352406-0\"), new StreamEntryID(\"1526999352406-1\") };\n    XClaimParams params = new XClaimParams().idle(10000L);\n\n    when(commandObjects.xclaim(\"key\", \"group\", \"consumerName\", 10000L, params, ids))\n        .thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase\n        .xclaim(\"key\", \"group\", \"consumerName\", 10000L, params, ids);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXclaimBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] group = \"group\".getBytes();\n    byte[] consumerName = \"consumer\".getBytes();\n    long minIdleTime = 10000L;\n    XClaimParams params = new XClaimParams();\n    byte[] id1 = \"id1\".getBytes();\n    byte[] id2 = \"id2\".getBytes();\n\n    when(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, id1, id2)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.xclaim(key, group, consumerName, minIdleTime, params, id1, id2);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXclaimJustId() {\n    StreamEntryID[] ids = { new StreamEntryID(\"1526999352406-0\"), new StreamEntryID(\"1526999352406-1\") };\n    XClaimParams params = new XClaimParams().idle(10000L);\n\n    when(commandObjects.xclaimJustId(\"key\", \"group\", \"consumerName\", 10000L, params, ids))\n        .thenReturn(listStreamEntryIdCommandObject);\n\n    Response<List<StreamEntryID>> response = pipeliningBase\n        .xclaimJustId(\"key\", \"group\", \"consumerName\", 10000L, params, ids);\n\n    assertThat(commands, contains(listStreamEntryIdCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXclaimJustIdBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] group = \"group\".getBytes();\n    byte[] consumerName = \"consumer\".getBytes();\n    long minIdleTime = 10000L;\n    XClaimParams params = new XClaimParams();\n    byte[] id1 = \"id1\".getBytes();\n    byte[] id2 = \"id2\".getBytes();\n\n    when(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, id1, id2)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.xclaimJustId(key, group, consumerName, minIdleTime, params, id1, id2);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXdel() {\n    StreamEntryID[] ids = { new StreamEntryID(\"1526999352406-0\"), new StreamEntryID(\"1526999352406-1\") };\n\n    when(commandObjects.xdel(\"key\", ids)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xdel(\"key\", ids);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXdelBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] id1 = \"id1\".getBytes();\n    byte[] id2 = \"id2\".getBytes();\n\n    when(commandObjects.xdel(key, id1, id2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xdel(key, id1, id2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupCreate() {\n    StreamEntryID id = new StreamEntryID(\"0-0\");\n\n    when(commandObjects.xgroupCreate(\"key\", \"groupName\", id, true)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.xgroupCreate(\"key\", \"groupName\", id, true);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupCreateBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    byte[] id = \"id\".getBytes();\n    boolean makeStream = true;\n\n    when(commandObjects.xgroupCreate(key, groupName, id, makeStream)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.xgroupCreate(key, groupName, id, makeStream);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupCreateConsumer() {\n    when(commandObjects.xgroupCreateConsumer(\"key\", \"groupName\", \"consumerName\"))\n        .thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.xgroupCreateConsumer(\"key\", \"groupName\", \"consumerName\");\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupCreateConsumerBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    byte[] consumerName = \"consumer\".getBytes();\n\n    when(commandObjects.xgroupCreateConsumer(key, groupName, consumerName)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.xgroupCreateConsumer(key, groupName, consumerName);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupDelConsumer() {\n    when(commandObjects.xgroupDelConsumer(\"key\", \"groupName\", \"consumerName\"))\n        .thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xgroupDelConsumer(\"key\", \"groupName\", \"consumerName\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupDelConsumerBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    byte[] consumerName = \"consumer\".getBytes();\n\n    when(commandObjects.xgroupDelConsumer(key, groupName, consumerName)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xgroupDelConsumer(key, groupName, consumerName);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupDestroy() {\n    when(commandObjects.xgroupDestroy(\"key\", \"groupName\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xgroupDestroy(\"key\", \"groupName\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupDestroyBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n\n    when(commandObjects.xgroupDestroy(key, groupName)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xgroupDestroy(key, groupName);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupSetID() {\n    StreamEntryID id = new StreamEntryID(\"0-0\");\n\n    when(commandObjects.xgroupSetID(\"key\", \"groupName\", id)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.xgroupSetID(\"key\", \"groupName\", id);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXgroupSetIDBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    byte[] id = \"id\".getBytes();\n\n    when(commandObjects.xgroupSetID(key, groupName, id)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.xgroupSetID(key, groupName, id);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoConsumers() {\n    when(commandObjects.xinfoConsumers(\"key\", \"group\")).thenReturn(listStreamConsumersInfoCommandObject);\n\n    Response<List<StreamConsumersInfo>> response = pipeliningBase.xinfoConsumers(\"key\", \"group\");\n\n    assertThat(commands, contains(listStreamConsumersInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoConsumersBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] group = \"group\".getBytes();\n\n    when(commandObjects.xinfoConsumers(key, group)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xinfoConsumers(key, group);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoConsumers2() {\n    when(commandObjects.xinfoConsumers2(\"key\", \"group\")).thenReturn(listStreamConsumerInfoCommandObject);\n\n    Response<List<StreamConsumerInfo>> response = pipeliningBase.xinfoConsumers2(\"key\", \"group\");\n\n    assertThat(commands, contains(listStreamConsumerInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoGroups() {\n    when(commandObjects.xinfoGroups(\"key\")).thenReturn(listStreamGroupInfoCommandObject);\n\n    Response<List<StreamGroupInfo>> response = pipeliningBase.xinfoGroups(\"key\");\n\n    assertThat(commands, contains(listStreamGroupInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoGroupsBinary() {\n    byte[] key = \"stream\".getBytes();\n\n    when(commandObjects.xinfoGroups(key)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xinfoGroups(key);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoStream() {\n    when(commandObjects.xinfoStream(\"key\")).thenReturn(streamInfoCommandObject);\n\n    Response<StreamInfo> response = pipeliningBase.xinfoStream(\"key\");\n\n    assertThat(commands, contains(streamInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoStreamBinary() {\n    byte[] key = \"stream\".getBytes();\n\n    when(commandObjects.xinfoStream(key)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.xinfoStream(key);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoStreamFull() {\n    when(commandObjects.xinfoStreamFull(\"key\")).thenReturn(streamFullInfoCommandObject);\n\n    Response<StreamFullInfo> response = pipeliningBase.xinfoStreamFull(\"key\");\n\n    assertThat(commands, contains(streamFullInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoStreamFullBinary() {\n    byte[] key = \"stream\".getBytes();\n\n    when(commandObjects.xinfoStreamFull(key)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.xinfoStreamFull(key);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoStreamFullWithCount() {\n    int count = 10;\n    when(commandObjects.xinfoStreamFull(\"key\", count)).thenReturn(streamFullInfoCommandObject);\n\n    Response<StreamFullInfo> response = pipeliningBase.xinfoStreamFull(\"key\", count);\n\n    assertThat(commands, contains(streamFullInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXinfoStreamFullWithCountBinary() {\n    byte[] key = \"stream\".getBytes();\n    int count = 10;\n\n    when(commandObjects.xinfoStreamFull(key, count)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.xinfoStreamFull(key, count);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXlen() {\n    when(commandObjects.xlen(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xlen(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXlenBinary() {\n    byte[] key = \"stream\".getBytes();\n\n    when(commandObjects.xlen(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xlen(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXpending() {\n    when(commandObjects.xpending(\"key\", \"groupName\")).thenReturn(streamPendingSummaryCommandObject);\n\n    Response<StreamPendingSummary> response = pipeliningBase.xpending(\"key\", \"groupName\");\n\n    assertThat(commands, contains(streamPendingSummaryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXpendingBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n\n    when(commandObjects.xpending(key, groupName)).thenReturn(objectCommandObject);\n\n    Response<Object> response = pipeliningBase.xpending(key, groupName);\n\n    assertThat(commands, contains(objectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXpendingWithParams() {\n    XPendingParams params = new XPendingParams();\n\n    when(commandObjects.xpending(\"key\", \"groupName\", params)).thenReturn(listStreamPendingEntryCommandObject);\n\n    Response<List<StreamPendingEntry>> response = pipeliningBase.xpending(\"key\", \"groupName\", params);\n\n    assertThat(commands, contains(listStreamPendingEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXpendingWithParamsBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] groupName = \"group\".getBytes();\n    XPendingParams params = new XPendingParams().count(10);\n\n    when(commandObjects.xpending(key, groupName, params)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xpending(key, groupName, params);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrange() {\n    String start = \"-\";\n    String end = \"+\";\n\n    when(commandObjects.xrange(\"key\", start, end)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrange(\"key\", start, end);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrangeBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] start = \"startId\".getBytes();\n    byte[] end = \"endId\".getBytes();\n\n    when(commandObjects.xrange(key, start, end)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xrange(key, start, end);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrangeWithCount() {\n    String start = \"-\";\n    String end = \"+\";\n    int count = 10;\n\n    when(commandObjects.xrange(\"key\", start, end, count)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrange(\"key\", start, end, count);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrangeWithCountBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] start = \"startId\".getBytes();\n    byte[] end = \"endId\".getBytes();\n    int count = 10;\n\n    when(commandObjects.xrange(key, start, end, count)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xrange(key, start, end, count);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrangeIds() {\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    StreamEntryID end = new StreamEntryID(\"9999999999999-0\");\n\n    when(commandObjects.xrange(\"key\", start, end)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrange(\"key\", start, end);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrangeIdsWithCount() {\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    StreamEntryID end = new StreamEntryID(\"9999999999999-0\");\n    int count = 10;\n\n    when(commandObjects.xrange(\"key\", start, end, count)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrange(\"key\", start, end, count);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXread() {\n    XReadParams xReadParams = new XReadParams();\n\n    Map<String, StreamEntryID> streams = new HashMap<>();\n    streams.put(\"key1\", new StreamEntryID(\"0-0\"));\n    streams.put(\"key2\", new StreamEntryID(\"0-0\"));\n\n    when(commandObjects.xread(xReadParams, streams)).thenReturn(listEntryStringListStreamEntryCommandObject);\n\n    Response<List<Map.Entry<String, List<StreamEntry>>>> response = pipeliningBase.xread(xReadParams, streams);\n\n    assertThat(commands, contains(listEntryStringListStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXreadBinary() {\n    XReadParams xReadParams = new XReadParams();\n    Map.Entry<byte[], byte[]> stream1 = new AbstractMap.SimpleImmutableEntry<>(\"stream1\".getBytes(), \"id1\".getBytes());\n\n    when(commandObjects.xread(xReadParams, stream1)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xread(xReadParams, stream1);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXreadGroup() {\n    XReadGroupParams xReadGroupParams = new XReadGroupParams();\n\n    Map<String, StreamEntryID> streams = new HashMap<>();\n    streams.put(\"stream1\", new StreamEntryID(\"0-0\"));\n\n    when(commandObjects.xreadGroup(\"groupName\", \"consumer\", xReadGroupParams, streams))\n        .thenReturn(listEntryStringListStreamEntryCommandObject);\n\n    Response<List<Map.Entry<String, List<StreamEntry>>>> response = pipeliningBase\n        .xreadGroup(\"groupName\", \"consumer\", xReadGroupParams, streams);\n\n    assertThat(commands, contains(listEntryStringListStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXreadGroupBinary() {\n    byte[] groupName = \"group\".getBytes();\n    byte[] consumer = \"consumer\".getBytes();\n    XReadGroupParams xReadGroupParams = new XReadGroupParams();\n    Map.Entry<byte[], byte[]> stream1 = new AbstractMap.SimpleImmutableEntry<>(\"stream1\".getBytes(), \"id1\".getBytes());\n\n    when(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, stream1)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xreadGroup(groupName, consumer, xReadGroupParams, stream1);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrevrange() {\n    String end = \"+\";\n    String start = \"-\";\n\n    when(commandObjects.xrevrange(\"key\", end, start)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrevrange(\"key\", end, start);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrevrangeBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] end = \"endId\".getBytes();\n    byte[] start = \"startId\".getBytes();\n\n    when(commandObjects.xrevrange(key, end, start)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xrevrange(key, end, start);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrevrangeWithCount() {\n    String end = \"+\";\n    String start = \"-\";\n    int count = 10;\n\n    when(commandObjects.xrevrange(\"key\", end, start, count)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrevrange(\"key\", end, start, count);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrevrangeWithCountBinary() {\n    byte[] key = \"stream\".getBytes();\n    byte[] end = \"endId\".getBytes();\n    byte[] start = \"startId\".getBytes();\n    int count = 10;\n\n    when(commandObjects.xrevrange(key, end, start, count)).thenReturn(listObjectCommandObject);\n\n    Response<List<Object>> response = pipeliningBase.xrevrange(key, end, start, count);\n\n    assertThat(commands, contains(listObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrevrangeIds() {\n    StreamEntryID end = new StreamEntryID(\"9999999999999-0\");\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n\n    when(commandObjects.xrevrange(\"key\", end, start)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrevrange(\"key\", end, start);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXrevrangeIdsWithCount() {\n    StreamEntryID end = new StreamEntryID(\"9999999999999-0\");\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    int count = 10;\n\n    when(commandObjects.xrevrange(\"key\", end, start, count)).thenReturn(listStreamEntryCommandObject);\n\n    Response<List<StreamEntry>> response = pipeliningBase.xrevrange(\"key\", end, start, count);\n\n    assertThat(commands, contains(listStreamEntryCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXtrim() {\n    when(commandObjects.xtrim(\"key\", 1000L, true)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xtrim(\"key\", 1000L, true);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXtrimBinary() {\n    byte[] key = \"stream\".getBytes();\n    long maxLen = 1000L;\n    boolean approximateLength = true;\n\n    when(commandObjects.xtrim(key, maxLen, approximateLength)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xtrim(key, maxLen, approximateLength);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXtrimWithParams() {\n    XTrimParams params = new XTrimParams().maxLen(1000L);\n    when(commandObjects.xtrim(\"key\", params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xtrim(\"key\", params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testXtrimWithParamsBinary() {\n    byte[] key = \"stream\".getBytes();\n    XTrimParams params = new XTrimParams().maxLen(1000L);\n\n    when(commandObjects.xtrim(key, params)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.xtrim(key, params);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseStringCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.resps.LCSMatchResult;\n\npublic class PipeliningBaseStringCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testAppend() {\n    when(commandObjects.append(\"key\", \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.append(\"key\", \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testAppendBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.append(key, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.append(key, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDecr() {\n    when(commandObjects.decr(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.decr(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDecrBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.decr(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.decr(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDecrBy() {\n    when(commandObjects.decrBy(\"key\", 10L)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.decrBy(\"key\", 10L);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testDecrByBinary() {\n    byte[] key = \"key\".getBytes();\n    long decrement = 2L;\n\n    when(commandObjects.decrBy(key, decrement)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.decrBy(key, decrement);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGet() {\n    when(commandObjects.get(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.get(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.get(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.get(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetDel() {\n    when(commandObjects.getDel(\"key\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.getDel(\"key\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetDelBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.getDel(key)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.getDel(key);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetEx() {\n    GetExParams params = new GetExParams();\n\n    when(commandObjects.getEx(\"key\", params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.getEx(\"key\", params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetExBinary() {\n    byte[] key = \"key\".getBytes();\n    GetExParams params = new GetExParams().ex(10);\n\n    when(commandObjects.getEx(key, params)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.getEx(key, params);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetrange() {\n    when(commandObjects.getrange(\"key\", 0, 100)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.getrange(\"key\", 0, 100);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetrangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long startOffset = 0L;\n    long endOffset = 10L;\n\n    when(commandObjects.getrange(key, startOffset, endOffset)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.getrange(key, startOffset, endOffset);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetSet() {\n    when(commandObjects.getSet(\"key\", \"value\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.getSet(\"key\", \"value\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testGetSetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.getSet(key, value)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.getSet(key, value);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testIncr() {\n    when(commandObjects.incr(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.incr(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testIncrBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.incr(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.incr(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testIncrBy() {\n    when(commandObjects.incrBy(\"key\", 10L)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.incrBy(\"key\", 10L);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testIncrByBinary() {\n    byte[] key = \"key\".getBytes();\n    long increment = 2L;\n\n    when(commandObjects.incrBy(key, increment)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.incrBy(key, increment);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testIncrByFloat() {\n    when(commandObjects.incrByFloat(\"key\", 1.5)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.incrByFloat(\"key\", 1.5);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testIncrByFloatBinary() {\n    byte[] key = \"key\".getBytes();\n    double increment = 2.5;\n\n    when(commandObjects.incrByFloat(key, increment)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.incrByFloat(key, increment);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLcs() {\n    LCSParams params = new LCSParams();\n\n    when(commandObjects.lcs(\"keyA\", \"keyB\", params)).thenReturn(lcsMatchResultCommandObject);\n\n    Response<LCSMatchResult> response = pipeliningBase.lcs(\"keyA\", \"keyB\", params);\n\n    assertThat(commands, contains(lcsMatchResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testLcsBinary() {\n    byte[] keyA = \"keyA\".getBytes();\n    byte[] keyB = \"keyB\".getBytes();\n    LCSParams params = new LCSParams().withMatchLen();\n\n    when(commandObjects.lcs(keyA, keyB, params)).thenReturn(lcsMatchResultCommandObject);\n\n    Response<LCSMatchResult> response = pipeliningBase.lcs(keyA, keyB, params);\n\n    assertThat(commands, contains(lcsMatchResultCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMget() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.mget(keys)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.mget(keys);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMgetBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n\n    when(commandObjects.mget(key1, key2)).thenReturn(listBytesCommandObject);\n\n    Response<List<byte[]>> response = pipeliningBase.mget(key1, key2);\n\n    assertThat(commands, contains(listBytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMset() {\n    String[] keysvalues = { \"key1\", \"value1\", \"key2\", \"value2\" };\n\n    when(commandObjects.mset(keysvalues)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.mset(keysvalues);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMsetBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    when(commandObjects.mset(key1, value1, key2, value2)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.mset(key1, value1, key2, value2);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMsetnx() {\n    String[] keysvalues = { \"key1\", \"value1\", \"key2\", \"value2\" };\n\n    when(commandObjects.msetnx(keysvalues)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.msetnx(keysvalues);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMsetnxBinary() {\n    byte[] key1 = \"key1\".getBytes();\n    byte[] value1 = \"value1\".getBytes();\n    byte[] key2 = \"key2\".getBytes();\n    byte[] value2 = \"value2\".getBytes();\n\n    when(commandObjects.msetnx(key1, value1, key2, value2)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.msetnx(key1, value1, key2, value2);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMsetex() {\n    MSetExParams params = new MSetExParams().nx().ex(3);\n    String[] keysvalues = {\"k1\",\"v1\",\"k2\",\"v2\"};\n\n    when(commandObjects.msetex(params, keysvalues)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.msetex(params, keysvalues);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testMsetexBinary() {\n    MSetExParams params = new MSetExParams().xx().keepTtl();\n    byte[] k1 = \"k1\".getBytes();\n    byte[] v1 = \"v1\".getBytes();\n    byte[] k2 = \"k2\".getBytes();\n    byte[] v2 = \"v2\".getBytes();\n\n    when(commandObjects.msetex(params, k1, v1, k2, v2)).thenReturn(booleanCommandObject);\n\n    Response<Boolean> response = pipeliningBase.msetex(params, k1, v1, k2, v2);\n\n    assertThat(commands, contains(booleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPsetex() {\n    when(commandObjects.psetex(\"key\", 100000, \"value\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.psetex(\"key\", 100000, \"value\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testPsetexBinary() {\n    byte[] key = \"key\".getBytes();\n    long milliseconds = 5000L;\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.psetex(key, milliseconds, value)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.psetex(key, milliseconds, value);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSet() {\n    when(commandObjects.set(\"key\", \"value\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.set(\"key\", \"value\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.set(key, value)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.set(key, value);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetWithParams() {\n    SetParams params = new SetParams();\n\n    when(commandObjects.set(\"key\", \"value\", params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.set(\"key\", \"value\", params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    SetParams params = new SetParams().nx().ex(10);\n\n    when(commandObjects.set(key, value, params)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.set(key, value, params);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetGet() {\n    when(commandObjects.setGet(\"key\", \"value\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.setGet(\"key\", \"value\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetGetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.setGet(key, value)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.setGet(key, value);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetGetWithParams() {\n    SetParams setParams = new SetParams();\n\n    when(commandObjects.setGet(\"key\", \"value\", setParams)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.setGet(\"key\", \"value\", setParams);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetGetWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    SetParams params = new SetParams().nx().ex(10);\n\n    when(commandObjects.setGet(key, value, params)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.setGet(key, value, params);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetex() {\n    when(commandObjects.setex(\"key\", 60, \"value\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.setex(\"key\", 60, \"value\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetexBinary() {\n    byte[] key = \"key\".getBytes();\n    long seconds = 60L;\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.setex(key, seconds, value)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.setex(key, seconds, value);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetnx() {\n    when(commandObjects.setnx(\"key\", \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.setnx(\"key\", \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetnxBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.setnx(key, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.setnx(key, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetrange() {\n    when(commandObjects.setrange(\"key\", 100, \"value\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.setrange(\"key\", 100, \"value\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSetrangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long offset = 10L;\n    byte[] value = \"value\".getBytes();\n\n    when(commandObjects.setrange(key, offset, value)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.setrange(key, offset, value);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testStrlen() {\n    when(commandObjects.strlen(\"key\")).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.strlen(\"key\");\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testStrlenBinary() {\n    byte[] key = \"key\".getBytes();\n\n    when(commandObjects.strlen(key)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.strlen(key);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSubstr() {\n    when(commandObjects.substr(\"key\", 0, 10)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.substr(\"key\", 0, 10);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testSubstrBinary() {\n    byte[] key = \"key\".getBytes();\n    int start = 0;\n    int end = 5;\n\n    when(commandObjects.substr(key, start, end)).thenReturn(bytesCommandObject);\n\n    Response<byte[]> response = pipeliningBase.substr(key, start, end);\n\n    assertThat(commands, contains(bytesCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTDigestCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.bloom.TDigestMergeParams;\n\npublic class PipeliningBaseTDigestCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testTdigestAdd() {\n    when(commandObjects.tdigestAdd(\"myTDigest\", 1.0, 2.0, 3.0)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tdigestAdd(\"myTDigest\", 1.0, 2.0, 3.0);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestByRank() {\n    when(commandObjects.tdigestByRank(\"myTDigest\", 1, 2)).thenReturn(listDoubleCommandObject);\n\n    Response<List<Double>> response = pipeliningBase.tdigestByRank(\"myTDigest\", 1, 2);\n\n    assertThat(commands, contains(listDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestByRevRank() {\n    when(commandObjects.tdigestByRevRank(\"myTDigest\", 1, 2)).thenReturn(listDoubleCommandObject);\n\n    Response<List<Double>> response = pipeliningBase.tdigestByRevRank(\"myTDigest\", 1, 2);\n\n    assertThat(commands, contains(listDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestCDF() {\n    when(commandObjects.tdigestCDF(\"myTDigest\", 1.0, 2.0)).thenReturn(listDoubleCommandObject);\n\n    Response<List<Double>> response = pipeliningBase.tdigestCDF(\"myTDigest\", 1.0, 2.0);\n\n    assertThat(commands, contains(listDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestCreate() {\n    when(commandObjects.tdigestCreate(\"myTDigest\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tdigestCreate(\"myTDigest\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestCreateWithCompression() {\n    when(commandObjects.tdigestCreate(\"myTDigest\", 100)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tdigestCreate(\"myTDigest\", 100);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestInfo() {\n    when(commandObjects.tdigestInfo(\"myTDigest\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.tdigestInfo(\"myTDigest\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestMax() {\n    when(commandObjects.tdigestMax(\"myTDigest\")).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.tdigestMax(\"myTDigest\");\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestMerge() {\n    when(commandObjects.tdigestMerge(\"destinationTDigest\", \"sourceTDigest1\", \"sourceTDigest2\"))\n        .thenReturn(stringCommandObject);\n\n    Response<String> response =\n        pipeliningBase.tdigestMerge(\"destinationTDigest\", \"sourceTDigest1\", \"sourceTDigest2\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestMergeWithParams() {\n    TDigestMergeParams mergeParams = new TDigestMergeParams().compression(100);\n\n    when(commandObjects.tdigestMerge(mergeParams, \"destinationTDigest\", \"sourceTDigest1\", \"sourceTDigest2\"))\n        .thenReturn(stringCommandObject);\n\n    Response<String> response =\n        pipeliningBase.tdigestMerge(mergeParams, \"destinationTDigest\", \"sourceTDigest1\", \"sourceTDigest2\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestMin() {\n    when(commandObjects.tdigestMin(\"myTDigest\")).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.tdigestMin(\"myTDigest\");\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestQuantile() {\n    when(commandObjects.tdigestQuantile(\"myTDigest\", 0.5, 0.9)).thenReturn(listDoubleCommandObject);\n\n    Response<List<Double>> response = pipeliningBase.tdigestQuantile(\"myTDigest\", 0.5, 0.9);\n\n    assertThat(commands, contains(listDoubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestRank() {\n    when(commandObjects.tdigestRank(\"myTDigest\", 1.0, 2.0)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.tdigestRank(\"myTDigest\", 1.0, 2.0);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestReset() {\n    when(commandObjects.tdigestReset(\"myTDigest\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tdigestReset(\"myTDigest\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestRevRank() {\n    when(commandObjects.tdigestRevRank(\"myTDigest\", 1.0, 2.0)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.tdigestRevRank(\"myTDigest\", 1.0, 2.0);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTdigestTrimmedMean() {\n    when(commandObjects.tdigestTrimmedMean(\"myTDigest\", 0.1, 0.9)).thenReturn(doubleCommandObject);\n\n    Response<Double> response = pipeliningBase.tdigestTrimmedMean(\"myTDigest\", 0.1, 0.9);\n\n    assertThat(commands, contains(doubleCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.timeseries.*;\n\npublic class PipeliningBaseTimeSeriesCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testTsAdd() {\n    when(commandObjects.tsAdd(\"myTimeSeries\", 42.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsAdd(\"myTimeSeries\", 42.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsAddWithTimestamp() {\n    when(commandObjects.tsAdd(\"myTimeSeries\", 1000L, 42.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsAdd(\"myTimeSeries\", 1000L, 42.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsAddWithTimestampAndParams() {\n    TSCreateParams createParams = TSCreateParams.createParams();\n\n    when(commandObjects.tsAdd(\"myTimeSeries\", 1000L, 42.0, createParams)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsAdd(\"myTimeSeries\", 1000L, 42.0, createParams);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsAddWithParams() {\n    TSAddParams addParams = mock(TSAddParams.class);\n\n    when(commandObjects.tsAdd(\"myTimeSeries\", 1000L, 42.0, addParams)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsAdd(\"myTimeSeries\", 1000L, 42.0, addParams);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsAlter() {\n    TSAlterParams alterParams = TSAlterParams.alterParams();\n\n    when(commandObjects.tsAlter(\"myTimeSeries\", alterParams)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tsAlter(\"myTimeSeries\", alterParams);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsCreate() {\n    when(commandObjects.tsCreate(\"myTimeSeries\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tsCreate(\"myTimeSeries\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsCreateWithParams() {\n    TSCreateParams createParams = TSCreateParams.createParams();\n\n    when(commandObjects.tsCreate(\"myTimeSeries\", createParams)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tsCreate(\"myTimeSeries\", createParams);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsCreateRule() {\n    AggregationType aggregationType = AggregationType.AVG;\n    long timeBucket = 60;\n\n    when(commandObjects.tsCreateRule(\"sourceTimeSeries\", \"destTimeSeries\", aggregationType, timeBucket)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tsCreateRule(\"sourceTimeSeries\", \"destTimeSeries\", aggregationType, timeBucket);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsCreateRuleWithAlignTimestamp() {\n    AggregationType aggregationType = AggregationType.AVG;\n    long bucketDuration = 60;\n    long alignTimestamp = 0;\n\n    when(commandObjects.tsCreateRule(\"sourceTimeSeries\", \"destTimeSeries\", aggregationType, bucketDuration, alignTimestamp)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tsCreateRule(\"sourceTimeSeries\", \"destTimeSeries\", aggregationType, bucketDuration, alignTimestamp);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsDecrBy() {\n    when(commandObjects.tsDecrBy(\"myTimeSeries\", 1.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsDecrBy(\"myTimeSeries\", 1.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsDecrByWithTimestamp() {\n    when(commandObjects.tsDecrBy(\"myTimeSeries\", 1.0, 1000L)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsDecrBy(\"myTimeSeries\", 1.0, 1000L);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsDecrByWithParams() {\n    TSDecrByParams decrByParams = mock(TSDecrByParams.class);\n    when(commandObjects.tsDecrBy(\"myTimeSeries\", 1.0, decrByParams)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsDecrBy(\"myTimeSeries\", 1.0, decrByParams);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsDel() {\n    when(commandObjects.tsDel(\"myTimeSeries\", 1000L, 2000L)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsDel(\"myTimeSeries\", 1000L, 2000L);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsDeleteRule() {\n    when(commandObjects.tsDeleteRule(\"sourceTimeSeries\", \"destTimeSeries\")).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.tsDeleteRule(\"sourceTimeSeries\", \"destTimeSeries\");\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsGet() {\n    when(commandObjects.tsGet(\"myTimeSeries\")).thenReturn(tsElementCommandObject);\n\n    Response<TSElement> response = pipeliningBase.tsGet(\"myTimeSeries\");\n\n    assertThat(commands, contains(tsElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsGetWithParams() {\n    TSGetParams getParams = TSGetParams.getParams();\n\n    when(commandObjects.tsGet(\"myTimeSeries\", getParams)).thenReturn(tsElementCommandObject);\n\n    Response<TSElement> response = pipeliningBase.tsGet(\"myTimeSeries\", getParams);\n\n    assertThat(commands, contains(tsElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsIncrBy() {\n    when(commandObjects.tsIncrBy(\"myTimeSeries\", 1.0)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsIncrBy(\"myTimeSeries\", 1.0);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsIncrByWithTimestamp() {\n    when(commandObjects.tsIncrBy(\"myTimeSeries\", 1.0, 1000L)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsIncrBy(\"myTimeSeries\", 1.0, 1000L);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsIncrByWithParams() {\n    TSIncrByParams incrByParams = mock(TSIncrByParams.class);\n    when(commandObjects.tsIncrBy(\"myTimeSeries\", 1.0, incrByParams)).thenReturn(longCommandObject);\n\n    Response<Long> response = pipeliningBase.tsIncrBy(\"myTimeSeries\", 1.0, incrByParams);\n\n    assertThat(commands, contains(longCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsInfo() {\n    when(commandObjects.tsInfo(\"myTimeSeries\")).thenReturn(tsInfoCommandObject);\n\n    Response<TSInfo> response = pipeliningBase.tsInfo(\"myTimeSeries\");\n\n    assertThat(commands, contains(tsInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsInfoDebug() {\n    when(commandObjects.tsInfoDebug(\"myTimeSeries\")).thenReturn(tsInfoCommandObject);\n\n    Response<TSInfo> response = pipeliningBase.tsInfoDebug(\"myTimeSeries\");\n\n    assertThat(commands, contains(tsInfoCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsMAdd() {\n    Map.Entry<String, TSElement> entry1 = new AbstractMap.SimpleEntry<>(\"ts1\", new TSElement(1000L, 1.0));\n    Map.Entry<String, TSElement> entry2 = new AbstractMap.SimpleEntry<>(\"ts2\", new TSElement(2000L, 2.0));\n\n    when(commandObjects.tsMAdd(entry1, entry2)).thenReturn(listLongCommandObject);\n\n    Response<List<Long>> response = pipeliningBase.tsMAdd(entry1, entry2);\n\n    assertThat(commands, contains(listLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsMGet() {\n    TSMGetParams multiGetParams = TSMGetParams.multiGetParams();\n    String[] filters = { \"sensor_id=123\" };\n\n    when(commandObjects.tsMGet(multiGetParams, filters)).thenReturn(mapStringTsmGetElementCommandObject);\n\n    Response<Map<String, TSMGetElement>> response = pipeliningBase.tsMGet(multiGetParams, filters);\n\n    assertThat(commands, contains(mapStringTsmGetElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsMRange() {\n    String[] filters = { \"sensor_id=123\" };\n\n    when(commandObjects.tsMRange(1000L, 2000L, filters)).thenReturn(mapStringTsmRangeElementsCommandObject);\n\n    Response<Map<String, TSMRangeElements>> response = pipeliningBase.tsMRange(1000L, 2000L, filters);\n\n    assertThat(commands, contains(mapStringTsmRangeElementsCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsMRangeWithParams() {\n    TSMRangeParams multiRangeParams = TSMRangeParams.multiRangeParams();\n\n    when(commandObjects.tsMRange(multiRangeParams)).thenReturn(mapStringTsmRangeElementsCommandObject);\n\n    Response<Map<String, TSMRangeElements>> response = pipeliningBase.tsMRange(multiRangeParams);\n\n    assertThat(commands, contains(mapStringTsmRangeElementsCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsMRevRange() {\n    String[] filters = { \"sensor_id=123\" };\n\n    when(commandObjects.tsMRevRange(1000L, 2000L, filters)).thenReturn(mapStringTsmRangeElementsCommandObject);\n\n    Response<Map<String, TSMRangeElements>> response = pipeliningBase.tsMRevRange(1000L, 2000L, filters);\n\n    assertThat(commands, contains(mapStringTsmRangeElementsCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsMRevRangeWithParams() {\n    TSMRangeParams multiRangeParams = TSMRangeParams.multiRangeParams();\n\n    when(commandObjects.tsMRevRange(multiRangeParams)).thenReturn(mapStringTsmRangeElementsCommandObject);\n\n    Response<Map<String, TSMRangeElements>> response = pipeliningBase.tsMRevRange(multiRangeParams);\n\n    assertThat(commands, contains(mapStringTsmRangeElementsCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsQueryIndex() {\n    String[] filters = { \"sensor_id=123\" };\n\n    when(commandObjects.tsQueryIndex(filters)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.tsQueryIndex(filters);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsRange() {\n    when(commandObjects.tsRange(\"myTimeSeries\", 1000L, 2000L)).thenReturn(listTsElementCommandObject);\n\n    Response<List<TSElement>> response = pipeliningBase.tsRange(\"myTimeSeries\", 1000L, 2000L);\n\n    assertThat(commands, contains(listTsElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsRangeWithParams() {\n    TSRangeParams rangeParams = TSRangeParams.rangeParams();\n\n    when(commandObjects.tsRange(\"myTimeSeries\", rangeParams)).thenReturn(listTsElementCommandObject);\n\n    Response<List<TSElement>> response = pipeliningBase.tsRange(\"myTimeSeries\", rangeParams);\n\n    assertThat(commands, contains(listTsElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsRevRange() {\n    when(commandObjects.tsRevRange(\"myTimeSeries\", 1000L, 2000L)).thenReturn(listTsElementCommandObject);\n\n    Response<List<TSElement>> response = pipeliningBase.tsRevRange(\"myTimeSeries\", 1000L, 2000L);\n\n    assertThat(commands, contains(listTsElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTsRevRangeWithParams() {\n    TSRangeParams rangeParams = TSRangeParams.rangeParams();\n\n    when(commandObjects.tsRevRange(\"myTimeSeries\", rangeParams)).thenReturn(listTsElementCommandObject);\n\n    Response<List<TSElement>> response = pipeliningBase.tsRevRange(\"myTimeSeries\", rangeParams);\n\n    assertThat(commands, contains(listTsElementCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTopKCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.when;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Response;\n\npublic class PipeliningBaseTopKCommandsTest extends PipeliningBaseMockedTestBase {\n\n  @Test\n  public void testTopkAdd() {\n    when(commandObjects.topkAdd(\"myTopK\", \"item1\", \"item2\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.topkAdd(\"myTopK\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkIncrBy() {\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"item1\", 1L);\n    itemIncrements.put(\"item2\", 2L);\n\n    when(commandObjects.topkIncrBy(\"myTopK\", itemIncrements)).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.topkIncrBy(\"myTopK\", itemIncrements);\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkInfo() {\n    when(commandObjects.topkInfo(\"myTopK\")).thenReturn(mapStringObjectCommandObject);\n\n    Response<Map<String, Object>> response = pipeliningBase.topkInfo(\"myTopK\");\n\n    assertThat(commands, contains(mapStringObjectCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkList() {\n    when(commandObjects.topkList(\"myTopK\")).thenReturn(listStringCommandObject);\n\n    Response<List<String>> response = pipeliningBase.topkList(\"myTopK\");\n\n    assertThat(commands, contains(listStringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkListWithCount() {\n    when(commandObjects.topkListWithCount(\"myTopK\")).thenReturn(mapStringLongCommandObject);\n\n    Response<Map<String, Long>> response = pipeliningBase.topkListWithCount(\"myTopK\");\n\n    assertThat(commands, contains(mapStringLongCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkQuery() {\n    when(commandObjects.topkQuery(\"myTopK\", \"item1\", \"item2\")).thenReturn(listBooleanCommandObject);\n\n    Response<List<Boolean>> response = pipeliningBase.topkQuery(\"myTopK\", \"item1\", \"item2\");\n\n    assertThat(commands, contains(listBooleanCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkReserve() {\n    when(commandObjects.topkReserve(\"myTopK\", 3L)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.topkReserve(\"myTopK\", 3L);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n  @Test\n  public void testTopkReserveWithParams() {\n    long width = 50L;\n    long depth = 5L;\n    double decay = 0.9;\n\n    when(commandObjects.topkReserve(\"myTopK\", 3L, width, depth, decay)).thenReturn(stringCommandObject);\n\n    Response<String> response = pipeliningBase.topkReserve(\"myTopK\", 3L, width, depth, decay);\n\n    assertThat(commands, contains(stringCommandObject));\n    assertThat(response, is(predefinedResponse));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisBitmapCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.args.BitCountOption;\nimport redis.clients.jedis.args.BitOP;\nimport redis.clients.jedis.params.BitPosParams;\n\npublic class UnifiedJedisBitmapCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testBitcount() {\n    String key = \"key\";\n    long expectedCount = 4L;\n\n    when(commandObjects.bitcount(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.bitcount(key);\n\n    assertThat(result, sameInstance(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitcount(key);\n  }\n\n  @Test\n  public void testBitcountBinary() {\n    byte[] key = \"key\".getBytes();\n    long expectedCount = 4L;\n\n    when(commandObjects.bitcount(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.bitcount(key);\n\n    assertThat(result, sameInstance(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitcount(key);\n  }\n\n  @Test\n  public void testBitcountRange() {\n    String key = \"key\";\n    long start = 1L;\n    long end = 2L;\n    long expectedCount = 2L;\n\n    when(commandObjects.bitcount(key, start, end)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.bitcount(key, start, end);\n\n    assertThat(result, sameInstance(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitcount(key, start, end);\n  }\n\n  @Test\n  public void testBitcountRangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long start = 1L;\n    long end = 2L;\n    long expectedCount = 2L;\n\n    when(commandObjects.bitcount(key, start, end)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.bitcount(key, start, end);\n\n    assertThat(result, sameInstance(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitcount(key, start, end);\n  }\n\n  @Test\n  public void testBitcountRangeOption() {\n    String key = \"key\";\n    long start = 1L;\n    long end = 2L;\n    BitCountOption option = BitCountOption.BYTE;\n    long expectedCount = 2L;\n\n    when(commandObjects.bitcount(key, start, end, option)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.bitcount(key, start, end, option);\n\n    assertThat(result, sameInstance(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitcount(key, start, end, option);\n  }\n\n  @Test\n  public void testBitcountRangeOptionBinary() {\n    byte[] key = \"key\".getBytes();\n    long start = 1L;\n    long end = 2L;\n    BitCountOption option = BitCountOption.BYTE;\n    long expectedCount = 2L;\n\n    when(commandObjects.bitcount(key, start, end, option)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.bitcount(key, start, end, option);\n\n    assertThat(result, sameInstance(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitcount(key, start, end, option);\n  }\n\n  @Test\n  public void testBitfield() {\n    String key = \"key\";\n    String[] arguments = { \"INCRBY\", \"mykey\", \"1\", \"1000\" };\n    List<Long> expectedResults = Arrays.asList(1000L, 2000L);\n\n    when(commandObjects.bitfield(key, arguments)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResults);\n\n    List<Long> results = jedis.bitfield(key, arguments);\n\n    assertThat(results, sameInstance(expectedResults));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).bitfield(key, arguments);\n  }\n\n  @Test\n  public void testBitfieldBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[][] arguments = { \"INCRBY\".getBytes(), \"mykey\".getBytes(), \"1\".getBytes(), \"1000\".getBytes() };\n    List<Long> expectedResults = Arrays.asList(1000L, 2000L);\n\n    when(commandObjects.bitfield(key, arguments)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResults);\n\n    List<Long> results = jedis.bitfield(key, arguments);\n\n    assertThat(results, sameInstance(expectedResults));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).bitfield(key, arguments);\n  }\n\n  @Test\n  public void testBitfieldReadonly() {\n    String key = \"key\";\n    String[] arguments = { \"GET\", \"u4\", \"0\" };\n    List<Long> expectedResults = Collections.singletonList(15L);\n\n    when(commandObjects.bitfieldReadonly(key, arguments)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResults);\n\n    List<Long> results = jedis.bitfieldReadonly(key, arguments);\n\n    assertThat(results, sameInstance(expectedResults));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).bitfieldReadonly(key, arguments);\n  }\n\n  @Test\n  public void testBitfieldReadonlyBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[][] arguments = { \"GET\".getBytes(), \"u4\".getBytes(), \"0\".getBytes() };\n    List<Long> expectedResults = Collections.singletonList(15L);\n\n    when(commandObjects.bitfieldReadonly(key, arguments)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResults);\n\n    List<Long> results = jedis.bitfieldReadonly(key, arguments);\n\n    assertThat(results, sameInstance(expectedResults));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).bitfieldReadonly(key, arguments);\n  }\n\n  @Test\n  public void testBitop() {\n    BitOP op = BitOP.OR;\n    String destKey = \"destKey\";\n    String[] srcKeys = { \"srcKey1\", \"srcKey2\" };\n    long expectedResponse = 3L;\n\n    when(commandObjects.bitop(op, destKey, srcKeys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.bitop(op, destKey, srcKeys);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitop(op, destKey, srcKeys);\n  }\n\n  @Test\n  public void testBitopBinary() {\n    BitOP op = BitOP.XOR;\n    byte[] destKey = \"destKey\".getBytes();\n    byte[][] srcKeys = { \"srcKey1\".getBytes(), \"srcKey2\".getBytes() };\n    long expectedResponse = 4L;\n\n    when(commandObjects.bitop(op, destKey, srcKeys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.bitop(op, destKey, srcKeys);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitop(op, destKey, srcKeys);\n  }\n\n  @Test\n  public void testBitpos() {\n    String key = \"key\";\n    boolean value = true; // Looking for the first bit set to 1\n    long expectedPosition = 2L; // Assuming the first bit set to 1 is at position 2\n\n    when(commandObjects.bitpos(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    long result = jedis.bitpos(key, value);\n\n    assertThat(result, sameInstance(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitpos(key, value);\n  }\n\n  @Test\n  public void testBitposBinary() {\n    byte[] key = \"key\".getBytes();\n    boolean value = true; // Looking for the first bit set to 1\n    long expectedPosition = 2L; // Assuming the first bit set to 1 is at position 2\n\n    when(commandObjects.bitpos(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    long result = jedis.bitpos(key, value);\n\n    assertThat(result, sameInstance(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitpos(key, value);\n  }\n\n  @Test\n  public void testBitposParams() {\n    String key = \"key\";\n    boolean value = false; // Looking for the first bit set to 0\n    BitPosParams params = new BitPosParams(1); // Starting the search from byte offset 1\n    long expectedPosition = 8L; // Assuming the first bit set to 0 from offset 1 is at position 8\n\n    when(commandObjects.bitpos(key, value, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    long result = jedis.bitpos(key, value, params);\n\n    assertThat(result, sameInstance(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitpos(key, value, params);\n  }\n\n  @Test\n  public void testBitposParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    boolean value = false; // Looking for the first bit set to 0\n    BitPosParams params = new BitPosParams(1); // Starting the search from byte offset 1\n    long expectedPosition = 8L; // Assuming the first bit set to 0 from offset 1 is at position 8\n\n    when(commandObjects.bitpos(key, value, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    long result = jedis.bitpos(key, value, params);\n\n    assertThat(result, sameInstance(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bitpos(key, value, params);\n  }\n\n  @Test\n  public void testGetbit() {\n    String key = \"key\";\n    long offset = 10L;\n    boolean expectedResponse = true;\n\n    when(commandObjects.getbit(key, offset)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.getbit(key, offset);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).getbit(key, offset);\n  }\n\n  @Test\n  public void testGetbitBinary() {\n    byte[] key = \"key\".getBytes();\n    long offset = 10L;\n    boolean expectedResponse = true;\n\n    when(commandObjects.getbit(key, offset)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.getbit(key, offset);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).getbit(key, offset);\n  }\n\n  @Test\n  public void testSetbit() {\n    String key = \"key\";\n    long offset = 10L;\n    boolean value = true;\n    boolean expectedResponse = true;\n\n    when(commandObjects.setbit(key, offset, value)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.setbit(key, offset, value);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).setbit(key, offset, value);\n  }\n\n  @Test\n  public void testSetbitBinary() {\n    byte[] key = \"key\".getBytes();\n    long offset = 10L;\n    boolean value = true;\n    boolean expectedResponse = true;\n\n    when(commandObjects.setbit(key, offset, value)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.setbit(key, offset, value);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).setbit(key, offset, value);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisBloomFilterCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.bloom.BFInsertParams;\nimport redis.clients.jedis.bloom.BFReserveParams;\n\npublic class UnifiedJedisBloomFilterCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testBfAdd() {\n    String key = \"testBloom\";\n    String item = \"item1\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.bfAdd(key, item)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.bfAdd(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).bfAdd(key, item);\n  }\n\n  @Test\n  public void testBfCard() {\n    String key = \"testBloom\";\n    long expectedResponse = 42L;\n\n    when(commandObjects.bfCard(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.bfCard(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).bfCard(key);\n  }\n\n  @Test\n  public void testBfExists() {\n    String key = \"testBloom\";\n    String item = \"item1\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.bfExists(key, item)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.bfExists(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).bfExists(key, item);\n  }\n\n  @Test\n  public void testBfInfo() {\n    String key = \"testBloom\";\n    Map<String, Object> expectedResponse = new HashMap<>();\n    expectedResponse.put(\"size\", 42L);\n    expectedResponse.put(\"numberOfFilters\", 3L);\n    expectedResponse.put(\"insertedItems\", 1000L);\n\n    when(commandObjects.bfInfo(key)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.bfInfo(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).bfInfo(key);\n  }\n\n  @Test\n  public void testBfInsert() {\n    String key = \"testBloom\";\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.bfInsert(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.bfInsert(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).bfInsert(key, items);\n  }\n\n  @Test\n  public void testBfInsertWithParams() {\n    String key = \"testBloom\";\n    BFInsertParams insertParams = new BFInsertParams().noCreate();\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.bfInsert(key, insertParams, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.bfInsert(key, insertParams, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).bfInsert(key, insertParams, items);\n  }\n\n  @Test\n  public void testBfLoadChunk() {\n    String key = \"testBloom\";\n    long iterator = 1L;\n    byte[] data = new byte[]{ 1, 2, 3 };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.bfLoadChunk(key, iterator, data)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.bfLoadChunk(key, iterator, data);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).bfLoadChunk(key, iterator, data);\n  }\n\n  @Test\n  public void testBfMAdd() {\n    String key = \"testBloom\";\n    String[] items = { \"item1\", \"item2\", \"item3\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false, true);\n\n    when(commandObjects.bfMAdd(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.bfMAdd(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).bfMAdd(key, items);\n  }\n\n  @Test\n  public void testBfMExists() {\n    String key = \"testBloom\";\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.bfMExists(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.bfMExists(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).bfMExists(key, items);\n  }\n\n  @Test\n  public void testBfReserve() {\n    String key = \"testBloom\";\n    double errorRate = 0.01;\n    long capacity = 10000L;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.bfReserve(key, errorRate, capacity)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.bfReserve(key, errorRate, capacity);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).bfReserve(key, errorRate, capacity);\n  }\n\n  @Test\n  public void testBfReserveWithParams() {\n    String key = \"testBloom\";\n    double errorRate = 0.01;\n    long capacity = 10000L;\n    BFReserveParams reserveParams = new BFReserveParams().expansion(2);\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.bfReserve(key, errorRate, capacity, reserveParams)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.bfReserve(key, errorRate, capacity, reserveParams);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).bfReserve(key, errorRate, capacity, reserveParams);\n  }\n\n  @Test\n  public void testBfScanDump() {\n    String key = \"testBloom\";\n    long iterator = 0L;\n    Map.Entry<Long, byte[]> expectedResponse = new AbstractMap.SimpleEntry<>(1L, new byte[]{ 1, 2, 3 });\n\n    when(commandObjects.bfScanDump(key, iterator)).thenReturn(entryLongBytesCommandObject);\n    when(commandExecutor.executeCommand(entryLongBytesCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<Long, byte[]> result = jedis.bfScanDump(key, iterator);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entryLongBytesCommandObject);\n    verify(commandObjects).bfScanDump(key, iterator);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisConnectionManagementCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\n\npublic class UnifiedJedisConnectionManagementCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testPing() {\n    when(commandObjects.ping()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"foo\");\n\n    String result = jedis.ping();\n\n    assertThat(result, equalTo(\"foo\"));\n\n    verify(commandObjects).ping();\n    verify(commandExecutor).executeCommand(stringCommandObject);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisCountMinSketchCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\npublic class UnifiedJedisCountMinSketchCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testCmsIncrBy() {\n    String key = \"testCMS\";\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"item1\", 1L);\n    itemIncrements.put(\"item2\", 2L);\n    List<Long> expectedResponse = Arrays.asList(1L, 2L);\n\n    when(commandObjects.cmsIncrBy(key, itemIncrements)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.cmsIncrBy(key, itemIncrements);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).cmsIncrBy(key, itemIncrements);\n  }\n\n  @Test\n  public void testCmsInfo() {\n    String key = \"testCMS\";\n    Map<String, Object> expectedResponse = new HashMap<>();\n    expectedResponse.put(\"width\", 1000L);\n    expectedResponse.put(\"depth\", 5L);\n    expectedResponse.put(\"count\", 42L);\n\n    when(commandObjects.cmsInfo(key)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.cmsInfo(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).cmsInfo(key);\n  }\n\n  @Test\n  public void testCmsInitByDim() {\n    String key = \"testCMS\";\n    long width = 1000L;\n    long depth = 5L;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cmsInitByDim(key, width, depth)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cmsInitByDim(key, width, depth);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cmsInitByDim(key, width, depth);\n  }\n\n  @Test\n  public void testCmsInitByProb() {\n    String key = \"testCMS\";\n    double error = 0.01;\n    double probability = 0.99;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cmsInitByProb(key, error, probability)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cmsInitByProb(key, error, probability);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cmsInitByProb(key, error, probability);\n  }\n\n  @Test\n  public void testCmsMerge() {\n    String destKey = \"destCMS\";\n    String[] keys = { \"cms1\", \"cms2\" };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cmsMerge(destKey, keys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cmsMerge(destKey, keys);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cmsMerge(destKey, keys);\n  }\n\n  @Test\n  public void testCmsMergeWithWeights() {\n    String destKey = \"destCMS\";\n    Map<String, Long> keysAndWeights = new HashMap<>();\n    keysAndWeights.put(\"cms1\", 1L);\n    keysAndWeights.put(\"cms2\", 2L);\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cmsMerge(destKey, keysAndWeights)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cmsMerge(destKey, keysAndWeights);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cmsMerge(destKey, keysAndWeights);\n  }\n\n  @Test\n  public void testCmsQuery() {\n    String key = \"testCMS\";\n    String[] items = { \"item1\", \"item2\" };\n    List<Long> expectedResponse = Arrays.asList(42L, 27L);\n\n    when(commandObjects.cmsQuery(key, items)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.cmsQuery(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).cmsQuery(key, items);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisCuckooFilterCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.bloom.CFInsertParams;\nimport redis.clients.jedis.bloom.CFReserveParams;\n\npublic class UnifiedJedisCuckooFilterCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testCfAdd() {\n    String key = \"testCuckooFilter\";\n    String item = \"item1\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.cfAdd(key, item)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.cfAdd(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).cfAdd(key, item);\n  }\n\n  @Test\n  public void testCfAddNx() {\n    String key = \"testCuckooFilter\";\n    String item = \"item1\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.cfAddNx(key, item)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.cfAddNx(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).cfAddNx(key, item);\n  }\n\n  @Test\n  public void testCfCount() {\n    String key = \"testCuckooFilter\";\n    String item = \"item1\";\n    long expectedResponse = 42L;\n\n    when(commandObjects.cfCount(key, item)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.cfCount(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).cfCount(key, item);\n  }\n\n  @Test\n  public void testCfDel() {\n    String key = \"testCuckooFilter\";\n    String item = \"item1\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.cfDel(key, item)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.cfDel(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).cfDel(key, item);\n  }\n\n  @Test\n  public void testCfExists() {\n    String key = \"testCuckooFilter\";\n    String item = \"item1\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.cfExists(key, item)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.cfExists(key, item);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).cfExists(key, item);\n  }\n\n  @Test\n  public void testCfInfo() {\n    String key = \"testCuckooFilter\";\n    Map<String, Object> expectedResponse = new HashMap<>();\n    expectedResponse.put(\"size\", 42L);\n    expectedResponse.put(\"bucketSize\", 2L);\n    expectedResponse.put(\"maxIterations\", 500L);\n\n    when(commandObjects.cfInfo(key)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.cfInfo(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).cfInfo(key);\n  }\n\n  @Test\n  public void testCfInsert() {\n    String key = \"testCuckooFilter\";\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.cfInsert(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.cfInsert(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).cfInsert(key, items);\n  }\n\n  @Test\n  public void testCfInsertWithParams() {\n    String key = \"testCuckooFilter\";\n    CFInsertParams insertParams = new CFInsertParams().noCreate();\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.cfInsert(key, insertParams, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.cfInsert(key, insertParams, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).cfInsert(key, insertParams, items);\n  }\n\n  @Test\n  public void testCfInsertNx() {\n    String key = \"testCuckooFilter\";\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.cfInsertNx(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.cfInsertNx(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).cfInsertNx(key, items);\n  }\n\n  @Test\n  public void testCfInsertNxWithParams() {\n    String key = \"testCuckooFilter\";\n    CFInsertParams insertParams = new CFInsertParams().noCreate();\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.cfInsertNx(key, insertParams, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.cfInsertNx(key, insertParams, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).cfInsertNx(key, insertParams, items);\n  }\n\n  @Test\n  public void testCfLoadChunk() {\n    String key = \"testCuckooFilter\";\n    long iterator = 1L;\n    byte[] data = new byte[]{ 1, 2, 3 };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cfLoadChunk(key, iterator, data)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cfLoadChunk(key, iterator, data);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cfLoadChunk(key, iterator, data);\n  }\n\n  @Test\n  public void testCfMExists() {\n    String key = \"testCuckooFilter\";\n    String[] items = { \"item1\", \"item2\", \"item3\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false, true);\n\n    when(commandObjects.cfMExists(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.cfMExists(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).cfMExists(key, items);\n  }\n\n  @Test\n  public void testCfReserve() {\n    String key = \"testCuckooFilter\";\n    long capacity = 10000L;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cfReserve(key, capacity)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cfReserve(key, capacity);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cfReserve(key, capacity);\n  }\n\n  @Test\n  public void testCfReserveWithParams() {\n    String key = \"testCuckooFilter\";\n    long capacity = 10000L;\n    CFReserveParams reserveParams = new CFReserveParams().expansion(2);\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.cfReserve(key, capacity, reserveParams)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.cfReserve(key, capacity, reserveParams);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).cfReserve(key, capacity, reserveParams);\n  }\n\n  @Test\n  public void testCfScanDump() {\n    String key = \"testCuckooFilter\";\n    long iterator = 0L;\n    Map.Entry<Long, byte[]> expectedResponse = new AbstractMap.SimpleEntry<>(1L, new byte[]{ 1, 2, 3 });\n\n    when(commandObjects.cfScanDump(key, iterator)).thenReturn(entryLongBytesCommandObject);\n    when(commandExecutor.executeCommand(entryLongBytesCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<Long, byte[]> result = jedis.cfScanDump(key, iterator);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entryLongBytesCommandObject);\n    verify(commandObjects).cfScanDump(key, iterator);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisGenericCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.contains;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ScanIteration;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.MigrateParams;\nimport redis.clients.jedis.params.RestoreParams;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.SortingParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class UnifiedJedisGenericCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testCopy() {\n    String srcKey = \"sourceKey\";\n    String dstKey = \"destinationKey\";\n    boolean replace = true;\n\n    when(commandObjects.copy(srcKey, dstKey, replace)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(true);\n\n    boolean result = jedis.copy(srcKey, dstKey, replace);\n\n    assertTrue(result);\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).copy(srcKey, dstKey, replace);\n  }\n\n  @Test\n  public void testCopyBinary() {\n    byte[] srcKey = new byte[]{ 1, 2, 3 };\n    byte[] dstKey = new byte[]{ 4, 5, 6 };\n    boolean replace = false;\n\n    when(commandObjects.copy(srcKey, dstKey, replace)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(true);\n\n    boolean result = jedis.copy(srcKey, dstKey, replace);\n\n    assertTrue(result);\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).copy(srcKey, dstKey, replace);\n  }\n\n  @Test\n  public void testDel() {\n    String key = \"key1\";\n\n    when(commandObjects.del(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.del(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).del(key);\n  }\n\n  @Test\n  public void testDelBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.del(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.del(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).del(key);\n  }\n\n  @Test\n  public void testDelMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.del(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.del(keys);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).del(keys);\n  }\n\n  @Test\n  public void testDelMultipleKeysBinary() {\n    byte[][] keys = { new byte[]{ 1, 2, 3 }, new byte[]{ 4, 5, 6 } };\n\n    when(commandObjects.del(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(2L);\n\n    long result = jedis.del(keys);\n\n    assertThat(result, equalTo(2L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).del(keys);\n  }\n\n  @Test\n  public void testDump() {\n    String key = \"key1\";\n\n    when(commandObjects.dump(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(new byte[]{ 1, 2, 3 });\n\n    byte[] result = jedis.dump(key);\n\n    assertThat(result, equalTo(new byte[]{ 1, 2, 3 }));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).dump(key);\n  }\n\n  @Test\n  public void testDumpBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.dump(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(new byte[]{ 4, 5, 6 });\n\n    byte[] result = jedis.dump(key);\n\n    assertThat(result, equalTo(new byte[]{ 4, 5, 6 }));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).dump(key);\n  }\n\n  @Test\n  public void testExists() {\n    String key = \"mykey\";\n\n    when(commandObjects.exists(key)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(true);\n\n    boolean result = jedis.exists(key);\n\n    assertTrue(result);\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).exists(key);\n  }\n\n  @Test\n  public void testExistsBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.exists(key)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(true);\n\n    boolean result = jedis.exists(key);\n\n    assertTrue(result);\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).exists(key);\n  }\n\n  @Test\n  public void testExistsMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.exists(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.exists(keys);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).exists(keys);\n  }\n\n  @Test\n  public void testExistsMultipleKeysBinary() {\n    byte[][] keys = { new byte[]{ 1, 2, 3 }, new byte[]{ 4, 5, 6 } };\n\n    when(commandObjects.exists(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(2L);\n\n    long result = jedis.exists(keys);\n\n    assertThat(result, equalTo(2L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).exists(keys);\n  }\n\n  @Test\n  public void testExpire() {\n    String key = \"key1\";\n    long seconds = 60L;\n\n    when(commandObjects.expire(key, seconds)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expire(key, seconds);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expire(key, seconds);\n  }\n\n  @Test\n  public void testExpireBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long seconds = 60L;\n\n    when(commandObjects.expire(key, seconds)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expire(key, seconds);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expire(key, seconds);\n  }\n\n  @Test\n  public void testExpireWithExpiryOption() {\n    String key = \"key1\";\n    long seconds = 60L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.expire(key, seconds, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expire(key, seconds, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expire(key, seconds, expiryOption);\n  }\n\n  @Test\n  public void testExpireWithExpiryOptionBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long seconds = 60L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.expire(key, seconds, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expire(key, seconds, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expire(key, seconds, expiryOption);\n  }\n\n  @Test\n  public void testExpireAt() {\n    String key = \"key1\";\n    long unixTime = 1633072800L;\n\n    when(commandObjects.expireAt(key, unixTime)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expireAt(key, unixTime);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expireAt(key, unixTime);\n  }\n\n  @Test\n  public void testExpireAtBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long unixTime = 1633072800L;\n\n    when(commandObjects.expireAt(key, unixTime)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expireAt(key, unixTime);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expireAt(key, unixTime);\n  }\n\n  @Test\n  public void testExpireAtWithExpiryOption() {\n    String key = \"key1\";\n    long unixTime = 1633072800L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.expireAt(key, unixTime, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expireAt(key, unixTime, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expireAt(key, unixTime, expiryOption);\n  }\n\n  @Test\n  public void testExpireAtWithExpiryOptionBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long unixTime = 1633072800L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.expireAt(key, unixTime, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.expireAt(key, unixTime, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expireAt(key, unixTime, expiryOption);\n  }\n\n  @Test\n  public void testExpireTime() {\n    String key = \"key1\";\n\n    when(commandObjects.expireTime(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1234567890L);\n\n    long result = jedis.expireTime(key);\n\n    assertThat(result, equalTo(1234567890L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expireTime(key);\n  }\n\n  @Test\n  public void testExpireTimeBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.expireTime(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1234567890L);\n\n    long result = jedis.expireTime(key);\n\n    assertThat(result, equalTo(1234567890L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).expireTime(key);\n  }\n\n  @Test\n  public void testKeys() {\n    String pattern = \"*\";\n    Set<String> expectedKeys = new HashSet<>(Arrays.asList(\"key1\", \"key2\", \"key3\"));\n\n    when(commandObjects.keys(pattern)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedKeys);\n\n    Set<String> result = jedis.keys(pattern);\n\n    assertThat(result, equalTo(expectedKeys));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).keys(pattern);\n  }\n\n  @Test\n  public void testKeysBinary() {\n    byte[] pattern = \"key*\".getBytes();\n    Set<byte[]> expectedKeys = new HashSet<>(Arrays.asList(\"key1\".getBytes(), \"key2\".getBytes()));\n\n    when(commandObjects.keys(pattern)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedKeys);\n\n    Set<byte[]> result = jedis.keys(pattern);\n\n    assertThat(result, equalTo(expectedKeys));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).keys(pattern);\n  }\n\n  @Test\n  public void testMigrate() {\n    String host = \"destinationHost\";\n    int port = 6379;\n    String key = \"myKey\";\n    int timeout = 5000;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.migrate(host, port, key, timeout)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.migrate(host, port, key, timeout);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).migrate(host, port, key, timeout);\n  }\n\n  @Test\n  public void testMigrateBinary() {\n    String host = \"destinationHost\";\n    int port = 6379;\n    byte[] key = \"myKey\".getBytes();\n    int timeout = 5000;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.migrate(host, port, key, timeout)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.migrate(host, port, key, timeout);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).migrate(host, port, key, timeout);\n  }\n\n  @Test\n  public void testMigrateMultipleKeys() {\n    String host = \"destinationHost\";\n    int port = 6379;\n    int timeout = 5000;\n    MigrateParams params = new MigrateParams();\n    String[] keys = { \"key1\", \"key2\" };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.migrate(host, port, timeout, params, keys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.migrate(host, port, timeout, params, keys);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).migrate(host, port, timeout, params, keys);\n  }\n\n  @Test\n  public void testMigrateMultipleKeysBinary() {\n    String host = \"destinationHost\";\n    int port = 6379;\n    int timeout = 5000;\n    MigrateParams params = new MigrateParams();\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes() };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.migrate(host, port, timeout, params, keys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.migrate(host, port, timeout, params, keys);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).migrate(host, port, timeout, params, keys);\n  }\n\n  @Test\n  public void testObjectEncoding() {\n    String key = \"myKey\";\n    String expectedEncoding = \"ziplist\";\n\n    when(commandObjects.objectEncoding(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedEncoding);\n\n    String result = jedis.objectEncoding(key);\n\n    assertThat(result, equalTo(expectedEncoding));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).objectEncoding(key);\n  }\n\n  @Test\n  public void testObjectEncodingBinary() {\n    byte[] key = \"myKey\".getBytes();\n    byte[] expectedEncoding = \"ziplist\".getBytes();\n\n    when(commandObjects.objectEncoding(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedEncoding);\n\n    byte[] result = jedis.objectEncoding(key);\n\n    assertThat(result, equalTo(expectedEncoding));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).objectEncoding(key);\n  }\n\n  @Test\n  public void testObjectFreq() {\n    String key = \"myKey\";\n    Long expectedFreq = 10L;\n\n    when(commandObjects.objectFreq(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedFreq);\n\n    Long result = jedis.objectFreq(key);\n\n    assertThat(result, equalTo(expectedFreq));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).objectFreq(key);\n  }\n\n  @Test\n  public void testObjectFreqBinary() {\n    byte[] key = \"myKey\".getBytes();\n    Long expectedFreq = 10L;\n\n    when(commandObjects.objectFreq(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedFreq);\n\n    Long result = jedis.objectFreq(key);\n\n    assertThat(result, equalTo(expectedFreq));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).objectFreq(key);\n  }\n\n  @Test\n  public void testObjectIdletime() {\n    String key = \"myKey\";\n    Long expectedIdletime = 3600L;\n\n    when(commandObjects.objectIdletime(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedIdletime);\n\n    Long result = jedis.objectIdletime(key);\n\n    assertThat(result, equalTo(expectedIdletime));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).objectIdletime(key);\n  }\n\n  @Test\n  public void testObjectIdletimeBinary() {\n    byte[] key = \"myKey\".getBytes();\n    Long expectedIdletime = 3600L;\n\n    when(commandObjects.objectIdletime(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedIdletime);\n\n    Long result = jedis.objectIdletime(key);\n\n    assertThat(result, equalTo(expectedIdletime));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).objectIdletime(key);\n  }\n\n  @Test\n  public void testObjectRefcount() {\n    String key = \"myKey\";\n    Long expectedRefcount = 42L;\n\n    when(commandObjects.objectRefcount(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRefcount);\n\n    Long result = jedis.objectRefcount(key);\n\n    assertThat(result, equalTo(expectedRefcount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).objectRefcount(key);\n  }\n\n  @Test\n  public void testObjectRefcountBinary() {\n    byte[] key = \"myKey\".getBytes();\n    Long expectedRefcount = 42L;\n\n    when(commandObjects.objectRefcount(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRefcount);\n\n    Long result = jedis.objectRefcount(key);\n\n    assertThat(result, equalTo(expectedRefcount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).objectRefcount(key);\n  }\n\n  @Test\n  public void testPersist() {\n    String key = \"key1\";\n\n    when(commandObjects.persist(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.persist(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).persist(key);\n  }\n\n  @Test\n  public void testPersistBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.persist(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.persist(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).persist(key);\n  }\n\n  @Test\n  public void testPexpire() {\n    String key = \"key1\";\n    long milliseconds = 1000L;\n\n    when(commandObjects.pexpire(key, milliseconds)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpire(key, milliseconds);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpire(key, milliseconds);\n  }\n\n  @Test\n  public void testPexpireBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long milliseconds = 1000L;\n\n    when(commandObjects.pexpire(key, milliseconds)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpire(key, milliseconds);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpire(key, milliseconds);\n  }\n\n  @Test\n  public void testPexpireWithExpiryOption() {\n    String key = \"key1\";\n    long milliseconds = 1000L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.pexpire(key, milliseconds, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpire(key, milliseconds, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpire(key, milliseconds, expiryOption);\n  }\n\n  @Test\n  public void testPexpireWithExpiryOptionBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long milliseconds = 1000L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.pexpire(key, milliseconds, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpire(key, milliseconds, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpire(key, milliseconds, expiryOption);\n  }\n\n  @Test\n  public void testPexpireAt() {\n    String key = \"key1\";\n    long millisecondsTimestamp = 1633072800123L;\n\n    when(commandObjects.pexpireAt(key, millisecondsTimestamp)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpireAt(key, millisecondsTimestamp);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpireAt(key, millisecondsTimestamp);\n  }\n\n  @Test\n  public void testPexpireAtBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long millisecondsTimestamp = 1633072800123L;\n\n    when(commandObjects.pexpireAt(key, millisecondsTimestamp)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpireAt(key, millisecondsTimestamp);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpireAt(key, millisecondsTimestamp);\n  }\n\n  @Test\n  public void testPexpireAtWithExpiryOption() {\n    String key = \"key1\";\n    long millisecondsTimestamp = 1633072800123L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpireAt(key, millisecondsTimestamp, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpireAt(key, millisecondsTimestamp, expiryOption);\n  }\n\n  @Test\n  public void testPexpireAtWithExpiryOptionBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long millisecondsTimestamp = 1633072800123L;\n    ExpiryOption expiryOption = ExpiryOption.NX;\n\n    when(commandObjects.pexpireAt(key, millisecondsTimestamp, expiryOption)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.pexpireAt(key, millisecondsTimestamp, expiryOption);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpireAt(key, millisecondsTimestamp, expiryOption);\n  }\n\n  @Test\n  public void testPexpireTime() {\n    String key = \"key1\";\n\n    when(commandObjects.pexpireTime(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1234567890123L);\n\n    long result = jedis.pexpireTime(key);\n\n    assertThat(result, equalTo(1234567890123L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpireTime(key);\n  }\n\n  @Test\n  public void testPexpireTimeBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.pexpireTime(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1234567890123L);\n\n    long result = jedis.pexpireTime(key);\n\n    assertThat(result, equalTo(1234567890123L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pexpireTime(key);\n  }\n\n  @Test\n  public void testPttl() {\n    String key = \"key1\";\n\n    when(commandObjects.pttl(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(120000L);\n\n    long result = jedis.pttl(key);\n\n    assertThat(result, equalTo(120000L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pttl(key);\n  }\n\n  @Test\n  public void testPttlBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.pttl(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(120000L);\n\n    long result = jedis.pttl(key);\n\n    assertThat(result, equalTo(120000L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pttl(key);\n  }\n\n  @Test\n  public void testRandomKey() {\n    String expectedKey = \"randomKey\";\n\n    when(commandObjects.randomKey()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedKey);\n\n    String result = jedis.randomKey();\n\n    assertThat(result, equalTo(expectedKey));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).randomKey();\n  }\n\n  @Test\n  public void testRandomBinaryKey() {\n    byte[] expectedKey = \"randomKey\".getBytes();\n\n    when(commandObjects.randomBinaryKey()).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedKey);\n\n    byte[] result = jedis.randomBinaryKey();\n\n    assertThat(result, equalTo(expectedKey));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).randomBinaryKey();\n  }\n\n  @Test\n  public void testRename() {\n    String oldkey = \"oldKey\";\n    String newkey = \"newKey\";\n    String expectedStatus = \"OK\";\n\n    when(commandObjects.rename(oldkey, newkey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedStatus);\n\n    String result = jedis.rename(oldkey, newkey);\n\n    assertEquals(expectedStatus, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).rename(oldkey, newkey);\n  }\n\n  @Test\n  public void testRenameBinary() {\n    byte[] oldkey = new byte[]{ 1, 2, 3 };\n    byte[] newkey = new byte[]{ 4, 5, 6 };\n    String expectedStatus = \"OK\";\n\n    when(commandObjects.rename(oldkey, newkey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedStatus);\n\n    String result = jedis.rename(oldkey, newkey);\n\n    assertEquals(expectedStatus, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).rename(oldkey, newkey);\n  }\n\n  @Test\n  public void testRenamenx() {\n    String oldkey = \"oldKey\";\n    String newkey = \"newKey\";\n    long expected = 1L;\n\n    when(commandObjects.renamenx(oldkey, newkey)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.renamenx(oldkey, newkey);\n\n    assertEquals(expected, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).renamenx(oldkey, newkey);\n  }\n\n  @Test\n  public void testRenamenxBinary() {\n    byte[] oldkey = new byte[]{ 1, 2, 3 };\n    byte[] newkey = new byte[]{ 4, 5, 6 };\n    long expected = 1L;\n\n    when(commandObjects.renamenx(oldkey, newkey)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.renamenx(oldkey, newkey);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).renamenx(oldkey, newkey);\n  }\n\n  @Test\n  public void testRestore() {\n    String key = \"key1\";\n    long ttl = 0L;\n    byte[] serializedValue = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.restore(key, ttl, serializedValue)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.restore(key, ttl, serializedValue);\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).restore(key, ttl, serializedValue);\n  }\n\n  @Test\n  public void testRestoreBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long ttl = 1000L;\n    byte[] serializedValue = new byte[]{ 4, 5, 6 };\n\n    when(commandObjects.restore(key, ttl, serializedValue)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.restore(key, ttl, serializedValue);\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).restore(key, ttl, serializedValue);\n  }\n\n  @Test\n  public void testRestoreWithParams() {\n    String key = \"key1\";\n    long ttl = 0L;\n    byte[] serializedValue = new byte[]{ 1, 2, 3 };\n    RestoreParams params = new RestoreParams();\n\n    when(commandObjects.restore(key, ttl, serializedValue, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.restore(key, ttl, serializedValue, params);\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).restore(key, ttl, serializedValue, params);\n\n  }\n\n  @Test\n  public void testRestoreWithParamsBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    long ttl = 1000L;\n    byte[] serializedValue = new byte[]{ 4, 5, 6 };\n    RestoreParams params = new RestoreParams();\n\n    when(commandObjects.restore(key, ttl, serializedValue, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.restore(key, ttl, serializedValue, params);\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).restore(key, ttl, serializedValue, params);\n  }\n\n  @Test\n  public void testScan() {\n    String cursor = \"0\";\n    ScanResult<String> expectedScanResult = new ScanResult<>(cursor, Arrays.asList(\"key1\", \"key2\"));\n\n    when(commandObjects.scan(cursor)).thenReturn(scanResultStringCommandObject);\n    when(commandExecutor.executeCommand(scanResultStringCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<String> result = jedis.scan(cursor);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultStringCommandObject);\n    verify(commandObjects).scan(cursor);\n  }\n\n  @Test\n  public void testScanBinary() {\n    byte[] cursor = \"0\".getBytes();\n    ScanResult<byte[]> expectedScanResult = new ScanResult<>(cursor, Arrays.asList(\"key1\".getBytes(), \"key2\".getBytes()));\n\n    when(commandObjects.scan(cursor)).thenReturn(scanResultBytesCommandObject);\n    when(commandExecutor.executeCommand(scanResultBytesCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<byte[]> result = jedis.scan(cursor);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultBytesCommandObject);\n    verify(commandObjects).scan(cursor);\n  }\n\n  @Test\n  public void testScanWithParams() {\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    ScanResult<String> expectedScanResult = new ScanResult<>(cursor, Arrays.asList(\"key1\", \"key2\"));\n\n    when(commandObjects.scan(cursor, params)).thenReturn(scanResultStringCommandObject);\n    when(commandExecutor.executeCommand(scanResultStringCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<String> result = jedis.scan(cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultStringCommandObject);\n    verify(commandObjects).scan(cursor, params);\n  }\n\n  @Test\n  public void testScanWithParamsBinary() {\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\".getBytes()).count(10);\n    ScanResult<byte[]> expectedScanResult = new ScanResult<>(cursor, Arrays.asList(\"key1\".getBytes(), \"key2\".getBytes()));\n\n    when(commandObjects.scan(cursor, params)).thenReturn(scanResultBytesCommandObject);\n    when(commandExecutor.executeCommand(scanResultBytesCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<byte[]> result = jedis.scan(cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultBytesCommandObject);\n    verify(commandObjects).scan(cursor, params);\n  }\n\n  @Test\n  public void testScanWithType() {\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    String type = \"hash\";\n    ScanResult<String> expectedScanResult = new ScanResult<>(cursor, Arrays.asList(\"key1\", \"key2\"));\n\n    when(commandObjects.scan(cursor, params, type)).thenReturn(scanResultStringCommandObject);\n    when(commandExecutor.executeCommand(scanResultStringCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<String> result = jedis.scan(cursor, params, type);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultStringCommandObject);\n    verify(commandObjects).scan(cursor, params, type);\n  }\n\n  @Test\n  public void testScanWithTypeBinary() {\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\".getBytes()).count(10);\n    byte[] type = \"string\".getBytes();\n    ScanResult<byte[]> expectedScanResult = new ScanResult<>(cursor, Arrays.asList(\"key1\".getBytes(), \"key2\".getBytes()));\n\n    when(commandObjects.scan(cursor, params, type)).thenReturn(scanResultBytesCommandObject);\n    when(commandExecutor.executeCommand(scanResultBytesCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<byte[]> result = jedis.scan(cursor, params, type);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultBytesCommandObject);\n    verify(commandObjects).scan(cursor, params, type);\n  }\n\n  @Test\n  public void testScanIteration() {\n    String cursor = \"0\";\n    String key1 = \"key1\";\n    String key2 = \"key2\";\n\n    Connection connection = mock(Connection.class);\n    when(connection.executeCommand(any(CommandArguments.class)))\n        .thenReturn(Arrays.asList(cursor.getBytes(), Arrays.asList(key1.getBytes(), key2.getBytes())));\n\n    when(connectionProvider.getConnectionMap()).thenAnswer(new Answer<Map<?, ?>>() {\n      @Override\n      public Map<?, ?> answer(InvocationOnMock invocationOnMock) {\n        return Collections.singletonMap(\"c\", connection);\n      }\n    });\n\n    ScanIteration result = jedis.scanIteration(10, \"prefix:*\");\n\n    ScanResult<String> batch = result.nextBatch();\n    assertThat(batch.getCursor(), equalTo(cursor));\n    assertThat(batch.getResult(), contains(key1, key2));\n\n    verify(connectionProvider).getConnectionMap();\n  }\n\n  @Test\n  public void testScanIterationWithType() {\n    String cursor = \"0\";\n    String key1 = \"key1\";\n    String key2 = \"key2\";\n\n    Connection connection = mock(Connection.class);\n    when(connection.executeCommand(any(CommandArguments.class)))\n        .thenReturn(Arrays.asList(cursor.getBytes(), Arrays.asList(key1.getBytes(), key2.getBytes())));\n\n    when(connectionProvider.getConnectionMap()).thenAnswer(new Answer<Map<?, ?>>() {\n      @Override\n      public Map<?, ?> answer(InvocationOnMock invocationOnMock) {\n        return Collections.singletonMap(\"c\", connection);\n      }\n    });\n\n    ScanIteration result = jedis.scanIteration(10, \"prefix:*\", \"zset\");\n\n    ScanResult<String> batch = result.nextBatch();\n    assertThat(batch.getCursor(), equalTo(cursor));\n    assertThat(batch.getResult(), contains(key1, key2));\n\n    verify(connectionProvider).getConnectionMap();\n  }\n\n  @Test\n  public void testSort() {\n    String key = \"key1\";\n    List<String> expected = Arrays.asList(\"one\", \"two\", \"three\");\n\n    when(commandObjects.sort(key)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expected);\n\n    List<String> result = jedis.sort(key);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).sort(key);\n  }\n\n  @Test\n  public void testSortBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    List<byte[]> expected = Arrays.asList(new byte[]{ 4 }, new byte[]{ 5 }, new byte[]{ 6 });\n\n    when(commandObjects.sort(key)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expected);\n\n    List<byte[]> result = jedis.sort(key);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).sort(key);\n  }\n\n  @Test\n  public void testSortWithParams() {\n    String key = \"key1\";\n    SortingParams sortingParams = new SortingParams().asc();\n    List<String> expected = Arrays.asList(\"one\", \"three\", \"two\");\n\n    when(commandObjects.sort(key, sortingParams)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expected);\n\n    List<String> result = jedis.sort(key, sortingParams);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).sort(key, sortingParams);\n  }\n\n  @Test\n  public void testSortWithParamsBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    SortingParams sortingParams = new SortingParams().asc();\n    List<byte[]> expected = Arrays.asList(new byte[]{ 4 }, new byte[]{ 6 }, new byte[]{ 5 });\n\n    when(commandObjects.sort(key, sortingParams)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expected);\n\n    List<byte[]> result = jedis.sort(key, sortingParams);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).sort(key, sortingParams);\n  }\n\n  @Test\n  public void testSortStore() {\n    String key = \"key1\";\n    String dstkey = \"resultKey\";\n\n    when(commandObjects.sort(key, dstkey)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.sort(key, dstkey);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sort(key, dstkey);\n  }\n\n  @Test\n  public void testSortStoreBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    byte[] dstkey = new byte[]{ 7, 8, 9 };\n\n    when(commandObjects.sort(key, dstkey)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.sort(key, dstkey);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sort(key, dstkey);\n  }\n\n  @Test\n  public void testSortStoreWithParams() {\n    String key = \"key1\";\n    SortingParams sortingParams = new SortingParams().asc();\n    String dstkey = \"resultKey\";\n\n    when(commandObjects.sort(key, sortingParams, dstkey)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.sort(key, sortingParams, dstkey);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sort(key, sortingParams, dstkey);\n  }\n\n  @Test\n  public void testSortStoreWithParamsBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    SortingParams sortingParams = new SortingParams().asc();\n    byte[] dstkey = new byte[]{ 7, 8, 9 };\n\n    when(commandObjects.sort(key, sortingParams, dstkey)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.sort(key, sortingParams, dstkey);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sort(key, sortingParams, dstkey);\n  }\n\n  @Test\n  public void testSortReadonly() {\n    String key = \"key1\";\n    SortingParams sortingParams = new SortingParams().asc();\n    List<String> expected = Arrays.asList(\"one\", \"three\", \"two\");\n\n    when(commandObjects.sortReadonly(key, sortingParams)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expected);\n\n    List<String> result = jedis.sortReadonly(key, sortingParams);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).sortReadonly(key, sortingParams);\n  }\n\n  @Test\n  public void testSortReadonlyBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    SortingParams sortingParams = new SortingParams().asc();\n    List<byte[]> expected = Arrays.asList(new byte[]{ 4 }, new byte[]{ 6 }, new byte[]{ 5 });\n\n    when(commandObjects.sortReadonly(key, sortingParams)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expected);\n\n    List<byte[]> result = jedis.sortReadonly(key, sortingParams);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).sortReadonly(key, sortingParams);\n  }\n\n  @Test\n  public void testTouch() {\n    String key = \"key1\";\n\n    when(commandObjects.touch(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.touch(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).touch(key);\n  }\n\n  @Test\n  public void testTouchBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.touch(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.touch(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).touch(key);\n  }\n\n  @Test\n  public void testTouchMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.touch(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.touch(keys);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).touch(keys);\n  }\n\n  @Test\n  public void testTouchMultipleKeysBinary() {\n    byte[][] keys = { new byte[]{ 1, 2, 3 }, new byte[]{ 4, 5, 6 } };\n\n    when(commandObjects.touch(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(2L);\n\n    long result = jedis.touch(keys);\n\n    assertThat(result, equalTo(2L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).touch(keys);\n  }\n\n  @Test\n  public void testTtl() {\n    String key = \"key1\";\n\n    when(commandObjects.ttl(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(120L);\n\n    long result = jedis.ttl(key);\n\n    assertThat(result, equalTo(120L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ttl(key);\n  }\n\n  @Test\n  public void testTtlBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.ttl(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(120L);\n\n    long result = jedis.ttl(key);\n\n    assertThat(result, equalTo(120L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ttl(key);\n  }\n\n  @Test\n  public void testType() {\n    String key = \"key1\";\n\n    when(commandObjects.type(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"string\");\n\n    String result = jedis.type(key);\n\n    assertThat(result, equalTo(\"string\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).type(key);\n  }\n\n  @Test\n  public void testTypeBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.type(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"string\");\n\n    String result = jedis.type(key);\n\n    assertThat(result, equalTo(\"string\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).type(key);\n  }\n\n  @Test\n  public void testUnlink() {\n    String key = \"key1\";\n\n    when(commandObjects.unlink(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.unlink(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).unlink(key);\n  }\n\n  @Test\n  public void testUnlinkBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n\n    when(commandObjects.unlink(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(1L);\n\n    long result = jedis.unlink(key);\n\n    assertThat(result, equalTo(1L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).unlink(key);\n  }\n\n  @Test\n  public void testUnlinkMultipleKeys() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n\n    when(commandObjects.unlink(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(3L);\n\n    long result = jedis.unlink(keys);\n\n    assertThat(result, equalTo(3L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).unlink(keys);\n  }\n\n  @Test\n  public void testUnlinkMultipleKeysBinary() {\n    byte[][] keys = { new byte[]{ 1, 2, 3 }, new byte[]{ 4, 5, 6 } };\n\n    when(commandObjects.unlink(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(2L);\n\n    long result = jedis.unlink(keys);\n\n    assertThat(result, equalTo(2L));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).unlink(keys);\n  }\n\n  @Test\n  public void testWaitReplicas() {\n    String sampleKey = \"myKey\";\n    int replicas = 2;\n    long timeout = 10000L;\n    long expectedReplicaCount = 2L;\n\n    when(commandObjects.waitReplicas(sampleKey, replicas, timeout)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedReplicaCount);\n\n    long result = jedis.waitReplicas(sampleKey, replicas, timeout);\n\n    assertThat(result, equalTo(expectedReplicaCount));\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).waitReplicas(sampleKey, replicas, timeout);\n  }\n\n  @Test\n  public void testWaitReplicasBinary() {\n    byte[] sampleKey = \"myKey\".getBytes();\n    int replicas = 2;\n    long timeout = 10000L;\n    long expectedReplicaCount = 2L;\n\n    when(commandObjects.waitReplicas(sampleKey, replicas, timeout)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedReplicaCount);\n\n    long result = jedis.waitReplicas(sampleKey, replicas, timeout);\n\n    assertThat(result, equalTo(expectedReplicaCount));\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).waitReplicas(sampleKey, replicas, timeout);\n  }\n\n  @Test\n  public void testWaitAOF() {\n    String sampleKey = \"myKey\";\n    long numLocal = 1L;\n    long numReplicas = 2L;\n    long timeout = 10000L;\n    KeyValue<Long, Long> expectedResponse = new KeyValue<>(numLocal, numReplicas);\n\n    when(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout)).thenReturn(keyValueLongLongCommandObject);\n    when(commandExecutor.executeCommand(keyValueLongLongCommandObject)).thenReturn(expectedResponse);\n\n    KeyValue<Long, Long> result = jedis.waitAOF(sampleKey, numLocal, numReplicas, timeout);\n\n    assertThat(result, equalTo(expectedResponse));\n    verify(commandExecutor).executeCommand(keyValueLongLongCommandObject);\n    verify(commandObjects).waitAOF(sampleKey, numLocal, numReplicas, timeout);\n  }\n\n  @Test\n  public void testWaitAOFBinary() {\n    byte[] sampleKey = \"myKey\".getBytes();\n    long numLocal = 1L;\n    long numReplicas = 2L;\n    long timeout = 10000L;\n    KeyValue<Long, Long> expectedResponse = new KeyValue<>(numLocal, numReplicas);\n\n    when(commandObjects.waitAOF(sampleKey, numLocal, numReplicas, timeout)).thenReturn(keyValueLongLongCommandObject);\n    when(commandExecutor.executeCommand(keyValueLongLongCommandObject)).thenReturn(expectedResponse);\n\n    KeyValue<Long, Long> result = jedis.waitAOF(sampleKey, numLocal, numReplicas, timeout);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(keyValueLongLongCommandObject);\n    verify(commandObjects).waitAOF(sampleKey, numLocal, numReplicas, timeout);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisGeospatialCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.params.GeoAddParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.GeoRadiusStoreParam;\nimport redis.clients.jedis.params.GeoSearchParam;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic class UnifiedJedisGeospatialCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testGeoadd() {\n    String key = \"cities\";\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    String member = \"Palermo\";\n    long expectedAdded = 1L;\n\n    when(commandObjects.geoadd(key, longitude, latitude, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.geoadd(key, longitude, latitude, member);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geoadd(key, longitude, latitude, member);\n  }\n\n  @Test\n  public void testGeoaddBinary() {\n    byte[] key = \"cities\".getBytes();\n    double longitude = 13.361389;\n    double latitude = 38.115556;\n    byte[] member = \"Palermo\".getBytes();\n    long expectedAdded = 1L;\n\n    when(commandObjects.geoadd(key, longitude, latitude, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.geoadd(key, longitude, latitude, member);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geoadd(key, longitude, latitude, member);\n  }\n\n  @Test\n  public void testGeoaddMap() {\n    String key = \"cities\";\n    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    memberCoordinateMap.put(\"Catania\", new GeoCoordinate(15.087269, 37.502669));\n    long expectedAdded = 2L;\n\n    when(commandObjects.geoadd(key, memberCoordinateMap)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.geoadd(key, memberCoordinateMap);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geoadd(key, memberCoordinateMap);\n  }\n\n  @Test\n  public void testGeoaddMapBinary() {\n    byte[] key = \"cities\".getBytes();\n    Map<byte[], GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"Palermo\".getBytes(), new GeoCoordinate(13.361389, 38.115556));\n    memberCoordinateMap.put(\"Catania\".getBytes(), new GeoCoordinate(15.087269, 37.502669));\n    long expectedAdded = 2L;\n\n    when(commandObjects.geoadd(key, memberCoordinateMap)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.geoadd(key, memberCoordinateMap);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geoadd(key, memberCoordinateMap);\n  }\n\n  @Test\n  public void testGeoaddMapWithParams() {\n    String key = \"cities\";\n    GeoAddParams params = GeoAddParams.geoAddParams().nx();\n    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"Palermo\", new GeoCoordinate(13.361389, 38.115556));\n    long expectedAdded = 1L;\n\n    when(commandObjects.geoadd(key, params, memberCoordinateMap)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.geoadd(key, params, memberCoordinateMap);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geoadd(key, params, memberCoordinateMap);\n  }\n\n  @Test\n  public void testGeoaddMapWithParamsBinary() {\n    byte[] key = \"cities\".getBytes();\n    GeoAddParams params = GeoAddParams.geoAddParams().nx();\n    Map<byte[], GeoCoordinate> memberCoordinateMap = new HashMap<>();\n    memberCoordinateMap.put(\"Palermo\".getBytes(), new GeoCoordinate(13.361389, 38.115556));\n    long expectedAdded = 1L;\n\n    when(commandObjects.geoadd(key, params, memberCoordinateMap)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.geoadd(key, params, memberCoordinateMap);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geoadd(key, params, memberCoordinateMap);\n  }\n\n  @Test\n  public void testGeodist() {\n    String key = \"cities\";\n    String member1 = \"Palermo\";\n    String member2 = \"Catania\";\n    Double expectedDistance = 166274.15156960033;\n\n    when(commandObjects.geodist(key, member1, member2)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedDistance);\n\n    Double result = jedis.geodist(key, member1, member2);\n\n    assertThat(result, equalTo(expectedDistance));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).geodist(key, member1, member2);\n  }\n\n  @Test\n  public void testGeodistBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member1 = \"Palermo\".getBytes();\n    byte[] member2 = \"Catania\".getBytes();\n    Double expectedDistance = 166274.15156960033;\n\n    when(commandObjects.geodist(key, member1, member2)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedDistance);\n\n    Double result = jedis.geodist(key, member1, member2);\n\n    assertThat(result, equalTo(expectedDistance));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).geodist(key, member1, member2);\n  }\n\n  @Test\n  public void testGeodistWithUnit() {\n    String key = \"cities\";\n    String member1 = \"Palermo\";\n    String member2 = \"Catania\";\n    GeoUnit unit = GeoUnit.KM;\n    Double expectedDistance = 166.274;\n\n    when(commandObjects.geodist(key, member1, member2, unit)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedDistance);\n\n    Double result = jedis.geodist(key, member1, member2, unit);\n\n    assertThat(result, equalTo(expectedDistance));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).geodist(key, member1, member2, unit);\n  }\n\n  @Test\n  public void testGeodistWithUnitBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member1 = \"Palermo\".getBytes();\n    byte[] member2 = \"Catania\".getBytes();\n    GeoUnit unit = GeoUnit.KM;\n    Double expectedDistance = 166.274;\n\n    when(commandObjects.geodist(key, member1, member2, unit)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedDistance);\n\n    Double result = jedis.geodist(key, member1, member2, unit);\n\n    assertThat(result, equalTo(expectedDistance));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).geodist(key, member1, member2, unit);\n  }\n\n  @Test\n  public void testGeohash() {\n    String key = \"cities\";\n    String[] members = { \"Palermo\", \"Catania\" };\n    List<String> expectedHashes = Arrays.asList(\"sqc8b49rny0\", \"sqdtr74hyu0\");\n\n    when(commandObjects.geohash(key, members)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedHashes);\n\n    List<String> result = jedis.geohash(key, members);\n\n    assertThat(result, equalTo(expectedHashes));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).geohash(key, members);\n  }\n\n  @Test\n  public void testGeohashBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[][] members = { \"Palermo\".getBytes(), \"Catania\".getBytes() };\n    List<byte[]> expectedHashes = Arrays.asList(\"sqc8b49rny0\".getBytes(), \"sqdtr74hyu0\".getBytes());\n\n    when(commandObjects.geohash(key, members)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedHashes);\n\n    List<byte[]> result = jedis.geohash(key, members);\n\n    assertThat(result, equalTo(expectedHashes));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).geohash(key, members);\n  }\n\n  @Test\n  public void testGeopos() {\n    String key = \"cities\";\n    String[] members = { \"Palermo\", \"Catania\" };\n    List<GeoCoordinate> expectedPositions = Arrays.asList(\n        new GeoCoordinate(13.361389, 38.115556),\n        new GeoCoordinate(15.087269, 37.502669)\n    );\n\n    when(commandObjects.geopos(key, members)).thenReturn(listGeoCoordinateCommandObject);\n    when(commandExecutor.executeCommand(listGeoCoordinateCommandObject)).thenReturn(expectedPositions);\n\n    List<GeoCoordinate> result = jedis.geopos(key, members);\n\n    assertThat(result, equalTo(expectedPositions));\n\n    verify(commandExecutor).executeCommand(listGeoCoordinateCommandObject);\n    verify(commandObjects).geopos(key, members);\n  }\n\n  @Test\n  public void testGeoposBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[][] members = { \"Palermo\".getBytes(), \"Catania\".getBytes() };\n    List<GeoCoordinate> expectedPositions = Arrays.asList(\n        new GeoCoordinate(13.361389, 38.115556),\n        new GeoCoordinate(15.087269, 37.502669)\n    );\n\n    when(commandObjects.geopos(key, members)).thenReturn(listGeoCoordinateCommandObject);\n    when(commandExecutor.executeCommand(listGeoCoordinateCommandObject)).thenReturn(expectedPositions);\n\n    List<GeoCoordinate> result = jedis.geopos(key, members);\n\n    assertThat(result, equalTo(expectedPositions));\n\n    verify(commandExecutor).executeCommand(listGeoCoordinateCommandObject);\n    verify(commandObjects).geopos(key, members);\n  }\n\n  @Test\n  public void testGeoradius() {\n    String key = \"cities\";\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadius(key, longitude, latitude, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadius(key, longitude, latitude, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadius(key, longitude, latitude, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusBinary() {\n    byte[] key = \"cities\".getBytes();\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadius(key, longitude, latitude, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadius(key, longitude, latitude, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadius(key, longitude, latitude, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusReadonly() {\n    String key = \"cities\";\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusReadonly(key, longitude, latitude, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusReadonly(key, longitude, latitude, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusReadonlyBinary() {\n    byte[] key = \"cities\".getBytes();\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusReadonly(key, longitude, latitude, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusReadonly(key, longitude, latitude, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusWithParam() {\n    String key = \"cities\";\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadius(key, longitude, latitude, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadius(key, longitude, latitude, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadius(key, longitude, latitude, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusWithParamBinary() {\n    byte[] key = \"cities\".getBytes();\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadius(key, longitude, latitude, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadius(key, longitude, latitude, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadius(key, longitude, latitude, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusReadonlyWithParam() {\n    String key = \"cities\";\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusReadonly(key, longitude, latitude, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusReadonly(key, longitude, latitude, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusReadonlyWithParamBinary() {\n    byte[] key = \"cities\".getBytes();\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusReadonly(key, longitude, latitude, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusReadonly(key, longitude, latitude, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusByMember() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMember(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMember(key, member, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMember(key, member, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusByMemberBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMember(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMember(key, member, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMember(key, member, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonly() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMemberReadonly(key, member, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMemberReadonly(key, member, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonlyBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMemberReadonly(key, member, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMemberReadonly(key, member, radius, unit);\n  }\n\n  @Test\n  public void testGeoradiusByMemberWithParam() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMember(key, member, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMember(key, member, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMember(key, member, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusByMemberWithParamBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMember(key, member, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMember(key, member, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMember(key, member, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonlyWithParam() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMemberReadonly(key, member, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMemberReadonly(key, member, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusByMemberReadonlyWithParamBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.georadiusByMemberReadonly(key, member, radius, unit, param);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).georadiusByMemberReadonly(key, member, radius, unit, param);\n  }\n\n  @Test\n  public void testGeoradiusStore() {\n    String key = \"cities\";\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam();\n    long expectedStored = 2L;\n\n    when(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).georadiusStore(key, longitude, latitude, radius, unit, param, storeParam);\n  }\n\n  @Test\n  public void testGeoradiusStoreBinary() {\n    byte[] key = \"cities\".getBytes();\n    double longitude = 15.087269;\n    double latitude = 37.502669;\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam();\n    long expectedStored = 2L;\n\n    when(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).georadiusStore(key, longitude, latitude, radius, unit, param, storeParam);\n  }\n\n  @Test\n  public void testGeoradiusByMemberStore() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam();\n    long expectedStored = 2L;\n\n    when(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.georadiusByMemberStore(key, member, radius, unit, param, storeParam);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).georadiusByMemberStore(key, member, radius, unit, param, storeParam);\n  }\n\n  @Test\n  public void testGeoradiusByMemberStoreBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    GeoRadiusParam param = GeoRadiusParam.geoRadiusParam().withDist();\n    GeoRadiusStoreParam storeParam = GeoRadiusStoreParam.geoRadiusStoreParam();\n    long expectedStored = 2L;\n\n    when(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.georadiusByMemberStore(key, member, radius, unit, param, storeParam);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).georadiusByMemberStore(key, member, radius, unit, param, storeParam);\n  }\n\n  @Test\n  public void testGeosearchByMemberRadius() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, member, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, member, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchByMemberRadiusBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, member, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, member, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, member, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchKeyCoordRadius() {\n    String key = \"cities\";\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, coord, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, coord, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, coord, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchKeyCoordRadiusBinary() {\n    byte[] key = \"cities\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, coord, radius, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, coord, radius, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, coord, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchByMemberBox() {\n    String key = \"cities\";\n    String member = \"Catania\";\n    double width = 100;\n    double height = 200;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, member, width, height, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, member, width, height, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, member, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchByMemberBoxBinary() {\n    byte[] key = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double width = 150;\n    double height = 75;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, member, width, height, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, member, width, height, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, member, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchByCoordBox() {\n    String key = \"cities\";\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double width = 100;\n    double height = 200;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, coord, width, height, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, coord, width, height, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, coord, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchByCoordBoxBinary() {\n    byte[] key = \"cities\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double width = 150;\n    double height = 75;\n    GeoUnit unit = GeoUnit.KM;\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, coord, width, height, unit)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, coord, width, height, unit);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, coord, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchWithParams() {\n    String key = \"cities\";\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM).withCoord().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, params)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, params);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, params);\n  }\n\n  @Test\n  public void testGeosearchWithParamsBinary() {\n    byte[] key = \"cities\".getBytes();\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM).withCoord().withDist();\n    List<GeoRadiusResponse> expectedResponses = new ArrayList<>();\n    expectedResponses.add(new GeoRadiusResponse(\"Palermo\".getBytes()));\n\n    when(commandObjects.geosearch(key, params)).thenReturn(listGeoRadiusResponseCommandObject);\n    when(commandExecutor.executeCommand(listGeoRadiusResponseCommandObject)).thenReturn(expectedResponses);\n\n    List<GeoRadiusResponse> result = jedis.geosearch(key, params);\n\n    assertThat(result, equalTo(expectedResponses));\n\n    verify(commandExecutor).executeCommand(listGeoRadiusResponseCommandObject);\n    verify(commandObjects).geosearch(key, params);\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberRadius() {\n    String dest = \"cities_store\";\n    String src = \"cities\";\n    String member = \"Catania\";\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 2L;\n\n    when(commandObjects.geosearchStore(dest, src, member, radius, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, member, radius, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, member, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberRadiusBinary() {\n    byte[] dest = \"cities_store\".getBytes();\n    byte[] src = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, member, radius, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, member, radius, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, member, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordRadius() {\n    String dest = \"cities_store\";\n    String src = \"cities\";\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 2L;\n\n    when(commandObjects.geosearchStore(dest, src, coord, radius, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, coord, radius, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, coord, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordRadiusBinary() {\n    byte[] dest = \"cities_store\".getBytes();\n    byte[] src = \"cities\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double radius = 100;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, coord, radius, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, coord, radius, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, coord, radius, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberBox() {\n    String dest = \"cities_store\";\n    String src = \"cities\";\n    String member = \"Catania\";\n    double width = 150;\n    double height = 75;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, member, width, height, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, member, width, height, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, member, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByMemberBoxBinary() {\n    byte[] dest = \"cities_store\".getBytes();\n    byte[] src = \"cities\".getBytes();\n    byte[] member = \"Catania\".getBytes();\n    double width = 150;\n    double height = 75;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, member, width, height, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, member, width, height, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, member, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordBox() {\n    String dest = \"cities_store\";\n    String src = \"cities\";\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double width = 150;\n    double height = 75;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, coord, width, height, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, coord, width, height, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, coord, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreByCoordBoxBinary() {\n    byte[] dest = \"cities_store\".getBytes();\n    byte[] src = \"cities\".getBytes();\n    GeoCoordinate coord = new GeoCoordinate(15.087269, 37.502669);\n    double width = 150;\n    double height = 75;\n    GeoUnit unit = GeoUnit.KM;\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, coord, width, height, unit)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, coord, width, height, unit);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, coord, width, height, unit);\n  }\n\n  @Test\n  public void testGeosearchStoreWithParams() {\n    String dest = \"cities_store\";\n    String src = \"cities\";\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM).withCoord().withDist();\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, params);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, params);\n  }\n\n  @Test\n  public void testGeosearchStoreWithParamsBinary() {\n    byte[] dest = \"cities_store\".getBytes();\n    byte[] src = \"cities\".getBytes();\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM).withCoord().withDist();\n    long expectedStored = 3L;\n\n    when(commandObjects.geosearchStore(dest, src, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.geosearchStore(dest, src, params);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStore(dest, src, params);\n  }\n\n  @Test\n  public void testGeosearchStoreStoreDist() {\n    String dest = \"cities_store\";\n    String src = \"cities\";\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM).withCoord().withDist();\n    long expectedStoredDist = 3L;\n\n    when(commandObjects.geosearchStoreStoreDist(dest, src, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredDist);\n\n    long result = jedis.geosearchStoreStoreDist(dest, src, params);\n\n    assertThat(result, equalTo(expectedStoredDist));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStoreStoreDist(dest, src, params);\n  }\n\n  @Test\n  public void testGeosearchStoreStoreDistBinary() {\n    byte[] dest = \"cities_store\".getBytes();\n    byte[] src = \"cities\".getBytes();\n    GeoSearchParam params = GeoSearchParam.geoSearchParam().byRadius(100, GeoUnit.KM).withCoord().withDist();\n    long expectedStoredDist = 3L;\n\n    when(commandObjects.geosearchStoreStoreDist(dest, src, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredDist);\n\n    long result = jedis.geosearchStoreStoreDist(dest, src, params);\n\n    assertThat(result, equalTo(expectedStoredDist));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).geosearchStoreStoreDist(dest, src, params);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisHashCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static java.util.Arrays.asList;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.args.ExpiryOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic class UnifiedJedisHashCommandsTest extends UnifiedJedisMockedTestBase {\n\n  private final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };\n\n  private final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A };\n  private final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B };\n  private final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C };\n\n  @Test\n  public void testHdel() {\n    String key = \"hashKey\";\n    String[] fields = { \"field1\", \"field2\" };\n    long expected = 2L;\n\n    when(commandObjects.hdel(key, fields)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hdel(key, fields);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hdel(key, fields);\n  }\n\n  @Test\n  public void testHdelBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[][] fields = { \"field1\".getBytes(), \"field2\".getBytes() };\n    long expected = 2L; // Assuming both fields were deleted\n\n    when(commandObjects.hdel(key, fields)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hdel(key, fields);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hdel(key, fields);\n  }\n\n  @Test\n  public void testHexists() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    boolean expected = true;\n\n    when(commandObjects.hexists(key, field)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expected);\n\n    boolean result = jedis.hexists(key, field);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).hexists(key, field);\n  }\n\n  @Test\n  public void testHexistsBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    boolean expected = true;\n\n    when(commandObjects.hexists(key, field)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expected);\n\n    boolean result = jedis.hexists(key, field);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).hexists(key, field);\n  }\n\n  @Test\n  public void testHget() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    String expectedValue = \"value1\";\n\n    when(commandObjects.hget(key, field)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedValue);\n\n    String result = jedis.hget(key, field);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).hget(key, field);\n  }\n\n  @Test\n  public void testHgetBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    byte[] expectedValue = \"value1\".getBytes();\n\n    when(commandObjects.hget(key, field)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedValue);\n\n    byte[] result = jedis.hget(key, field);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).hget(key, field);\n  }\n\n  @Test\n  public void testHgetAll() {\n    String key = \"hashKey\";\n    Map<String, String> expectedMap = new HashMap<>();\n    expectedMap.put(\"field1\", \"value1\");\n    expectedMap.put(\"field2\", \"value2\");\n\n    when(commandObjects.hgetAll(key)).thenReturn(mapStringStringCommandObject);\n    when(commandExecutor.executeCommand(mapStringStringCommandObject)).thenReturn(expectedMap);\n\n    Map<String, String> result = jedis.hgetAll(key);\n\n    assertThat(result, equalTo(expectedMap));\n\n    verify(commandExecutor).executeCommand(mapStringStringCommandObject);\n    verify(commandObjects).hgetAll(key);\n  }\n\n  @Test\n  public void testHgetAllBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    Map<byte[], byte[]> expectedMap = new HashMap<>();\n    expectedMap.put(\"field1\".getBytes(), \"value1\".getBytes());\n    expectedMap.put(\"field2\".getBytes(), \"value2\".getBytes());\n\n    when(commandObjects.hgetAll(key)).thenReturn(mapBytesBytesCommandObject);\n    when(commandExecutor.executeCommand(mapBytesBytesCommandObject)).thenReturn(expectedMap);\n\n    Map<byte[], byte[]> result = jedis.hgetAll(key);\n\n    assertThat(result, equalTo(expectedMap));\n\n    verify(commandExecutor).executeCommand(mapBytesBytesCommandObject);\n    verify(commandObjects).hgetAll(key);\n  }\n\n  @Test\n  public void testHincrBy() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    long increment = 2L;\n    long expectedValue = 5L; // Assuming the original value was 3\n\n    when(commandObjects.hincrBy(key, field, increment)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.hincrBy(key, field, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hincrBy(key, field, increment);\n  }\n\n  @Test\n  public void testHincrByBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    long increment = 2L;\n    long expectedValue = 5L; // Assuming the original value was 3\n\n    when(commandObjects.hincrBy(key, field, increment)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.hincrBy(key, field, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hincrBy(key, field, increment);\n  }\n\n  @Test\n  public void testHincrByFloat() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    double increment = 1.5;\n    double expectedValue = 4.5; // Assuming the original value was 3.0\n\n    when(commandObjects.hincrByFloat(key, field, increment)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedValue);\n\n    double result = jedis.hincrByFloat(key, field, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).hincrByFloat(key, field, increment);\n  }\n\n  @Test\n  public void testHincrByFloatBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    double increment = 1.5;\n    double expectedValue = 4.5; // Assuming the original value was 3.0\n\n    when(commandObjects.hincrByFloat(key, field, increment)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedValue);\n\n    double result = jedis.hincrByFloat(key, field, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).hincrByFloat(key, field, increment);\n  }\n\n  @Test\n  public void testHkeys() {\n    String key = \"hashKey\";\n    Set<String> expectedKeys = new HashSet<>(Arrays.asList(\"field1\", \"field2\", \"field3\"));\n\n    when(commandObjects.hkeys(key)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedKeys);\n\n    Set<String> result = jedis.hkeys(key);\n\n    assertThat(result, equalTo(expectedKeys));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).hkeys(key);\n  }\n\n  @Test\n  public void testHkeysBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    Set<byte[]> expectedKeys = new HashSet<>(Arrays.asList(\"field1\".getBytes(), \"field2\".getBytes(), \"field3\".getBytes()));\n\n    when(commandObjects.hkeys(key)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedKeys);\n\n    Set<byte[]> result = jedis.hkeys(key);\n\n    assertThat(result, equalTo(expectedKeys));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).hkeys(key);\n  }\n\n  @Test\n  public void testHlen() {\n    String key = \"hashKey\";\n    long expected = 3L; // Assuming there are 3 fields in the hash\n\n    when(commandObjects.hlen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hlen(key);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hlen(key);\n  }\n\n  @Test\n  public void testHlenBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    long expected = 3L; // Assuming there are 3 fields in the hash\n\n    when(commandObjects.hlen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hlen(key);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hlen(key);\n  }\n\n  @Test\n  public void testHmget() {\n    String key = \"hashKey\";\n    String[] fields = { \"field1\", \"field2\" };\n    List<String> expectedValues = Arrays.asList(\"value1\", \"value2\");\n\n    when(commandObjects.hmget(key, fields)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.hmget(key, fields);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).hmget(key, fields);\n  }\n\n  @Test\n  public void testHmgetBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[][] fields = { \"field1\".getBytes(), \"field2\".getBytes() };\n    List<byte[]> expectedValues = Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes());\n\n    when(commandObjects.hmget(key, fields)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedValues);\n\n    List<byte[]> result = jedis.hmget(key, fields);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).hmget(key, fields);\n  }\n\n  @Test\n  public void testHmset() {\n    String key = \"hashKey\";\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    String expectedStatus = \"OK\";\n\n    when(commandObjects.hmset(key, hash)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedStatus);\n\n    String result = jedis.hmset(key, hash);\n\n    assertThat(result, equalTo(expectedStatus));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).hmset(key, hash);\n  }\n\n  @Test\n  public void testHmsetBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n    hash.put(\"field2\".getBytes(), \"value2\".getBytes());\n    String expectedStatus = \"OK\";\n\n    when(commandObjects.hmset(key, hash)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedStatus);\n\n    String result = jedis.hmset(key, hash);\n\n    assertThat(result, equalTo(expectedStatus));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).hmset(key, hash);\n  }\n\n  @Test\n  public void testHrandfield() {\n    String key = \"hashKey\";\n    String expectedField = \"field1\";\n\n    when(commandObjects.hrandfield(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedField);\n\n    String result = jedis.hrandfield(key);\n\n    assertThat(result, equalTo(expectedField));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).hrandfield(key);\n  }\n\n  @Test\n  public void testHrandfieldBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] expectedField = \"field1\".getBytes();\n\n    when(commandObjects.hrandfield(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedField);\n\n    byte[] result = jedis.hrandfield(key);\n\n    assertThat(result, equalTo(expectedField));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).hrandfield(key);\n  }\n\n  @Test\n  public void testHrandfieldCount() {\n    String key = \"hashKey\";\n    long count = 2;\n    List<String> expectedFields = Arrays.asList(\"field1\", \"field2\");\n\n    when(commandObjects.hrandfield(key, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedFields);\n\n    List<String> result = jedis.hrandfield(key, count);\n\n    assertThat(result, equalTo(expectedFields));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).hrandfield(key, count);\n  }\n\n  @Test\n  public void testHrandfieldCountBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    long count = 2;\n    List<byte[]> expectedFields = Arrays.asList(\"field1\".getBytes(), \"field2\".getBytes());\n\n    when(commandObjects.hrandfield(key, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedFields);\n\n    List<byte[]> result = jedis.hrandfield(key, count);\n\n    assertThat(result, equalTo(expectedFields));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).hrandfield(key, count);\n  }\n\n  @Test\n  public void testHrandfieldWithValues() {\n    String key = \"hashKey\";\n    long count = 2;\n    List<Map.Entry<String, String>> expectedEntries = new ArrayList<>();\n    expectedEntries.add(new AbstractMap.SimpleEntry<>(\"field1\", \"value1\"));\n    expectedEntries.add(new AbstractMap.SimpleEntry<>(\"field2\", \"value2\"));\n\n    when(commandObjects.hrandfieldWithValues(key, count)).thenReturn(listEntryStringStringCommandObject);\n    when(commandExecutor.executeCommand(listEntryStringStringCommandObject)).thenReturn(expectedEntries);\n\n    List<Map.Entry<String, String>> result = jedis.hrandfieldWithValues(key, count);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listEntryStringStringCommandObject);\n    verify(commandObjects).hrandfieldWithValues(key, count);\n  }\n\n  @Test\n  public void testHrandfieldWithValuesBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    long count = 2;\n    List<Map.Entry<byte[], byte[]>> expectedEntries = new ArrayList<>();\n\n    expectedEntries.add(new AbstractMap.SimpleEntry<>(\"field1\".getBytes(), \"value1\".getBytes()));\n    expectedEntries.add(new AbstractMap.SimpleEntry<>(\"field2\".getBytes(), \"value2\".getBytes()));\n\n    when(commandObjects.hrandfieldWithValues(key, count)).thenReturn(listEntryBytesBytesCommandObject);\n    when(commandExecutor.executeCommand(listEntryBytesBytesCommandObject)).thenReturn(expectedEntries);\n\n    List<Map.Entry<byte[], byte[]>> result = jedis.hrandfieldWithValues(key, count);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listEntryBytesBytesCommandObject);\n    verify(commandObjects).hrandfieldWithValues(key, count);\n  }\n\n  @Test\n  public void testHscan() {\n    String key = \"hashKey\";\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    List<Map.Entry<String, String>> scanResultData = new ArrayList<>();\n    scanResultData.add(new AbstractMap.SimpleEntry<>(\"field1\", \"value1\"));\n    scanResultData.add(new AbstractMap.SimpleEntry<>(\"field2\", \"value2\"));\n    ScanResult<Map.Entry<String, String>> expectedScanResult = new ScanResult<>(cursor, scanResultData);\n\n    when(commandObjects.hscan(key, cursor, params)).thenReturn(scanResultEntryStringStringCommandObject);\n    when(commandExecutor.executeCommand(scanResultEntryStringStringCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<Map.Entry<String, String>> result = jedis.hscan(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultEntryStringStringCommandObject);\n    verify(commandObjects).hscan(key, cursor, params);\n  }\n\n  @Test\n  public void testHscanBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;\n    ScanParams params = new ScanParams().match(\"*\".getBytes()).count(10);\n    List<Map.Entry<byte[], byte[]>> scanResultData = new ArrayList<>();\n    scanResultData.add(new AbstractMap.SimpleEntry<>(\"field1\".getBytes(), \"value1\".getBytes()));\n    scanResultData.add(new AbstractMap.SimpleEntry<>(\"field2\".getBytes(), \"value2\".getBytes()));\n    ScanResult<Map.Entry<byte[], byte[]>> expectedScanResult = new ScanResult<>(cursor, scanResultData);\n\n    when(commandObjects.hscan(key, cursor, params)).thenReturn(scanResultEntryBytesBytesCommandObject);\n    when(commandExecutor.executeCommand(scanResultEntryBytesBytesCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<Map.Entry<byte[], byte[]>> result = jedis.hscan(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultEntryBytesBytesCommandObject);\n    verify(commandObjects).hscan(key, cursor, params);\n  }\n\n  @Test\n  public void testHscanNoValues() {\n    String key = \"hashKey\";\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    List<String> scanResultData = Arrays.asList(\"field1\", \"field2\");\n    ScanResult<String> expectedScanResult = new ScanResult<>(cursor, scanResultData);\n\n    when(commandObjects.hscanNoValues(key, cursor, params)).thenReturn(scanResultStringCommandObject);\n    when(commandExecutor.executeCommand(scanResultStringCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<String> result = jedis.hscanNoValues(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultStringCommandObject);\n    verify(commandObjects).hscanNoValues(key, cursor, params);\n  }\n\n  @Test\n  public void testHscanNoValuesBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;\n    ScanParams params = new ScanParams().match(\"*\".getBytes()).count(10);\n    List<byte[]> scanResultData = Arrays.asList(\"field1\".getBytes(), \"field2\".getBytes());\n    ScanResult<byte[]> expectedScanResult = new ScanResult<>(cursor, scanResultData);\n\n    when(commandObjects.hscanNoValues(key, cursor, params)).thenReturn(scanResultBytesCommandObject);\n    when(commandExecutor.executeCommand(scanResultBytesCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<byte[]> result = jedis.hscanNoValues(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultBytesCommandObject);\n    verify(commandObjects).hscanNoValues(key, cursor, params);\n  }\n\n  @Test\n  public void testHset() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    String value = \"value1\";\n    long expected = 1L; // Assuming the field was newly set\n\n    when(commandObjects.hset(key, field, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hset(key, field, value);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hset(key, field, value);\n  }\n\n  @Test\n  public void testHsetBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    byte[] value = \"value1\".getBytes();\n    long expected = 1L; // Assuming the field was newly set\n\n    when(commandObjects.hset(key, field, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hset(key, field, value);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hset(key, field, value);\n  }\n\n  @Test\n  public void testHsetMap() {\n    String key = \"hashKey\";\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    long expected = 2L; // Assuming both fields were newly set\n\n    when(commandObjects.hset(key, hash)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hset(key, hash);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hset(key, hash);\n  }\n\n  @Test\n  public void testHsetMapBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n    hash.put(\"field2\".getBytes(), \"value2\".getBytes());\n    long expected = 2L; // Assuming both fields were newly set\n\n    when(commandObjects.hset(key, hash)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hset(key, hash);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hset(key, hash);\n  }\n\n  @Test\n  public void testHsetObject() {\n    String key = \"myHash\";\n    String field = \"myField\";\n    Object value = \"myValue\";\n    long expectedResponse = 1L;\n\n    when(commandObjects.hsetObject(key, field, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.hsetObject(key, field, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hsetObject(key, field, value);\n  }\n\n  @Test\n  public void testHsetObjectMap() {\n    String key = \"myHash\";\n    Map<String, Object> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    long expectedResponse = 2L;\n\n    when(commandObjects.hsetObject(key, hash)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.hsetObject(key, hash);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hsetObject(key, hash);\n  }\n\n  @Test\n  public void testHsetnx() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    String value = \"value1\";\n    long expected = 1L; // Assuming the field was newly set\n\n    when(commandObjects.hsetnx(key, field, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hsetnx(key, field, value);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hsetnx(key, field, value);\n  }\n\n  @Test\n  public void testHsetnxBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    byte[] value = \"value1\".getBytes();\n    long expected = 1L; // Assuming the field was newly set\n\n    when(commandObjects.hsetnx(key, field, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expected);\n\n    long result = jedis.hsetnx(key, field, value);\n\n    assertThat(result, equalTo(expected));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hsetnx(key, field, value);\n  }\n\n  @Test\n  public void testHstrlen() {\n    String key = \"hashKey\";\n    String field = \"field1\";\n    long expectedLength = 6L; // Assuming the value of the field is \"value1\"\n\n    when(commandObjects.hstrlen(key, field)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.hstrlen(key, field);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hstrlen(key, field);\n  }\n\n  @Test\n  public void testHstrlenBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    byte[] field = \"field1\".getBytes();\n    long expectedLength = 6L; // Assuming the value of the field is \"value1\".getBytes()\n\n    when(commandObjects.hstrlen(key, field)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.hstrlen(key, field);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).hstrlen(key, field);\n  }\n\n  @Test\n  public void testHvals() {\n    String key = \"hashKey\";\n    List<String> expectedValues = Arrays.asList(\"value1\", \"value2\", \"value3\");\n\n    when(commandObjects.hvals(key)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.hvals(key);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).hvals(key);\n  }\n\n  @Test\n  public void testHvalsBinary() {\n    byte[] key = \"hashKey\".getBytes();\n    List<byte[]> expectedValues = Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes(), \"value3\".getBytes());\n\n    when(commandObjects.hvals(key)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedValues);\n\n    List<byte[]> result = jedis.hvals(key);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).hvals(key);\n  }\n\n  @Test\n  public void hexpire() {\n    String key = \"hash\";\n    long seconds = 100;\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpire(key, seconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpire(key, seconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpire(key, seconds, fields);\n  }\n\n  @Test\n  public void hexpireCondition() {\n    String key = \"hash\";\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpire(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpire(key, seconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpire(key, seconds, condition, fields);\n  }\n\n  @Test\n  public void hpexpire() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 100L, 200L, 300L );\n\n    when(commandObjects.hpexpire(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpire(key, milliseconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpire(key, milliseconds, fields);\n  }\n\n  @Test\n  public void hpexpireCondition() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 100L, 200L, 300L );\n\n    when(commandObjects.hpexpire(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpire(key, milliseconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpire(key, milliseconds, condition, fields);\n  }\n\n  @Test\n  public void hexpireAt() {\n    String key = \"hash\";\n    long seconds = 100;\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpireAt(key, seconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpireAt(key, seconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpireAt(key, seconds, fields);\n  }\n\n  @Test\n  public void hexpireAtCondition() {\n    String key = \"hash\";\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpireAt(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpireAt(key, seconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpireAt(key, seconds, condition, fields);\n  }\n\n  @Test\n  public void hpexpireAt() {\n    String key = \"hash\";\n    long milliseconds = 10000;\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hpexpireAt(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpireAt(key, milliseconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpireAt(key, milliseconds, fields);\n  }\n\n  @Test\n  public void hpexpireAtCondition() {\n    String key = \"hash\";\n    long milliseconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hpexpireAt(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpireAt(key, milliseconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpireAt(key, milliseconds, condition, fields);\n  }\n\n  @Test\n  public void hexpireTime() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 10L, 20L, 30L );\n\n    when(commandObjects.hexpireTime(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpireTime(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpireTime(key, fields);\n  }\n\n  @Test\n  public void hpexpireTime() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1000L, 2000L, 3000L );\n\n    when(commandObjects.hpexpireTime(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpireTime(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpireTime(key, fields);\n  }\n\n  @Test\n  public void httl() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 10L, 20L, 30L );\n\n    when(commandObjects.httl(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.httl(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).httl(key, fields);\n  }\n\n  @Test\n  public void hpttl() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1000L, 2000L, 3000L );\n\n    when(commandObjects.hpttl(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpttl(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpttl(key, fields);\n  }\n\n  @Test\n  public void hpersist() {\n    String key = \"hash\";\n    String[] fields = { \"one\", \"two\", \"three\" };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hpersist(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpersist(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpersist(key, fields);\n  }\n\n  @Test\n  public void hexpireBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpire(key, seconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpire(key, seconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpire(key, seconds, fields);\n  }\n\n  @Test\n  public void hexpireConditionBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpire(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpire(key, seconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpire(key, seconds, condition, fields);\n  }\n\n  @Test\n  public void hpexpireBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 100L, 200L, 300L );\n\n    when(commandObjects.hpexpire(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpire(key, milliseconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpire(key, milliseconds, fields);\n  }\n\n  @Test\n  public void hpexpireConditionBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 100L, 200L, 300L );\n\n    when(commandObjects.hpexpire(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpire(key, milliseconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpire(key, milliseconds, condition, fields);\n  }\n\n  @Test\n  public void hexpireAtBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpireAt(key, seconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpireAt(key, seconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpireAt(key, seconds, fields);\n  }\n\n  @Test\n  public void hexpireAtConditionBinary() {\n    byte[] key = bfoo;\n    long seconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hexpireAt(key, seconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpireAt(key, seconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpireAt(key, seconds, condition, fields);\n  }\n\n  @Test\n  public void hpexpireAtBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 10000;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hpexpireAt(key, milliseconds, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpireAt(key, milliseconds, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpireAt(key, milliseconds, fields);\n  }\n\n  @Test\n  public void hpexpireAtConditionBinary() {\n    byte[] key = bfoo;\n    long milliseconds = 100;\n    ExpiryOption condition = mock(ExpiryOption.class);\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hpexpireAt(key, milliseconds, condition, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpireAt(key, milliseconds, condition, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpireAt(key, milliseconds, condition, fields);\n  }\n\n  @Test\n  public void hexpireTimeBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 10L, 20L, 30L );\n\n    when(commandObjects.hexpireTime(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hexpireTime(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hexpireTime(key, fields);\n  }\n\n  @Test\n  public void hpexpireTimeBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1000L, 2000L, 3000L );\n\n    when(commandObjects.hpexpireTime(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpexpireTime(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpexpireTime(key, fields);\n  }\n\n  @Test\n  public void httlBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 10L, 20L, 30L );\n\n    when(commandObjects.httl(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.httl(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).httl(key, fields);\n  }\n\n  @Test\n  public void hpttlBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1000L, 2000L, 3000L );\n\n    when(commandObjects.hpttl(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpttl(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpttl(key, fields);\n  }\n\n  @Test\n  public void hpersistBinary() {\n    byte[] key = bfoo;\n    byte[][] fields = { bbar1, bbar2, bbar3 };\n    List<Long> expected = asList( 1L, 2L, 3L );\n\n    when(commandObjects.hpersist(key, fields)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expected);\n\n    assertThat(jedis.hpersist(key, fields), equalTo(expected));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).hpersist(key, fields);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisHyperloglogCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\n\npublic class UnifiedJedisHyperloglogCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testPfadd() {\n    String key = \"hll\";\n    String[] elements = { \"element1\", \"element2\" };\n    long expectedAdded = 1L;\n\n    when(commandObjects.pfadd(key, elements)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.pfadd(key, elements);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pfadd(key, elements);\n  }\n\n  @Test\n  public void testPfaddBinary() {\n    byte[] key = \"hll\".getBytes();\n    byte[][] elements = { \"element1\".getBytes(), \"element2\".getBytes() };\n    long expectedAdded = 1L;\n\n    when(commandObjects.pfadd(key, elements)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.pfadd(key, elements);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pfadd(key, elements);\n  }\n\n  @Test\n  public void testPfcount() {\n    String key = \"hll\";\n    long expectedCount = 42L;\n\n    when(commandObjects.pfcount(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.pfcount(key);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pfcount(key);\n  }\n\n  @Test\n  public void testPfcountBinary() {\n    byte[] key = \"hll\".getBytes();\n    long expectedCount = 42L;\n\n    when(commandObjects.pfcount(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.pfcount(key);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pfcount(key);\n  }\n\n  @Test\n  public void testPfcountMultipleKeys() {\n    String[] keys = { \"hll1\", \"hll2\" };\n    long expectedCount = 84L;\n\n    when(commandObjects.pfcount(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.pfcount(keys);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pfcount(keys);\n  }\n\n  @Test\n  public void testPfcountMultipleKeysBinary() {\n    byte[][] keys = { \"hll1\".getBytes(), \"hll2\".getBytes() };\n    long expectedCount = 84L;\n\n    when(commandObjects.pfcount(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.pfcount(keys);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).pfcount(keys);\n  }\n\n  @Test\n  public void testPfmergeString() {\n    String destkey = \"hll1\";\n    String[] sourcekeys = { \"hll2\", \"hll3\" };\n    String expectedStatus = \"OK\";\n    when(commandObjects.pfmerge(destkey, sourcekeys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedStatus);\n\n    String result = jedis.pfmerge(destkey, sourcekeys);\n\n    assertThat(result, equalTo(expectedStatus));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).pfmerge(destkey, sourcekeys);\n  }\n\n  @Test\n  public void testPfmergeBinary() {\n    byte[] destkey = \"hll1\".getBytes();\n    byte[][] sourcekeys = { \"hll2\".getBytes(), \"hll3\".getBytes() };\n    String expectedStatus = \"OK\";\n\n    when(commandObjects.pfmerge(destkey, sourcekeys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedStatus);\n\n    String result = jedis.pfmerge(destkey, sourcekeys);\n\n    assertThat(result, equalTo(expectedStatus));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).pfmerge(destkey, sourcekeys);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisJsonCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport com.google.gson.JsonObject;\nimport org.json.JSONArray;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport redis.clients.jedis.json.JsonObjectMapper;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.Path2;\n\npublic class UnifiedJedisJsonCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testJsonArrAppendWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    Object[] pojos = new Object[]{ \"value1\", \"value2\" };\n    Long expectedResponse = 4L;\n\n    when(commandObjects.jsonArrAppend(key, path, pojos)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonArrAppend(key, path, pojos);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonArrAppend(key, path, pojos);\n  }\n\n  @Test\n  public void testJsonArrAppendWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    Object[] objects = new Object[]{ \"value1\", \"value2\" };\n    List<Long> expectedResponse = Arrays.asList(3L, 4L);\n\n    when(commandObjects.jsonArrAppend(key, path, objects)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrAppend(key, path, objects);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrAppend(key, path, objects);\n  }\n\n  @Test\n  public void testJsonArrAppendWithPath2WithEscape() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    Object[] objects = new Object[]{ \"value1\", \"value2\" };\n    List<Long> expectedResponse = Arrays.asList(3L, 4L);\n\n    when(commandObjects.jsonArrAppendWithEscape(key, path, objects)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrAppendWithEscape(key, path, objects);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrAppendWithEscape(key, path, objects);\n  }\n\n  @Test\n  public void testJsonArrIndexWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    Object scalar = \"value\";\n    long expectedResponse = 2L;\n\n    when(commandObjects.jsonArrIndex(key, path, scalar)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonArrIndex(key, path, scalar);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonArrIndex(key, path, scalar);\n  }\n\n  @Test\n  public void testJsonArrIndexWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    Object scalar = \"value\";\n    List<Long> expectedResponse = Collections.singletonList(2L);\n\n    when(commandObjects.jsonArrIndex(key, path, scalar)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrIndex(key, path, scalar);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrIndex(key, path, scalar);\n  }\n\n  @Test\n  public void testJsonArrIndexWithPath2WithEscape() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    Object scalar = \"value\";\n    List<Long> expectedResponse = Collections.singletonList(2L);\n\n    when(commandObjects.jsonArrIndexWithEscape(key, path, scalar)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrIndexWithEscape(key, path, scalar);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrIndexWithEscape(key, path, scalar);\n  }\n\n  @Test\n  public void testJsonArrInsertWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    int index = 1;\n    Object[] pojos = new Object[]{ \"value1\", \"value2\" };\n    long expectedResponse = 5L;\n\n    when(commandObjects.jsonArrInsert(key, path, index, pojos)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonArrInsert(key, path, index, pojos);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonArrInsert(key, path, index, pojos);\n  }\n\n  @Test\n  public void testJsonArrInsertWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    int index = 1;\n    Object[] objects = new Object[]{ \"value1\", \"value2\" };\n    List<Long> expectedResponse = Collections.singletonList(5L);\n\n    when(commandObjects.jsonArrInsert(key, path, index, objects)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrInsert(key, path, index, objects);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrInsert(key, path, index, objects);\n  }\n\n  @Test\n  public void testJsonArrInsertWithPath2WithEscape() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    int index = 1;\n    Object[] objects = new Object[]{ \"value1\", \"value2\" };\n    List<Long> expectedResponse = Collections.singletonList(5L);\n\n    when(commandObjects.jsonArrInsertWithEscape(key, path, index, objects)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrInsertWithEscape(key, path, index, objects);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrInsertWithEscape(key, path, index, objects);\n  }\n\n  @Test\n  public void testJsonArrLen() {\n    String key = \"testKey\";\n    Long expectedResponse = 10L;\n\n    when(commandObjects.jsonArrLen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonArrLen(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonArrLen(key);\n  }\n\n  @Test\n  public void testJsonArrLenWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    Long expectedResponse = 10L;\n\n    when(commandObjects.jsonArrLen(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonArrLen(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonArrLen(key, path);\n  }\n\n  @Test\n  public void testJsonArrLenWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    List<Long> expectedResponse = Collections.singletonList(10L);\n\n    when(commandObjects.jsonArrLen(key, path)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrLen(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrLen(key, path);\n  }\n\n  @Test\n  public void testJsonArrPop() {\n    String key = \"testKey\";\n    Object expectedResponse = \"poppedValue\";\n\n    when(commandObjects.jsonArrPop(key)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonArrPop(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonArrPop(key);\n  }\n\n  @Test\n  public void testJsonArrPopWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    Object expectedResponse = \"poppedValue\";\n\n    when(commandObjects.jsonArrPop(key, path)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonArrPop(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonArrPop(key, path);\n  }\n\n  @Test\n  public void testJsonArrPopWithPathAndIndex() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    int index = 1;\n    Object expectedResponse = \"poppedValueAtIndex\";\n\n    when(commandObjects.jsonArrPop(key, path, index)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonArrPop(key, path, index);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonArrPop(key, path, index);\n  }\n\n  @Test\n  public void testJsonArrPopWithClassAndPath() {\n    String key = \"testKey\";\n    Class<String> clazz = String.class;\n    Path path = Path.of(\".path.to.array\");\n    String expectedResponse = \"poppedValue\";\n\n    when(commandObjects.jsonArrPop(key, clazz, path)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonArrPop(key, clazz, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonArrPop(key, clazz, path);\n  }\n\n  @Test\n  public void testJsonArrPopWithClassPathAndIndex() {\n    String key = \"testKey\";\n    Class<String> clazz = String.class;\n    Path path = Path.of(\".path.to.array\");\n    int index = 1;\n    String expectedResponse = \"poppedValueAtIndex\";\n\n    when(commandObjects.jsonArrPop(key, clazz, path, index)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonArrPop(key, clazz, path, index);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonArrPop(key, clazz, path, index);\n  }\n\n  @Test\n  public void testJsonArrPopWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    List<Object> expectedResponse = Collections.singletonList(\"poppedValue\");\n\n    when(commandObjects.jsonArrPop(key, path)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedResponse);\n\n    List<Object> result = jedis.jsonArrPop(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).jsonArrPop(key, path);\n  }\n\n  @Test\n  public void testJsonArrPopWithPath2AndIndex() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    int index = 1;\n    List<Object> expectedResponse = Collections.singletonList(\"poppedValueAtIndex\");\n\n    when(commandObjects.jsonArrPop(key, path, index)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedResponse);\n\n    List<Object> result = jedis.jsonArrPop(key, path, index);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).jsonArrPop(key, path, index);\n  }\n\n  @Test\n  public void testJsonArrPopWithClass() {\n    String key = \"testKey\";\n    Class<String> clazz = String.class;\n    String expectedResponse = \"poppedValue\";\n\n    when(commandObjects.jsonArrPop(key, clazz)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonArrPop(key, clazz);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonArrPop(key, clazz);\n  }\n\n  @Test\n  public void testJsonArrTrimWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.array\");\n    int start = 1;\n    int stop = 3;\n    Long expectedResponse = 3L;\n\n    when(commandObjects.jsonArrTrim(key, path, start, stop)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonArrTrim(key, path, start, stop);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonArrTrim(key, path, start, stop);\n  }\n\n  @Test\n  public void testJsonArrTrimWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.array\");\n    int start = 1;\n    int stop = 3;\n    List<Long> expectedResponse = Collections.singletonList(3L);\n\n    when(commandObjects.jsonArrTrim(key, path, start, stop)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonArrTrim(key, path, start, stop);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonArrTrim(key, path, start, stop);\n  }\n\n  @Test\n  public void testJsonClear() {\n    String key = \"testKey\";\n    long expectedResponse = 1L;\n\n    when(commandObjects.jsonClear(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonClear(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonClear(key);\n  }\n\n  @Test\n  public void testJsonClearWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    long expectedResponse = 1L;\n\n    when(commandObjects.jsonClear(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonClear(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonClear(key, path);\n  }\n\n  @Test\n  public void testJsonClearWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    long expectedResponse = 1L;\n\n    when(commandObjects.jsonClear(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonClear(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonClear(key, path);\n  }\n\n  @Test\n  public void testJsonDebugMemory() {\n    String key = \"testKey\";\n    long expectedResponse = 1024L;\n\n    when(commandObjects.jsonDebugMemory(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonDebugMemory(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonDebugMemory(key);\n  }\n\n  @Test\n  public void testJsonDebugMemoryWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    long expectedResponse = 512L;\n\n    when(commandObjects.jsonDebugMemory(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonDebugMemory(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonDebugMemory(key, path);\n  }\n\n  @Test\n  public void testJsonDebugMemoryWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    List<Long> expectedResponse = Collections.singletonList(512L);\n\n    when(commandObjects.jsonDebugMemory(key, path)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonDebugMemory(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonDebugMemory(key, path);\n  }\n\n  @Test\n  public void testJsonDel() {\n    String key = \"testKey\";\n    long expectedResponse = 1L;\n\n    when(commandObjects.jsonDel(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonDel(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonDel(key);\n  }\n\n  @Test\n  public void testJsonDelWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    long expectedResponse = 1L;\n\n    when(commandObjects.jsonDel(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonDel(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonDel(key, path);\n  }\n\n  @Test\n  public void testJsonDelWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    long expectedResponse = 1L;\n\n    when(commandObjects.jsonDel(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonDel(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonDel(key, path);\n  }\n\n  @Test\n  public void testJsonGet() {\n    String key = \"testKey\";\n    Object expectedResponse = new JsonObject();\n\n    when(commandObjects.jsonGet(key)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonGet(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonGet(key);\n  }\n\n  @Test\n  public void testJsonGetWithClass() {\n    String key = \"testKey\";\n    Class<MyBean> clazz = MyBean.class;\n    MyBean expectedResponse = new MyBean();\n\n    when(commandObjects.jsonGet(key, clazz)).thenReturn(myBeanCommandObject);\n    when(commandExecutor.executeCommand(myBeanCommandObject)).thenReturn(expectedResponse);\n\n    MyBean result = jedis.jsonGet(key, clazz);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(myBeanCommandObject);\n    verify(commandObjects).jsonGet(key, clazz);\n  }\n\n  @Test\n  public void testJsonGetWithPath() {\n    String key = \"testKey\";\n    Path[] paths = new Path[]{ Path.of(\".path.to.element1\"), Path.of(\".path.to.element2\") };\n    Object expectedResponse = new JsonObject();\n\n    when(commandObjects.jsonGet(key, paths)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonGet(key, paths);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonGet(key, paths);\n  }\n\n  @Test\n  public void testJsonGetWithPath2() {\n    String key = \"testKey\";\n    Path2[] paths = new Path2[]{ Path2.of(\".path.to.element1\"), Path2.of(\".path.to.element2\") };\n    Object expectedResponse = new JsonObject();\n\n    when(commandObjects.jsonGet(key, paths)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonGet(key, paths);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonGet(key, paths);\n  }\n\n  @Test\n  public void testJsonGetAsPlainString() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    String expectedResponse = \"{\\\"field\\\":\\\"value\\\"}\";\n\n    when(commandObjects.jsonGetAsPlainString(key, path)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonGetAsPlainString(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonGetAsPlainString(key, path);\n  }\n\n  @Test\n  public void testJsonGetWithClassAndPath() {\n    String key = \"testKey\";\n    Class<MyBean> clazz = MyBean.class;\n    Path[] paths = new Path[]{ Path.of(\".path.to.element1\"), Path.of(\".path.to.element2\") };\n    MyBean expectedResponse = new MyBean();\n\n    when(commandObjects.jsonGet(key, clazz, paths)).thenReturn(myBeanCommandObject);\n    when(commandExecutor.executeCommand(myBeanCommandObject)).thenReturn(expectedResponse);\n\n    MyBean result = jedis.jsonGet(key, clazz, paths);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(myBeanCommandObject);\n    verify(commandObjects).jsonGet(key, clazz, paths);\n  }\n\n  @Test\n  public void testJsonMergeWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    Object pojo = new MyBean();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonMerge(key, path, pojo)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonMerge(key, path, pojo);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonMerge(key, path, pojo);\n  }\n\n  @Test\n  public void testJsonMergeWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    Object object = new JsonObject();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonMerge(key, path, object)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonMerge(key, path, object);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonMerge(key, path, object);\n  }\n\n  @Test\n  public void testJsonMGetWithPathAndClass() {\n    Path path = Path.of(\".path.to.element\");\n    Class<MyBean> clazz = MyBean.class;\n    String[] keys = { \"testKey1\", \"testKey2\" };\n    List<MyBean> expectedResponse = Arrays.asList(new MyBean(), new MyBean());\n\n    when(commandObjects.jsonMGet(path, clazz, keys)).thenReturn(listMyBeanCommandObject);\n    when(commandExecutor.executeCommand(listMyBeanCommandObject)).thenReturn(expectedResponse);\n\n    List<MyBean> result = jedis.jsonMGet(path, clazz, keys);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listMyBeanCommandObject);\n    verify(commandObjects).jsonMGet(path, clazz, keys);\n  }\n\n  @Test\n  public void testJsonMGetWithPath2() {\n    Path2 path = Path2.of(\".path.to.element\");\n    String[] keys = { \"testKey1\", \"testKey2\" };\n    List<JSONArray> expectedResponse = Arrays.asList(new JSONArray(), new JSONArray());\n\n    when(commandObjects.jsonMGet(path, keys)).thenReturn(listJsonArrayCommandObject);\n    when(commandExecutor.executeCommand(listJsonArrayCommandObject)).thenReturn(expectedResponse);\n\n    List<JSONArray> result = jedis.jsonMGet(path, keys);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listJsonArrayCommandObject);\n    verify(commandObjects).jsonMGet(path, keys);\n  }\n\n  @Test\n  public void testJsonNumIncrByWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    double value = 10.5;\n    double expectedResponse = 20.5;\n\n    when(commandObjects.jsonNumIncrBy(key, path, value)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedResponse);\n\n    double result = jedis.jsonNumIncrBy(key, path, value);\n\n    assertEquals(expectedResponse, result, 0.0);\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).jsonNumIncrBy(key, path, value);\n  }\n\n  @Test\n  public void testJsonNumIncrByWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    double value = 10.5;\n    Object expectedResponse = 20.5;\n\n    when(commandObjects.jsonNumIncrBy(key, path, value)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.jsonNumIncrBy(key, path, value);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).jsonNumIncrBy(key, path, value);\n  }\n\n  @Test\n  public void testJsonSetWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    Object pojo = new MyBean();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSet(key, path, pojo)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSet(key, path, pojo);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSet(key, path, pojo);\n  }\n\n  @Test\n  public void testJsonSetWithPathAndParams() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    Object pojo = new MyBean();\n    JsonSetParams params = new JsonSetParams().nx();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSet(key, path, pojo, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSet(key, path, pojo, params);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSet(key, path, pojo, params);\n  }\n\n  @Test\n  public void testJsonSetWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    Object object = new JsonObject();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSet(key, path, object)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSet(key, path, object);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSet(key, path, object);\n  }\n\n  @Test\n  public void testJsonSetWithPath2WithEscape() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    Object object = new JsonObject();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSetWithEscape(key, path, object)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSetWithEscape(key, path, object);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSetWithEscape(key, path, object);\n  }\n\n  @Test\n  public void testJsonSetWithPath2AndParams() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    Object object = new JsonObject();\n    JsonSetParams params = new JsonSetParams().nx();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSet(key, path, object, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSet(key, path, object, params);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSet(key, path, object, params);\n  }\n\n  @Test\n  public void testJsonSetWithPath2EscapeAndParams() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    Object object = new JsonObject();\n    JsonSetParams params = new JsonSetParams().nx();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSetWithEscape(key, path, object, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSetWithEscape(key, path, object, params);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSetWithEscape(key, path, object, params);\n  }\n\n  @Test\n  public void testJsonSetWithPlainString() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    String jsonString = \"{\\\"field\\\":\\\"value\\\"}\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonSetWithPlainString(key, path, jsonString)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonSetWithPlainString(key, path, jsonString);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonSetWithPlainString(key, path, jsonString);\n  }\n\n  @Test\n  public void testJsonStrAppend() {\n    String key = \"testKey\";\n    Object string = \"additional string\";\n    long expectedResponse = 20L;\n\n    when(commandObjects.jsonStrAppend(key, string)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonStrAppend(key, string);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonStrAppend(key, string);\n  }\n\n  @Test\n  public void testJsonStrAppendWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    Object string = \"additional string\";\n    long expectedResponse = 20L;\n\n    when(commandObjects.jsonStrAppend(key, path, string)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.jsonStrAppend(key, path, string);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonStrAppend(key, path, string);\n  }\n\n  @Test\n  public void testJsonStrAppendWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    Object string = \"additional string\";\n    List<Long> expectedResponse = Collections.singletonList(20L);\n\n    when(commandObjects.jsonStrAppend(key, path, string)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonStrAppend(key, path, string);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonStrAppend(key, path, string);\n  }\n\n  @Test\n  public void testJsonStrLen() {\n    String key = \"testKey\";\n    Long expectedResponse = 15L;\n\n    when(commandObjects.jsonStrLen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonStrLen(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonStrLen(key);\n  }\n\n  @Test\n  public void testJsonStrLenWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    Long expectedResponse = 15L;\n\n    when(commandObjects.jsonStrLen(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonStrLen(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonStrLen(key, path);\n  }\n\n  @Test\n  public void testJsonStrLenWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    List<Long> expectedResponse = Collections.singletonList(15L);\n\n    when(commandObjects.jsonStrLen(key, path)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonStrLen(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonStrLen(key, path);\n  }\n\n  @Test\n  public void testJsonToggleWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.jsonToggle(key, path)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.jsonToggle(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).jsonToggle(key, path);\n  }\n\n  @Test\n  public void testJsonToggleWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.jsonToggle(key, path)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.jsonToggle(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).jsonToggle(key, path);\n  }\n\n  @Test\n  public void testJsonType() {\n    String key = \"testKey\";\n    Class<?> expectedResponse = String.class;\n\n    when(commandObjects.jsonType(key)).thenReturn(classCommandObject);\n    when(commandExecutor.executeCommand(classCommandObject)).thenAnswer(new Answer<Class<?>>() {\n      @Override\n      public Class<?> answer(InvocationOnMock invocationOnMock) {\n        return expectedResponse;\n      }\n    });\n\n    Class<?> result = jedis.jsonType(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(classCommandObject);\n    verify(commandObjects).jsonType(key);\n  }\n\n  @Test\n  public void testJsonTypeWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.element\");\n    Class<?> expectedResponse = String.class;\n\n    when(commandObjects.jsonType(key, path)).thenReturn(classCommandObject);\n    when(commandExecutor.executeCommand(classCommandObject)).thenAnswer(new Answer<Class<?>>() {\n      @Override\n      public Class<?> answer(InvocationOnMock invocationOnMock) {\n        return expectedResponse;\n      }\n    });\n\n    Class<?> result = jedis.jsonType(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(classCommandObject);\n    verify(commandObjects).jsonType(key, path);\n  }\n\n  @Test\n  public void testJsonTypeWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.element\");\n    List<Class<?>> expectedResponse = Collections.singletonList(String.class);\n\n    when(commandObjects.jsonType(key, path)).thenReturn(listClassCommandObject);\n    when(commandExecutor.executeCommand(listClassCommandObject)).thenReturn(expectedResponse);\n\n    List<Class<?>> result = jedis.jsonType(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listClassCommandObject);\n    verify(commandObjects).jsonType(key, path);\n  }\n\n  @Test\n  public void testJsonObjKeys() {\n    String key = \"testKey\";\n    List<String> expectedResponse = Arrays.asList(\"key1\", \"key2\", \"key3\");\n\n    when(commandObjects.jsonObjKeys(key)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.jsonObjKeys(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).jsonObjKeys(key);\n  }\n\n  @Test\n  public void testJsonObjKeysWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.object\");\n    List<String> expectedResponse = Arrays.asList(\"key1\", \"key2\");\n\n    when(commandObjects.jsonObjKeys(key, path)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.jsonObjKeys(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).jsonObjKeys(key, path);\n  }\n\n  @Test\n  public void testJsonObjKeysWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.object\");\n    List<List<String>> expectedResponse = Collections.singletonList(Arrays.asList(\"key1\", \"key2\"));\n\n    when(commandObjects.jsonObjKeys(key, path)).thenReturn(listListStringCommandObject);\n    when(commandExecutor.executeCommand(listListStringCommandObject)).thenReturn(expectedResponse);\n\n    List<List<String>> result = jedis.jsonObjKeys(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listListStringCommandObject);\n    verify(commandObjects).jsonObjKeys(key, path);\n  }\n\n  @Test\n  public void testJsonObjLen() {\n    String key = \"testKey\";\n    Long expectedResponse = 5L;\n\n    when(commandObjects.jsonObjLen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonObjLen(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonObjLen(key);\n  }\n\n  @Test\n  public void testJsonObjLenWithPath() {\n    String key = \"testKey\";\n    Path path = Path.of(\".path.to.object\");\n    Long expectedResponse = 3L;\n\n    when(commandObjects.jsonObjLen(key, path)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    Long result = jedis.jsonObjLen(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).jsonObjLen(key, path);\n  }\n\n  @Test\n  public void testJsonObjLenWithPath2() {\n    String key = \"testKey\";\n    Path2 path = Path2.of(\".path.to.object\");\n    List<Long> expectedResponse = Collections.singletonList(3L);\n\n    when(commandObjects.jsonObjLen(key, path)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.jsonObjLen(key, path);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).jsonObjLen(key, path);\n  }\n\n  @Test\n  public void testSetJsonObjectMapper() {\n    JsonObjectMapper jsonObjectMapper = mock(JsonObjectMapper.class);\n\n    jedis.setJsonObjectMapper(jsonObjectMapper);\n\n    verify(commandObjects).setJsonObjectMapper(jsonObjectMapper);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisListCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.args.ListDirection;\nimport redis.clients.jedis.args.ListPosition;\nimport redis.clients.jedis.params.LPosParams;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class UnifiedJedisListCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testBlmove() {\n    String srcKey = \"sourceList\";\n    String dstKey = \"destinationList\";\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n    double timeout = 10.5; // Timeout in seconds\n    String expectedMovedValue = \"value\";\n\n    when(commandObjects.blmove(srcKey, dstKey, from, to, timeout)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedMovedValue);\n\n    String result = jedis.blmove(srcKey, dstKey, from, to, timeout);\n\n    assertThat(result, equalTo(expectedMovedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).blmove(srcKey, dstKey, from, to, timeout);\n  }\n\n  @Test\n  public void testBlmoveBinary() {\n    byte[] srcKey = \"sourceList\".getBytes();\n    byte[] dstKey = \"destinationList\".getBytes();\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n    double timeout = 10.5; // Timeout in seconds\n    byte[] expectedMovedValue = \"value\".getBytes();\n\n    when(commandObjects.blmove(srcKey, dstKey, from, to, timeout)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedMovedValue);\n\n    byte[] result = jedis.blmove(srcKey, dstKey, from, to, timeout);\n\n    assertThat(result, equalTo(expectedMovedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).blmove(srcKey, dstKey, from, to, timeout);\n  }\n\n  @Test\n  public void testBlmpop() {\n    double timeout = 10.5; // Timeout in seconds\n    ListDirection direction = ListDirection.LEFT;\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    KeyValue<String, List<String>> expectedKeyValue = new KeyValue<>(\"listKey1\", Arrays.asList(\"value1\", \"value2\"));\n\n    when(commandObjects.blmpop(timeout, direction, keys)).thenReturn(keyValueStringListStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, List<String>> result = jedis.blmpop(timeout, direction, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringListStringCommandObject);\n    verify(commandObjects).blmpop(timeout, direction, keys);\n  }\n\n  @Test\n  public void testBlmpopBinary() {\n    double timeout = 10.5; // Timeout in seconds\n    ListDirection direction = ListDirection.LEFT;\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    KeyValue<byte[], List<byte[]>> expectedKeyValue = new KeyValue<>(\"listKey1\".getBytes(), Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes()));\n\n    when(commandObjects.blmpop(timeout, direction, keys)).thenReturn(keyValueBytesListBytesCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListBytesCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], List<byte[]>> result = jedis.blmpop(timeout, direction, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListBytesCommandObject);\n    verify(commandObjects).blmpop(timeout, direction, keys);\n  }\n\n  @Test\n  public void testBlmpopCount() {\n    double timeout = 10.5; // Timeout in seconds\n    ListDirection direction = ListDirection.RIGHT;\n    int count = 2;\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    KeyValue<String, List<String>> expectedKeyValue = new KeyValue<>(\"listKey2\", Arrays.asList(\"value3\", \"value4\"));\n\n    when(commandObjects.blmpop(timeout, direction, count, keys)).thenReturn(keyValueStringListStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, List<String>> result = jedis.blmpop(timeout, direction, count, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringListStringCommandObject);\n    verify(commandObjects).blmpop(timeout, direction, count, keys);\n  }\n\n  @Test\n  public void testBlmpopCountBinary() {\n    double timeout = 10.5; // Timeout in seconds\n    ListDirection direction = ListDirection.RIGHT;\n    int count = 2;\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    KeyValue<byte[], List<byte[]>> expectedKeyValue = new KeyValue<>(\"listKey2\".getBytes(), Arrays.asList(\"value3\".getBytes(), \"value4\".getBytes()));\n\n    when(commandObjects.blmpop(timeout, direction, count, keys)).thenReturn(keyValueBytesListBytesCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListBytesCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], List<byte[]>> result = jedis.blmpop(timeout, direction, count, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListBytesCommandObject);\n    verify(commandObjects).blmpop(timeout, direction, count, keys);\n  }\n\n  @Test\n  public void testBlpop() {\n    int timeout = 10; // Timeout in seconds\n    String key = \"listKey\";\n    List<String> expectedValues = Arrays.asList(key, \"value1\");\n\n    when(commandObjects.blpop(timeout, key)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.blpop(timeout, key);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).blpop(timeout, key);\n  }\n\n  @Test\n  public void testBlpopBinary() {\n    int timeout = 10; // Timeout in seconds\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    List<byte[]> expectedValues = Arrays.asList(\"listKey1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.blpop(timeout, keys)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedValues);\n\n    List<byte[]> result = jedis.blpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).blpop(timeout, keys);\n  }\n\n  @Test\n  public void testBlpopDoubleTimeout() {\n    double timeout = 10.5; // Timeout in seconds\n    String key = \"listKey\";\n    KeyValue<String, String> expectedKeyValue = new KeyValue<>(key, \"value1\");\n\n    when(commandObjects.blpop(timeout, key)).thenReturn(keyValueStringStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, String> result = jedis.blpop(timeout, key);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringStringCommandObject);\n    verify(commandObjects).blpop(timeout, key);\n  }\n\n  @Test\n  public void testBlpopDoubleTimeoutBinary() {\n    double timeout = 10.5; // Timeout in seconds\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    KeyValue<byte[], byte[]> expectedKeyValue = new KeyValue<>(\"listKey1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.blpop(timeout, keys)).thenReturn(keyValueBytesBytesCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesBytesCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], byte[]> result = jedis.blpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesBytesCommandObject);\n    verify(commandObjects).blpop(timeout, keys);\n  }\n\n  @Test\n  public void testBlpopMultipleKeys() {\n    int timeout = 10; // Timeout in seconds\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    List<String> expectedValues = Arrays.asList(\"listKey1\", \"value1\");\n\n    when(commandObjects.blpop(timeout, keys)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.blpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).blpop(timeout, keys);\n  }\n\n  @Test\n  public void testBlpopMultipleKeysDoubleTimeout() {\n    double timeout = 10.5; // Timeout in seconds\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    KeyValue<String, String> expectedKeyValue = new KeyValue<>(\"listKey1\", \"value1\");\n\n    when(commandObjects.blpop(timeout, keys)).thenReturn(keyValueStringStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, String> result = jedis.blpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringStringCommandObject);\n    verify(commandObjects).blpop(timeout, keys);\n  }\n\n  @Test\n  public void testBrpop() {\n    int timeout = 10; // Timeout in seconds\n    String key = \"listKey\";\n    List<String> expectedValues = Arrays.asList(key, \"value1\");\n\n    when(commandObjects.brpop(timeout, key)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.brpop(timeout, key);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).brpop(timeout, key);\n  }\n\n  @Test\n  public void testBrpopBinary() {\n    int timeout = 10; // Timeout in seconds\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    List<byte[]> expectedValues = Arrays.asList(\"listKey1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.brpop(timeout, keys)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedValues);\n\n    List<byte[]> result = jedis.brpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).brpop(timeout, keys);\n  }\n\n  @Test\n  public void testBrpopDoubleTimeout() {\n    double timeout = 10.5; // Timeout in seconds\n    String key = \"listKey\";\n    KeyValue<String, String> expectedKeyValue = new KeyValue<>(key, \"value1\");\n\n    when(commandObjects.brpop(timeout, key)).thenReturn(keyValueStringStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, String> result = jedis.brpop(timeout, key);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringStringCommandObject);\n    verify(commandObjects).brpop(timeout, key);\n  }\n\n  @Test\n  public void testBrpopDoubleTimeoutBinary() {\n    double timeout = 10.5; // Timeout in seconds\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    KeyValue<byte[], byte[]> expectedKeyValue = new KeyValue<>(\"listKey1\".getBytes(), \"value1\".getBytes());\n\n    when(commandObjects.brpop(timeout, keys)).thenReturn(keyValueBytesBytesCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesBytesCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], byte[]> result = jedis.brpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesBytesCommandObject);\n    verify(commandObjects).brpop(timeout, keys);\n  }\n\n  @Test\n  public void testBrpopMultipleKeys() {\n    int timeout = 10; // Timeout in seconds\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    List<String> expectedValues = Arrays.asList(\"listKey1\", \"value1\");\n\n    when(commandObjects.brpop(timeout, keys)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.brpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).brpop(timeout, keys);\n  }\n\n  @Test\n  public void testBrpopMultipleKeysDoubleTimeout() {\n    double timeout = 10.5; // Timeout in seconds\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    KeyValue<String, String> expectedKeyValue = new KeyValue<>(\"listKey1\", \"value1\");\n\n    when(commandObjects.brpop(timeout, keys)).thenReturn(keyValueStringStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, String> result = jedis.brpop(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringStringCommandObject);\n    verify(commandObjects).brpop(timeout, keys);\n  }\n\n  @Test\n  public void testBrpoplpush() {\n    String source = \"sourceList\";\n    String destination = \"destinationList\";\n    int timeout = 10; // Timeout in seconds\n    String expectedPoppedAndPushedValue = \"value\";\n\n    when(commandObjects.brpoplpush(source, destination, timeout)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPoppedAndPushedValue);\n\n    String result = jedis.brpoplpush(source, destination, timeout);\n\n    assertThat(result, equalTo(expectedPoppedAndPushedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).brpoplpush(source, destination, timeout);\n  }\n\n  @Test\n  public void testBrpoplpushBinary() {\n    byte[] source = \"sourceList\".getBytes();\n    byte[] destination = \"destinationList\".getBytes();\n    int timeout = 10; // Timeout in seconds\n    byte[] expectedPoppedAndPushedValue = \"value\".getBytes();\n\n    when(commandObjects.brpoplpush(source, destination, timeout)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPoppedAndPushedValue);\n\n    byte[] result = jedis.brpoplpush(source, destination, timeout);\n\n    assertThat(result, equalTo(expectedPoppedAndPushedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).brpoplpush(source, destination, timeout);\n  }\n\n  @Test\n  public void testLindex() {\n    String key = \"listKey\";\n    long index = 1; // Get the element at index 1\n    String expectedValue = \"value2\";\n\n    when(commandObjects.lindex(key, index)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedValue);\n\n    String result = jedis.lindex(key, index);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).lindex(key, index);\n  }\n\n  @Test\n  public void testLindexBinary() {\n    byte[] key = \"listKey\".getBytes();\n    long index = 1; // Get the element at index 1\n    byte[] expectedValue = \"value2\".getBytes();\n\n    when(commandObjects.lindex(key, index)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedValue);\n\n    byte[] result = jedis.lindex(key, index);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).lindex(key, index);\n  }\n\n  @Test\n  public void testLinsert() {\n    String key = \"listKey\";\n    ListPosition where = ListPosition.BEFORE;\n    String pivot = \"pivotValue\";\n    String value = \"newValue\";\n    long expectedInsertions = 1L; // Assuming one element was inserted\n\n    when(commandObjects.linsert(key, where, pivot, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedInsertions);\n\n    long result = jedis.linsert(key, where, pivot, value);\n\n    assertThat(result, equalTo(expectedInsertions));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).linsert(key, where, pivot, value);\n  }\n\n  @Test\n  public void testLinsertBinary() {\n    byte[] key = \"listKey\".getBytes();\n    ListPosition where = ListPosition.AFTER;\n    byte[] pivot = \"pivotValue\".getBytes();\n    byte[] value = \"newValue\".getBytes();\n    long expectedInsertions = 1L; // Assuming one element was inserted\n\n    when(commandObjects.linsert(key, where, pivot, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedInsertions);\n\n    long result = jedis.linsert(key, where, pivot, value);\n\n    assertThat(result, equalTo(expectedInsertions));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).linsert(key, where, pivot, value);\n  }\n\n  @Test\n  public void testLlen() {\n    String key = \"listKey\";\n    long expectedLength = 5L; // Assuming the length of the list is 5\n\n    when(commandObjects.llen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.llen(key);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).llen(key);\n  }\n\n  @Test\n  public void testLlenBinary() {\n    byte[] key = \"listKey\".getBytes();\n    long expectedLength = 5L; // Assuming the length of the list is 5\n\n    when(commandObjects.llen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.llen(key);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).llen(key);\n  }\n\n  @Test\n  public void testLmove() {\n    String srcKey = \"sourceList\";\n    String dstKey = \"destinationList\";\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n    String expectedMovedValue = \"value\";\n    when(commandObjects.lmove(srcKey, dstKey, from, to)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedMovedValue);\n\n    String result = jedis.lmove(srcKey, dstKey, from, to);\n\n    assertThat(result, equalTo(expectedMovedValue));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).lmove(srcKey, dstKey, from, to);\n  }\n\n  @Test\n  public void testLmoveBinary() {\n    byte[] srcKey = \"sourceList\".getBytes();\n    byte[] dstKey = \"destinationList\".getBytes();\n    ListDirection from = ListDirection.LEFT;\n    ListDirection to = ListDirection.RIGHT;\n    byte[] expectedMovedValue = \"value\".getBytes();\n\n    when(commandObjects.lmove(srcKey, dstKey, from, to)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedMovedValue);\n\n    byte[] result = jedis.lmove(srcKey, dstKey, from, to);\n\n    assertThat(result, equalTo(expectedMovedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).lmove(srcKey, dstKey, from, to);\n  }\n\n  @Test\n  public void testLmpop() {\n    ListDirection direction = ListDirection.LEFT;\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    KeyValue<String, List<String>> expectedKeyValue = new KeyValue<>(\"listKey1\", Arrays.asList(\"value1\", \"value2\"));\n\n    when(commandObjects.lmpop(direction, keys)).thenReturn(keyValueStringListStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, List<String>> result = jedis.lmpop(direction, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringListStringCommandObject);\n    verify(commandObjects).lmpop(direction, keys);\n  }\n\n  @Test\n  public void testLmpopBinary() {\n    ListDirection direction = ListDirection.LEFT;\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    KeyValue<byte[], List<byte[]>> expectedKeyValue = new KeyValue<>(\"listKey1\".getBytes(), Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes()));\n\n    when(commandObjects.lmpop(direction, keys)).thenReturn(keyValueBytesListBytesCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListBytesCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], List<byte[]>> result = jedis.lmpop(direction, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListBytesCommandObject);\n    verify(commandObjects).lmpop(direction, keys);\n  }\n\n  @Test\n  public void testLmpopCount() {\n    ListDirection direction = ListDirection.RIGHT;\n    int count = 2;\n    String[] keys = { \"listKey1\", \"listKey2\" };\n    KeyValue<String, List<String>> expectedKeyValue = new KeyValue<>(\"listKey2\", Arrays.asList(\"value3\", \"value4\"));\n\n    when(commandObjects.lmpop(direction, count, keys)).thenReturn(keyValueStringListStringCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListStringCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, List<String>> result = jedis.lmpop(direction, count, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringListStringCommandObject);\n    verify(commandObjects).lmpop(direction, count, keys);\n  }\n\n  @Test\n  public void testLmpopCountBinary() {\n    ListDirection direction = ListDirection.RIGHT;\n    int count = 2;\n    byte[][] keys = { \"listKey1\".getBytes(), \"listKey2\".getBytes() };\n    KeyValue<byte[], List<byte[]>> expectedKeyValue = new KeyValue<>(\"listKey2\".getBytes(), Arrays.asList(\"value3\".getBytes(), \"value4\".getBytes()));\n\n    when(commandObjects.lmpop(direction, count, keys)).thenReturn(keyValueBytesListBytesCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListBytesCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], List<byte[]>> result = jedis.lmpop(direction, count, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListBytesCommandObject);\n    verify(commandObjects).lmpop(direction, count, keys);\n  }\n\n  @Test\n  public void testLpop() {\n    String key = \"listKey\";\n    String expectedPoppedValue = \"poppedValue\";\n\n    when(commandObjects.lpop(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPoppedValue);\n\n    String result = jedis.lpop(key);\n\n    assertThat(result, equalTo(expectedPoppedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).lpop(key);\n  }\n\n  @Test\n  public void testLpopBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[] expectedPoppedValue = \"poppedValue\".getBytes();\n\n    when(commandObjects.lpop(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPoppedValue);\n\n    byte[] result = jedis.lpop(key);\n\n    assertThat(result, equalTo(expectedPoppedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).lpop(key);\n  }\n\n  @Test\n  public void testLpopCount() {\n    String key = \"listKey\";\n    int count = 2; // Pop two elements\n    List<String> expectedPoppedValues = Arrays.asList(\"value1\", \"value2\");\n\n    when(commandObjects.lpop(key, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedPoppedValues);\n\n    List<String> result = jedis.lpop(key, count);\n\n    assertThat(result, equalTo(expectedPoppedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).lpop(key, count);\n  }\n\n  @Test\n  public void testLpopCountBinary() {\n    byte[] key = \"listKey\".getBytes();\n    int count = 2; // Pop two elements\n    List<byte[]> expectedPoppedValues = Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes());\n\n    when(commandObjects.lpop(key, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedPoppedValues);\n\n    List<byte[]> result = jedis.lpop(key, count);\n\n    assertThat(result, equalTo(expectedPoppedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).lpop(key, count);\n  }\n\n  @Test\n  public void testLpos() {\n    String key = \"listKey\";\n    String element = \"valueToFind\";\n    Long expectedPosition = 1L; // Assuming the element is at index 1\n\n    when(commandObjects.lpos(key, element)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    Long result = jedis.lpos(key, element);\n\n    assertThat(result, equalTo(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpos(key, element);\n  }\n\n  @Test\n  public void testLposBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[] element = \"valueToFind\".getBytes();\n    Long expectedPosition = 1L; // Assuming the element is at index 1\n\n    when(commandObjects.lpos(key, element)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    Long result = jedis.lpos(key, element);\n\n    assertThat(result, equalTo(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpos(key, element);\n  }\n\n  @Test\n  public void testLposWithParams() {\n    String key = \"listKey\";\n    String element = \"valueToFind\";\n    LPosParams params = new LPosParams().rank(2); // Find the second occurrence\n    Long expectedPosition = 3L; // Assuming the second occurrence is at index 3\n\n    when(commandObjects.lpos(key, element, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    Long result = jedis.lpos(key, element, params);\n\n    assertThat(result, equalTo(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpos(key, element, params);\n  }\n\n  @Test\n  public void testLposWithParamsBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[] element = \"valueToFind\".getBytes();\n    LPosParams params = new LPosParams().rank(2); // Find the second occurrence\n    Long expectedPosition = 3L; // Assuming the second occurrence is at index 3\n\n    when(commandObjects.lpos(key, element, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPosition);\n\n    Long result = jedis.lpos(key, element, params);\n\n    assertThat(result, equalTo(expectedPosition));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpos(key, element, params);\n  }\n\n  @Test\n  public void testLposWithParamsCount() {\n    String key = \"listKey\";\n    String element = \"valueToFind\";\n    LPosParams params = new LPosParams().rank(1); // Find the first occurrence\n    long count = 2; // Find up to two positions\n    List<Long> expectedPositions = Arrays.asList(1L, 4L); // Assuming occurrences at indexes 1 and 4\n\n    when(commandObjects.lpos(key, element, params, count)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedPositions);\n\n    List<Long> result = jedis.lpos(key, element, params, count);\n\n    assertThat(result, equalTo(expectedPositions));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).lpos(key, element, params, count);\n  }\n\n  @Test\n  public void testLposWithParamsCountBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[] element = \"valueToFind\".getBytes();\n    LPosParams params = new LPosParams().rank(1); // Find the first occurrence\n    long count = 2; // Find up to two positions\n    List<Long> expectedPositions = Arrays.asList(1L, 4L); // Assuming occurrences at indexes 1 and 4\n\n    when(commandObjects.lpos(key, element, params, count)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedPositions);\n\n    List<Long> result = jedis.lpos(key, element, params, count);\n\n    assertThat(result, equalTo(expectedPositions));\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).lpos(key, element, params, count);\n  }\n\n  @Test\n  public void testLpush() {\n    String key = \"listKey\";\n    String[] strings = { \"value1\", \"value2\", \"value3\" };\n    long expectedLength = 3L; // Assuming the new length of the list is 3 after LPUSH\n\n    when(commandObjects.lpush(key, strings)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.lpush(key, strings);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpush(key, strings);\n  }\n\n  @Test\n  public void testLpushBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[][] args = { \"value1\".getBytes(), \"value2\".getBytes(), \"value3\".getBytes() };\n    long expectedLength = 3L; // Assuming the new length of the list is 3 after LPUSH\n\n    when(commandObjects.lpush(key, args)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.lpush(key, args);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpush(key, args);\n  }\n\n  @Test\n  public void testLpushx() {\n    String key = \"listKey\";\n    String[] strings = { \"value1\", \"value2\" };\n    long expectedLength = 5L; // Assuming the new length of the list is 5 after LPUSHX\n\n    when(commandObjects.lpushx(key, strings)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.lpushx(key, strings);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpushx(key, strings);\n  }\n\n  @Test\n  public void testLpushxBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[][] args = { \"value1\".getBytes(), \"value2\".getBytes() };\n    long expectedLength = 5L; // Assuming the new length of the list is 5 after LPUSHX\n\n    when(commandObjects.lpushx(key, args)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.lpushx(key, args);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lpushx(key, args);\n  }\n\n  @Test\n  public void testLrange() {\n    String key = \"listKey\";\n    long start = 0;\n    long stop = -1; // Get all elements in the list\n    List<String> expectedValues = Arrays.asList(\"value1\", \"value2\", \"value3\");\n\n    when(commandObjects.lrange(key, start, stop)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.lrange(key, start, stop);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).lrange(key, start, stop);\n  }\n\n  @Test\n  public void testLrangeBinary() {\n    byte[] key = \"listKey\".getBytes();\n    long start = 0;\n    long stop = -1; // Get all elements in the list\n    List<byte[]> expectedValues = Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes(), \"value3\".getBytes());\n\n    when(commandObjects.lrange(key, start, stop)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedValues);\n\n    List<byte[]> result = jedis.lrange(key, start, stop);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).lrange(key, start, stop);\n  }\n\n  @Test\n  public void testLrem() {\n    String key = \"listKey\";\n    long count = 1; // Remove the first occurrence\n    String value = \"valueToRemove\";\n    long expectedRemovals = 1L; // Assuming one element was removed\n\n    when(commandObjects.lrem(key, count, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.lrem(key, count, value);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lrem(key, count, value);\n  }\n\n  @Test\n  public void testLremBinary() {\n    byte[] key = \"listKey\".getBytes();\n    long count = 1; // Remove the first occurrence\n    byte[] value = \"valueToRemove\".getBytes();\n    long expectedRemovals = 1L; // Assuming one element was removed\n\n    when(commandObjects.lrem(key, count, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.lrem(key, count, value);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).lrem(key, count, value);\n  }\n\n  @Test\n  public void testLtrim() {\n    String key = \"listKey\";\n    long start = 1;\n    long stop = -1; // Trim the list to keep elements from index 1 to the end\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ltrim(key, start, stop)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ltrim(key, start, stop);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ltrim(key, start, stop);\n  }\n\n  @Test\n  public void testLtrimBinary() {\n    byte[] key = \"listKey\".getBytes();\n    long start = 1;\n    long stop = -1; // Trim the list to keep elements from index 1 to the end\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ltrim(key, start, stop)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ltrim(key, start, stop);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ltrim(key, start, stop);\n  }\n\n  @Test\n  public void testLset() {\n    String key = \"listKey\";\n    long index = 1; // Set the element at index 1\n    String value = \"newValue\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.lset(key, index, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.lset(key, index, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).lset(key, index, value);\n  }\n\n  @Test\n  public void testLsetBinary() {\n    byte[] key = \"listKey\".getBytes();\n    long index = 1; // Set the element at index 1\n    byte[] value = \"newValue\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.lset(key, index, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.lset(key, index, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).lset(key, index, value);\n  }\n\n  @Test\n  public void testRpop() {\n    String key = \"listKey\";\n    String expectedPoppedValue = \"poppedValue\";\n\n    when(commandObjects.rpop(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPoppedValue);\n\n    String result = jedis.rpop(key);\n\n    assertThat(result, equalTo(expectedPoppedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).rpop(key);\n  }\n\n  @Test\n  public void testRpopBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[] expectedPoppedValue = \"poppedValue\".getBytes();\n\n    when(commandObjects.rpop(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPoppedValue);\n\n    byte[] result = jedis.rpop(key);\n\n    assertThat(result, equalTo(expectedPoppedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).rpop(key);\n  }\n\n  @Test\n  public void testRpopCount() {\n    String key = \"listKey\";\n    int count = 2; // Pop two elements\n    List<String> expectedPoppedValues = Arrays.asList(\"value1\", \"value2\");\n\n    when(commandObjects.rpop(key, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedPoppedValues);\n\n    List<String> result = jedis.rpop(key, count);\n\n    assertThat(result, equalTo(expectedPoppedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).rpop(key, count);\n  }\n\n  @Test\n  public void testRpopCountBinary() {\n    byte[] key = \"listKey\".getBytes();\n    int count = 2; // Pop two elements\n    List<byte[]> expectedPoppedValues = Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes());\n\n    when(commandObjects.rpop(key, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedPoppedValues);\n\n    List<byte[]> result = jedis.rpop(key, count);\n\n    assertThat(result, equalTo(expectedPoppedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).rpop(key, count);\n  }\n\n  @Test\n  public void testRpoplpush() {\n    String srckey = \"sourceList\";\n    String dstkey = \"destinationList\";\n    String expectedPoppedAndPushedValue = \"value\";\n\n    when(commandObjects.rpoplpush(srckey, dstkey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPoppedAndPushedValue);\n\n    String result = jedis.rpoplpush(srckey, dstkey);\n\n    assertThat(result, equalTo(expectedPoppedAndPushedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).rpoplpush(srckey, dstkey);\n  }\n\n  @Test\n  public void testRpoplpushBinary() {\n    byte[] srckey = \"sourceList\".getBytes();\n    byte[] dstkey = \"destinationList\".getBytes();\n    byte[] expectedPoppedAndPushedValue = \"value\".getBytes();\n\n    when(commandObjects.rpoplpush(srckey, dstkey)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPoppedAndPushedValue);\n\n    byte[] result = jedis.rpoplpush(srckey, dstkey);\n\n    assertThat(result, equalTo(expectedPoppedAndPushedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).rpoplpush(srckey, dstkey);\n  }\n\n  @Test\n  public void testRpush() {\n    String key = \"listKey\";\n    String[] strings = { \"value1\", \"value2\", \"value3\" };\n    long expectedLength = 3L; // Assuming the new length of the list is 3 after RPUSH\n\n    when(commandObjects.rpush(key, strings)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.rpush(key, strings);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).rpush(key, strings);\n  }\n\n  @Test\n  public void testRpushBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[][] args = { \"value1\".getBytes(), \"value2\".getBytes(), \"value3\".getBytes() };\n    long expectedLength = 3L; // Assuming the new length of the list is 3 after RPUSH\n    when(commandObjects.rpush(key, args)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.rpush(key, args);\n\n    assertThat(result, equalTo(expectedLength));\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).rpush(key, args);\n  }\n\n  @Test\n  public void testRpushx() {\n    String key = \"listKey\";\n    String[] strings = { \"value1\", \"value2\" };\n    long expectedLength = 7L; // Assuming the new length of the list is 7 after RPUSHX\n\n    when(commandObjects.rpushx(key, strings)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.rpushx(key, strings);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).rpushx(key, strings);\n  }\n\n  @Test\n  public void testRpushxBinary() {\n    byte[] key = \"listKey\".getBytes();\n    byte[][] args = { \"value1\".getBytes(), \"value2\".getBytes() };\n    long expectedLength = 7L; // Assuming the new length of the list is 7 after RPUSHX\n\n    when(commandObjects.rpushx(key, args)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.rpushx(key, args);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).rpushx(key, args);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisMockedTestBase.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.mockito.Mock;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.CommandObjects;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.executors.CommandExecutor;\nimport redis.clients.jedis.mocked.MockedCommandObjectsTestBase;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\n/**\n * Base class for {@link UnifiedJedis} mocked unit tests. Exposes a {@link UnifiedJedis} instance that\n * uses mocked executors, providers and command objects, which can be asserted upon.\n */\npublic abstract class UnifiedJedisMockedTestBase extends MockedCommandObjectsTestBase {\n\n  /**\n   * The {@link UnifiedJedis} instance under-test.\n   */\n  protected UnifiedJedis jedis;\n\n  /**\n   * Mocked {@link CommandExecutor} instance. Instead of going to the wire and exchanging data\n   * with a real Redis server, this instance is trained to returned pre-packaged response data,\n   * depending on what is being tested.\n   */\n  @Mock\n  protected CommandExecutor commandExecutor;\n\n  /**\n   * Mocked {@link ConnectionProvider}. This is not really used in tests, except in some very\n   * specific test cases.\n   */\n  @Mock\n  protected ConnectionProvider connectionProvider;\n\n  /**\n   * {@link CommandObjects} instance used by the {@link UnifiedJedis} under-test. Depending on\n   * the test case, it is trained to return one of the mock {@link CommandObject} instances inherited\n   * from the superclass.\n   */\n  @Mock\n  protected CommandObjects commandObjects;\n\n  @BeforeEach\n  public void setUp() {\n    jedis = new UnifiedJedis(commandExecutor, connectionProvider, commandObjects);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    // We want to be accurate about our mocks, hence we verify no more interactions here.\n    // This might mean that some methods need to verify their interactions in a more verbose way,\n    // but overall the benefit should be greater than the cost.\n    verify(connectionProvider).getConnection();\n    verifyNoMoreInteractions(connectionProvider);\n    verifyNoMoreInteractions(commandExecutor);\n    verifyNoMoreInteractions(commandObjects);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisPubSubCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\n\npublic class UnifiedJedisPubSubCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testPublishWithStringChannelAndMessage() {\n    String channel = \"myChannel\";\n    String message = \"Hello, World!\";\n    long expectedPublishCount = 10L;\n\n    when(commandObjects.publish(channel, message)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPublishCount);\n\n    long result = jedis.publish(channel, message);\n\n    assertThat(result, equalTo(expectedPublishCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).publish(channel, message);\n  }\n\n  @Test\n  public void testPublishWithByteArrayChannelAndMessage() {\n    byte[] channel = \"myChannel\".getBytes();\n    byte[] message = \"Hello, World!\".getBytes();\n    long expectedPublishCount = 10L;\n\n    when(commandObjects.publish(channel, message)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedPublishCount);\n\n    long result = jedis.publish(channel, message);\n\n    assertThat(result, equalTo(expectedPublishCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).publish(channel, message);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisScriptingAndFunctionsCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.args.FlushMode;\nimport redis.clients.jedis.args.FunctionRestorePolicy;\nimport redis.clients.jedis.resps.FunctionStats;\nimport redis.clients.jedis.resps.LibraryInfo;\n\npublic class UnifiedJedisScriptingAndFunctionsCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testEval() {\n    String script = \"return 1\";\n    Object expectedEvalResult = 1;\n\n    when(commandObjects.eval(script)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.eval(script);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script);\n  }\n\n  @Test\n  public void testEvalBinary() {\n    byte[] script = \"return 1\".getBytes();\n    Object expectedEvalResult = 1;\n\n    when(commandObjects.eval(script)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.eval(script);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script);\n  }\n\n  @Test\n  public void testEvalWithParams() {\n    String script = \"return KEYS[1]\";\n    int keyCount = 1;\n    String[] params = { \"key1\" };\n    Object expectedEvalResult = \"key1\";\n\n    when(commandObjects.eval(script, keyCount, params)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.eval(script, keyCount, params);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script, keyCount, params);\n  }\n\n  @Test\n  public void testEvalWithParamsBinary() {\n    byte[] script = \"return KEYS[1]\".getBytes();\n    int keyCount = 1;\n    byte[][] params = { \"key1\".getBytes() };\n    Object expectedEvalResult = \"key1\".getBytes();\n\n    when(commandObjects.eval(script, keyCount, params)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.eval(script, keyCount, params);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script, keyCount, params);\n  }\n\n  @Test\n  public void testEvalWithLists() {\n    String script = \"return KEYS[1]\";\n    List<String> keys = Collections.singletonList(\"key1\");\n    List<String> args = Collections.emptyList();\n    Object expectedEvalResult = \"key1\";\n\n    when(commandObjects.eval(script, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.eval(script, keys, args);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script, keys, args);\n  }\n\n  @Test\n  public void testEvalWithListsBinary() {\n    byte[] script = \"return KEYS[1]\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.emptyList();\n    Object expectedEvalResult = \"key1\".getBytes();\n\n    when(commandObjects.eval(script, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.eval(script, keys, args);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script, keys, args);\n  }\n\n  @Test\n  public void testEvalWithSampleKey() {\n    String script = \"return redis.call('get', KEYS[1])\";\n    String sampleKey = \"myKey\";\n    Object expectedResponse = \"value\";\n\n    when(commandObjects.eval(script, sampleKey)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.eval(script, sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script, sampleKey);\n  }\n\n  @Test\n  public void testEvalWithSampleKeyBinary() {\n    byte[] script = \"return redis.call('get', KEYS[1])\".getBytes();\n    byte[] sampleKey = \"myKey\".getBytes();\n    Object expectedResponse = \"value\".getBytes();\n\n    when(commandObjects.eval(script, sampleKey)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.eval(script, sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).eval(script, sampleKey);\n  }\n\n  @Test\n  public void testEvalReadonly() {\n    String script = \"return KEYS[1]\";\n    List<String> keys = Collections.singletonList(\"key1\");\n    List<String> args = Collections.emptyList();\n    Object expectedEvalResult = \"key1\";\n\n    when(commandObjects.evalReadonly(script, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.evalReadonly(script, keys, args);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalReadonly(script, keys, args);\n  }\n\n  @Test\n  public void testEvalReadonlyBinary() {\n    byte[] script = \"return KEYS[1]\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.emptyList();\n    Object expectedEvalResult = \"key1\".getBytes();\n\n    when(commandObjects.evalReadonly(script, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalResult);\n\n    Object result = jedis.evalReadonly(script, keys, args);\n\n    assertThat(result, equalTo(expectedEvalResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalReadonly(script, keys, args);\n  }\n\n  @Test\n  public void testEvalsha() {\n    String sha1 = \"someSha1Hash\";\n    Object expectedEvalshaResult = 1;\n\n    when(commandObjects.evalsha(sha1)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalsha(sha1);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1);\n  }\n\n  @Test\n  public void testEvalshaBinary() {\n    byte[] sha1 = \"someSha1Hash\".getBytes();\n    Object expectedEvalshaResult = 1;\n\n    when(commandObjects.evalsha(sha1)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalsha(sha1);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1);\n  }\n\n  @Test\n  public void testEvalshaWithParams() {\n    String sha1 = \"someSha1Hash\";\n    int keyCount = 1;\n    String[] params = { \"key1\" };\n    Object expectedEvalshaResult = \"key1\";\n\n    when(commandObjects.evalsha(sha1, keyCount, params)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalsha(sha1, keyCount, params);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1, keyCount, params);\n  }\n\n  @Test\n  public void testEvalshaWithParamsBinary() {\n    byte[] sha1 = \"someSha1Hash\".getBytes();\n    int keyCount = 1;\n    byte[][] params = { \"key1\".getBytes() };\n    Object expectedEvalshaResult = \"key1\".getBytes();\n\n    when(commandObjects.evalsha(sha1, keyCount, params)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalsha(sha1, keyCount, params);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1, keyCount, params);\n  }\n\n  @Test\n  public void testEvalshaWithLists() {\n    String sha1 = \"someSha1Hash\";\n    List<String> keys = Collections.singletonList(\"key1\");\n    List<String> args = Collections.emptyList();\n    Object expectedEvalshaResult = \"key1\";\n\n    when(commandObjects.evalsha(sha1, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalsha(sha1, keys, args);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1, keys, args);\n  }\n\n  @Test\n  public void testEvalshaWithListsBinary() {\n    byte[] sha1 = \"someSha1Hash\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.emptyList();\n    Object expectedEvalshaResult = \"key1\".getBytes();\n\n    when(commandObjects.evalsha(sha1, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalsha(sha1, keys, args);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1, keys, args);\n  }\n\n  @Test\n  public void testEvalshaWithSampleKey() {\n    String sha1 = \"someSha1Hash\";\n    String sampleKey = \"myKey\";\n    Object expectedResponse = \"value\";\n\n    when(commandObjects.evalsha(sha1, sampleKey)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.evalsha(sha1, sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1, sampleKey);\n  }\n\n  @Test\n  public void testEvalshaWithSampleKeyBinary() {\n    byte[] sha1 = \"someSha1Hash\".getBytes();\n    byte[] sampleKey = \"myKey\".getBytes();\n    Object expectedResponse = \"value\".getBytes();\n\n    when(commandObjects.evalsha(sha1, sampleKey)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedResponse);\n\n    Object result = jedis.evalsha(sha1, sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalsha(sha1, sampleKey);\n  }\n\n  @Test\n  public void testEvalshaReadonly() {\n    String sha1 = \"someSha1Hash\";\n    List<String> keys = Collections.singletonList(\"key1\");\n    List<String> args = Collections.emptyList();\n    Object expectedEvalshaResult = \"key1\";\n\n    when(commandObjects.evalshaReadonly(sha1, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalshaReadonly(sha1, keys, args);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalshaReadonly(sha1, keys, args);\n  }\n\n  @Test\n  public void testEvalshaReadonlyBinary() {\n    byte[] sha1 = \"someSha1Hash\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.emptyList();\n    Object expectedEvalshaResult = \"key1\".getBytes();\n\n    when(commandObjects.evalshaReadonly(sha1, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedEvalshaResult);\n\n    Object result = jedis.evalshaReadonly(sha1, keys, args);\n\n    assertThat(result, equalTo(expectedEvalshaResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).evalshaReadonly(sha1, keys, args);\n  }\n\n  @Test\n  public void testFcall() {\n    String name = \"myFunction\";\n    List<String> keys = Collections.singletonList(\"key1\");\n    List<String> args = Collections.singletonList(\"arg1\");\n    Object expectedFcallResult = \"someResult\";\n\n    when(commandObjects.fcall(name, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedFcallResult);\n\n    Object result = jedis.fcall(name, keys, args);\n\n    assertThat(result, equalTo(expectedFcallResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).fcall(name, keys, args);\n  }\n\n  @Test\n  public void testFcallBinary() {\n    byte[] name = \"myFunction\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg1\".getBytes());\n    Object expectedFcallResult = \"someResult\".getBytes();\n\n    when(commandObjects.fcall(name, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedFcallResult);\n\n    Object result = jedis.fcall(name, keys, args);\n\n    assertThat(result, equalTo(expectedFcallResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).fcall(name, keys, args);\n  }\n\n  @Test\n  public void testFcallReadonly() {\n    String name = \"myFunction\";\n    List<String> keys = Collections.singletonList(\"key1\");\n    List<String> args = Collections.singletonList(\"arg1\");\n    Object expectedFcallResult = \"someResult\";\n\n    when(commandObjects.fcallReadonly(name, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedFcallResult);\n\n    Object result = jedis.fcallReadonly(name, keys, args);\n\n    assertThat(result, equalTo(expectedFcallResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).fcallReadonly(name, keys, args);\n  }\n\n  @Test\n  public void testFcallReadonlyBinary() {\n    byte[] name = \"myFunction\".getBytes();\n    List<byte[]> keys = Collections.singletonList(\"key1\".getBytes());\n    List<byte[]> args = Collections.singletonList(\"arg1\".getBytes());\n    Object expectedFcallResult = \"someResult\".getBytes();\n\n    when(commandObjects.fcallReadonly(name, keys, args)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedFcallResult);\n\n    Object result = jedis.fcallReadonly(name, keys, args);\n\n    assertThat(result, equalTo(expectedFcallResult));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).fcallReadonly(name, keys, args);\n  }\n\n  @Test\n  public void testFunctionDelete() {\n    String libraryName = \"mylib\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionDelete(libraryName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionDelete(libraryName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionDelete(libraryName);\n  }\n\n  @Test\n  public void testFunctionDeleteBinary() {\n    byte[] libraryName = \"mylib\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionDelete(libraryName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionDelete(libraryName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionDelete(libraryName);\n  }\n\n  @Test\n  public void testFunctionDump() {\n    byte[] expectedDump = \"someSerializedData\".getBytes();\n\n    when(commandObjects.functionDump()).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedDump);\n\n    byte[] result = jedis.functionDump();\n\n    assertThat(result, equalTo(expectedDump));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).functionDump();\n  }\n\n  @Test\n  public void testFunctionFlush() {\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionFlush()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionFlush();\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionFlush();\n  }\n\n  @Test\n  public void testFunctionFlushWithMode() {\n    FlushMode mode = FlushMode.ASYNC;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionFlush(mode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionFlush(mode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionFlush(mode);\n  }\n\n  @Test\n  public void testFunctionKill() {\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionKill()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionKill();\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionKill();\n  }\n\n  @Test\n  public void testFunctionList() {\n    List<LibraryInfo> expectedLibraryInfoList = new ArrayList<>();\n\n    when(commandObjects.functionList()).thenReturn(listLibraryInfoCommandObject);\n    when(commandExecutor.executeCommand(listLibraryInfoCommandObject)).thenReturn(expectedLibraryInfoList);\n\n    List<LibraryInfo> result = jedis.functionList();\n\n    assertThat(result, equalTo(expectedLibraryInfoList));\n\n    verify(commandExecutor).executeCommand(listLibraryInfoCommandObject);\n    verify(commandObjects).functionList();\n  }\n\n  @Test\n  public void testFunctionListBinary() {\n    List<Object> expectedFunctionListBinary = new ArrayList<>();\n\n    when(commandObjects.functionListBinary()).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedFunctionListBinary);\n\n    List<Object> result = jedis.functionListBinary();\n\n    assertThat(result, equalTo(expectedFunctionListBinary));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).functionListBinary();\n  }\n\n  @Test\n  public void testFunctionListWithPattern() {\n    String libraryNamePattern = \"mylib*\";\n    List<LibraryInfo> expectedLibraryInfoList = new ArrayList<>();\n\n    when(commandObjects.functionList(libraryNamePattern)).thenReturn(listLibraryInfoCommandObject);\n    when(commandExecutor.executeCommand(listLibraryInfoCommandObject)).thenReturn(expectedLibraryInfoList);\n\n    List<LibraryInfo> result = jedis.functionList(libraryNamePattern);\n\n    assertThat(result, equalTo(expectedLibraryInfoList));\n\n    verify(commandExecutor).executeCommand(listLibraryInfoCommandObject);\n    verify(commandObjects).functionList(libraryNamePattern);\n  }\n\n  @Test\n  public void testFunctionListWithPatternBinary() {\n    byte[] libraryNamePattern = \"mylib*\".getBytes();\n    List<Object> expectedFunctionList = new ArrayList<>();\n\n    when(commandObjects.functionList(libraryNamePattern)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedFunctionList);\n\n    List<Object> result = jedis.functionList(libraryNamePattern);\n\n    assertThat(result, equalTo(expectedFunctionList));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).functionList(libraryNamePattern);\n  }\n\n  @Test\n  public void testFunctionListWithCode() {\n    List<LibraryInfo> expectedLibraryInfoList = new ArrayList<>();\n\n    when(commandObjects.functionListWithCode()).thenReturn(listLibraryInfoCommandObject);\n    when(commandExecutor.executeCommand(listLibraryInfoCommandObject)).thenReturn(expectedLibraryInfoList);\n\n    List<LibraryInfo> result = jedis.functionListWithCode();\n\n    assertThat(result, equalTo(expectedLibraryInfoList));\n\n    verify(commandExecutor).executeCommand(listLibraryInfoCommandObject);\n    verify(commandObjects).functionListWithCode();\n  }\n\n  @Test\n  public void testFunctionListWithCodeBinary() {\n    List<Object> expectedFunctionListWithCodeBinary = new ArrayList<>();\n\n    when(commandObjects.functionListWithCodeBinary()).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedFunctionListWithCodeBinary);\n\n    List<Object> result = jedis.functionListWithCodeBinary();\n\n    assertThat(result, equalTo(expectedFunctionListWithCodeBinary));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).functionListWithCodeBinary();\n  }\n\n  @Test\n  public void testFunctionListWithCodeAndPattern() {\n    String libraryNamePattern = \"mylib*\";\n    List<LibraryInfo> expectedLibraryInfoList = new ArrayList<>();\n\n    when(commandObjects.functionListWithCode(libraryNamePattern)).thenReturn(listLibraryInfoCommandObject);\n    when(commandExecutor.executeCommand(listLibraryInfoCommandObject)).thenReturn(expectedLibraryInfoList);\n\n    List<LibraryInfo> result = jedis.functionListWithCode(libraryNamePattern);\n\n    assertThat(result, equalTo(expectedLibraryInfoList));\n\n    verify(commandExecutor).executeCommand(listLibraryInfoCommandObject);\n    verify(commandObjects).functionListWithCode(libraryNamePattern);\n  }\n\n  @Test\n  public void testFunctionListWithCodeAndPatternBinary() {\n    byte[] libraryNamePattern = \"mylib*\".getBytes();\n    List<Object> expectedFunctionListWithCode = new ArrayList<>();\n\n    when(commandObjects.functionListWithCode(libraryNamePattern)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedFunctionListWithCode);\n\n    List<Object> result = jedis.functionListWithCode(libraryNamePattern);\n\n    assertThat(result, equalTo(expectedFunctionListWithCode));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).functionListWithCode(libraryNamePattern);\n  }\n\n  @Test\n  public void testFunctionLoad() {\n    String functionCode = \"function myfunc() return 'hello' end\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionLoad(functionCode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionLoad(functionCode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionLoad(functionCode);\n  }\n\n  @Test\n  public void testFunctionLoadWithBinary() {\n    byte[] functionCode = \"function myfunc() return 'hello' end\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionLoad(functionCode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionLoad(functionCode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionLoad(functionCode);\n  }\n\n  @Test\n  public void testFunctionLoadReplace() {\n    String functionCode = \"function myfunc() return 'hello' end\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionLoadReplace(functionCode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionLoadReplace(functionCode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionLoadReplace(functionCode);\n  }\n\n  @Test\n  public void testFunctionLoadReplaceBinary() {\n    byte[] functionCode = \"function myfunc() return 'hello' end\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionLoadReplace(functionCode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionLoadReplace(functionCode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionLoadReplace(functionCode);\n  }\n\n  @Test\n  public void testFunctionRestore() {\n    byte[] serializedValue = \"serializedData\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionRestore(serializedValue)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionRestore(serializedValue);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionRestore(serializedValue);\n  }\n\n  @Test\n  public void testFunctionRestoreWithPolicy() {\n    byte[] serializedValue = \"serializedData\".getBytes();\n    FunctionRestorePolicy policy = FunctionRestorePolicy.FLUSH;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.functionRestore(serializedValue, policy)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.functionRestore(serializedValue, policy);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).functionRestore(serializedValue, policy);\n  }\n\n  @Test\n  public void testFunctionStats() {\n    FunctionStats expectedFunctionStats = mock(FunctionStats.class);\n\n    when(commandObjects.functionStats()).thenReturn(functionStatsCommandObject);\n    when(commandExecutor.executeCommand(functionStatsCommandObject)).thenReturn(expectedFunctionStats);\n\n    FunctionStats result = jedis.functionStats();\n\n    assertThat(result, sameInstance(expectedFunctionStats));\n\n    verify(commandExecutor).executeCommand(functionStatsCommandObject);\n    verify(commandObjects).functionStats();\n  }\n\n  @Test\n  public void testFunctionStatsBinary() {\n    Object expectedFunctionStatsBinary = new Object();\n\n    when(commandObjects.functionStatsBinary()).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedFunctionStatsBinary);\n\n    Object result = jedis.functionStatsBinary();\n\n    assertThat(result, equalTo(expectedFunctionStatsBinary));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).functionStatsBinary();\n  }\n\n  @Test\n  public void testScriptExistsWithSha1s() {\n    List<String> sha1s = Arrays.asList(\"sha1One\", \"sha1Two\");\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.scriptExists(sha1s)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.scriptExists(sha1s);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).scriptExists(sha1s);\n  }\n\n  @Test\n  public void testScriptExistsWithSha1AndSampleKey() {\n    String sha1 = \"someSha1Hash\";\n    String sampleKey = \"myKey\";\n    Boolean expectedResponse = true;\n\n    when(commandObjects.scriptExists(sampleKey, sha1)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(Collections.singletonList(expectedResponse));\n\n    Boolean result = jedis.scriptExists(sha1, sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).scriptExists(sampleKey, sha1);\n  }\n\n  @Test\n  public void testScriptExistsWithSha1AndSampleKeyBinary() {\n    byte[] sha1 = \"someSha1Hash\".getBytes();\n    byte[] sampleKey = \"myKey\".getBytes();\n    Boolean expectedResponse = true;\n\n    when(commandObjects.scriptExists(sampleKey, new byte[][]{ sha1 })).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(Collections.singletonList(expectedResponse));\n\n    Boolean result = jedis.scriptExists(sha1, sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).scriptExists(sampleKey, new byte[][]{ sha1 });\n  }\n\n  @Test\n  public void testScriptExistsWithKeyAndSha1s() {\n    String sampleKey = \"myKey\";\n    String[] sha1s = { \"sha1One\", \"sha1Two\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.scriptExists(sampleKey, sha1s)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.scriptExists(sampleKey, sha1s);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).scriptExists(sampleKey, sha1s);\n  }\n\n  @Test\n  public void testScriptExistsWithKeyAndSha1sBinary() {\n    byte[] sampleKey = \"myKey\".getBytes();\n    byte[][] sha1s = { \"sha1One\".getBytes(), \"sha1Two\".getBytes() };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.scriptExists(sampleKey, sha1s)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.scriptExists(sampleKey, sha1s);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).scriptExists(sampleKey, sha1s);\n  }\n\n  @Test\n  public void testScriptFlushWithoutKey() {\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptFlush()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptFlush();\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptFlush();\n  }\n\n  @Test\n  public void testScriptFlush() {\n    String sampleKey = \"myKey\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptFlush(sampleKey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptFlush(sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptFlush(sampleKey);\n  }\n\n  @Test\n  public void testScriptFlushBinary() {\n    byte[] sampleKey = \"myKey\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptFlush(sampleKey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptFlush(sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptFlush(sampleKey);\n  }\n\n  @Test\n  public void testScriptFlushWithMode() {\n    String sampleKey = \"myKey\";\n    FlushMode flushMode = FlushMode.SYNC;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptFlush(sampleKey, flushMode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptFlush(sampleKey, flushMode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptFlush(sampleKey, flushMode);\n  }\n\n  @Test\n  public void testScriptFlushWithModeBinary() {\n    byte[] sampleKey = \"myKey\".getBytes();\n    FlushMode flushMode = FlushMode.SYNC;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptFlush(sampleKey, flushMode)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptFlush(sampleKey, flushMode);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptFlush(sampleKey, flushMode);\n  }\n\n  @Test\n  public void testScriptKillWithoutKey() {\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptKill()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptKill();\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptKill();\n  }\n\n  @Test\n  public void testScriptKill() {\n    String sampleKey = \"myKey\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptKill(sampleKey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptKill(sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptKill(sampleKey);\n  }\n\n  @Test\n  public void testScriptKillBinary() {\n    byte[] sampleKey = \"myKey\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.scriptKill(sampleKey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.scriptKill(sampleKey);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptKill(sampleKey);\n  }\n\n  @Test\n  public void testScriptLoadWithoutKey() {\n    String script = \"return redis.call('get', 'constantKey')\";\n    String expectedSha1 = \"someSha1Hash\";\n\n    when(commandObjects.scriptLoad(script)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedSha1);\n\n    String result = jedis.scriptLoad(script);\n\n    assertThat(result, equalTo(expectedSha1));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptLoad(script);\n  }\n\n  @Test\n  public void testScriptLoad() {\n    String script = \"return redis.call('get', KEYS[1])\";\n    String sampleKey = \"myKey\";\n    String expectedSha1 = \"someSha1Hash\";\n\n    when(commandObjects.scriptLoad(script, sampleKey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedSha1);\n\n    String result = jedis.scriptLoad(script, sampleKey);\n\n    assertThat(result, equalTo(expectedSha1));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).scriptLoad(script, sampleKey);\n  }\n\n  @Test\n  public void testScriptLoadBinary() {\n    byte[] script = \"return redis.call('get', KEYS[1])\".getBytes();\n    byte[] sampleKey = \"myKey\".getBytes();\n    byte[] expectedSha1 = \"someSha1Hash\".getBytes();\n\n    when(commandObjects.scriptLoad(script, sampleKey)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedSha1);\n\n    byte[] result = jedis.scriptLoad(script, sampleKey);\n\n    assertThat(result, equalTo(expectedSha1));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).scriptLoad(script, sampleKey);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisSearchAndQueryCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.schemafields.SchemaField;\nimport redis.clients.jedis.search.schemafields.TextField;\n\npublic class UnifiedJedisSearchAndQueryCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testFtAggregate() {\n    String indexName = \"myIndex\";\n    AggregationBuilder aggr = new AggregationBuilder().groupBy(\"@field\");\n    AggregationResult expectedResponse = mock(AggregationResult.class);\n\n    when(commandObjects.ftAggregate(indexName, aggr)).thenReturn(aggregationResultCommandObject);\n    when(commandExecutor.executeCommand(aggregationResultCommandObject)).thenReturn(expectedResponse);\n\n    AggregationResult result = jedis.ftAggregate(indexName, aggr);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(aggregationResultCommandObject);\n    verify(commandObjects).ftAggregate(indexName, aggr);\n  }\n\n  @Test\n  public void testFtAliasAdd() {\n    String aliasName = \"myAlias\";\n    String indexName = \"myIndex\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftAliasAdd(aliasName, indexName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftAliasAdd(aliasName, indexName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftAliasAdd(aliasName, indexName);\n  }\n\n  @Test\n  public void testFtAliasDel() {\n    String aliasName = \"myAlias\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftAliasDel(aliasName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftAliasDel(aliasName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftAliasDel(aliasName);\n  }\n\n  @Test\n  public void testFtAliasUpdate() {\n    String aliasName = \"myAlias\";\n    String indexName = \"myIndex\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftAliasUpdate(aliasName, indexName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftAliasUpdate(aliasName, indexName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftAliasUpdate(aliasName, indexName);\n  }\n\n  @Test\n  public void testFtAlterWithSchema() {\n    String indexName = \"myIndex\";\n    Schema schema = new Schema().addField(new Schema.Field(\"myField\", Schema.FieldType.TEXT));\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftAlter(indexName, schema)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftAlter(indexName, schema);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftAlter(indexName, schema);\n  }\n\n  @Test\n  public void testFtAlterWithSchemaFields() {\n    String indexName = \"myIndex\";\n    Iterable<SchemaField> schemaFields = Collections.singletonList(new TextField(\"newField\"));\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftAlter(indexName, schemaFields)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftAlter(indexName, schemaFields);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftAlter(indexName, schemaFields);\n  }\n\n  @Test\n  public void testFtConfigGet() {\n    String option = \"TIMEOUT\";\n    Map<String, Object> expectedResponse = Collections.singletonMap(option, \"1000\");\n\n    when(commandObjects.ftConfigGet(option)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.ftConfigGet(option);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).ftConfigGet(option);\n  }\n\n  @Test\n  public void testFtConfigGetWithIndexName() {\n    String indexName = \"myIndex\";\n    String option = \"TIMEOUT\";\n    Map<String, Object> expectedResponse = Collections.singletonMap(option, \"1000\");\n\n    when(commandObjects.ftConfigGet(indexName, option)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.ftConfigGet(indexName, option);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).ftConfigGet(indexName, option);\n  }\n\n  @Test\n  public void testFtConfigSet() {\n    String option = \"TIMEOUT\";\n    String value = \"1000\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftConfigSet(option, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftConfigSet(option, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftConfigSet(option, value);\n  }\n\n  @Test\n  public void testFtConfigSetWithIndexName() {\n    String indexName = \"myIndex\";\n    String option = \"TIMEOUT\";\n    String value = \"1000\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftConfigSet(indexName, option, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftConfigSet(indexName, option, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftConfigSet(indexName, option, value);\n  }\n\n  @Test\n  public void testFtCreateWithOptionsAndSchema() {\n    String indexName = \"myIndex\";\n    IndexOptions indexOptions = IndexOptions.defaultOptions();\n    Schema schema = new Schema().addField(new Schema.Field(\"myField\", Schema.FieldType.TEXT));\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftCreate(indexName, indexOptions, schema)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftCreate(indexName, indexOptions, schema);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftCreate(indexName, indexOptions, schema);\n  }\n\n  @Test\n  public void testFtCreateWithCreateParamsAndSchemaFields() {\n    String indexName = \"myIndex\";\n    FTCreateParams createParams = FTCreateParams.createParams();\n    Iterable<SchemaField> schemaFields = Collections.singletonList(new TextField(\"myField\"));\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftCreate(indexName, createParams, schemaFields)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftCreate(indexName, createParams, schemaFields);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftCreate(indexName, createParams, schemaFields);\n  }\n\n  @Test\n  public void testFtCursorDel() {\n    String indexName = \"myIndex\";\n    long cursorId = 123L;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftCursorDel(indexName, cursorId)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftCursorDel(indexName, cursorId);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftCursorDel(indexName, cursorId);\n  }\n\n  @Test\n  public void testFtCursorRead() {\n    String indexName = \"myIndex\";\n    long cursorId = 123L;\n    int count = 10;\n    AggregationResult expectedResponse = mock(AggregationResult.class);\n\n    when(commandObjects.ftCursorRead(indexName, cursorId, count)).thenReturn(aggregationResultCommandObject);\n    when(commandExecutor.executeCommand(aggregationResultCommandObject)).thenReturn(expectedResponse);\n\n    AggregationResult result = jedis.ftCursorRead(indexName, cursorId, count);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(aggregationResultCommandObject);\n    verify(commandObjects).ftCursorRead(indexName, cursorId, count);\n  }\n\n  @Test\n  public void testFtDictAdd() {\n    String dictionary = \"myDict\";\n    String[] terms = { \"term1\", \"term2\" };\n    long expectedResponse = 2L;\n\n    when(commandObjects.ftDictAdd(dictionary, terms)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftDictAdd(dictionary, terms);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftDictAdd(dictionary, terms);\n  }\n\n  @Test\n  public void testFtDictAddBySampleKey() {\n    String indexName = \"myIndex\";\n    String dictionary = \"myDict\";\n    String[] terms = { \"term1\", \"term2\" };\n    long expectedResponse = 2L;\n\n    when(commandObjects.ftDictAddBySampleKey(indexName, dictionary, terms)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftDictAddBySampleKey(indexName, dictionary, terms);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftDictAddBySampleKey(indexName, dictionary, terms);\n  }\n\n  @Test\n  public void testFtDictDel() {\n    String dictionary = \"myDict\";\n    String[] terms = { \"term1\", \"term2\" };\n    long expectedResponse = 1L;\n\n    when(commandObjects.ftDictDel(dictionary, terms)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftDictDel(dictionary, terms);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftDictDel(dictionary, terms);\n  }\n\n  @Test\n  public void testFtDictDelBySampleKey() {\n    String indexName = \"myIndex\";\n    String dictionary = \"myDict\";\n    String[] terms = { \"term1\", \"term2\" };\n    long expectedResponse = 1L;\n\n    when(commandObjects.ftDictDelBySampleKey(indexName, dictionary, terms)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftDictDelBySampleKey(indexName, dictionary, terms);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftDictDelBySampleKey(indexName, dictionary, terms);\n  }\n\n  @Test\n  public void testFtDictDump() {\n    String dictionary = \"myDict\";\n    Set<String> expectedResponse = new HashSet<>(Arrays.asList(\"term1\", \"term2\"));\n\n    when(commandObjects.ftDictDump(dictionary)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedResponse);\n\n    Set<String> result = jedis.ftDictDump(dictionary);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).ftDictDump(dictionary);\n  }\n\n  @Test\n  public void testFtDictDumpBySampleKey() {\n    String indexName = \"myIndex\";\n    String dictionary = \"myDict\";\n    Set<String> expectedResponse = new HashSet<>(Arrays.asList(\"term1\", \"term2\"));\n\n    when(commandObjects.ftDictDumpBySampleKey(indexName, dictionary)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedResponse);\n\n    Set<String> result = jedis.ftDictDumpBySampleKey(indexName, dictionary);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).ftDictDumpBySampleKey(indexName, dictionary);\n  }\n\n  @Test\n  public void testFtDropIndex() {\n    String indexName = \"myIndex\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftDropIndex(indexName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftDropIndex(indexName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftDropIndex(indexName);\n  }\n\n  @Test\n  public void testFtDropIndexDD() {\n    String indexName = \"myIndex\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftDropIndexDD(indexName)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftDropIndexDD(indexName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftDropIndexDD(indexName);\n  }\n\n  @Test\n  public void testFtExplain() {\n    String indexName = \"myIndex\";\n    Query query = new Query(\"hello world\").limit(0, 10);\n    String expectedResponse = \"QUERY PLAN\";\n\n    when(commandObjects.ftExplain(indexName, query)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftExplain(indexName, query);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftExplain(indexName, query);\n  }\n\n  @Test\n  public void testFtExplainCLI() {\n    String indexName = \"myIndex\";\n    Query query = new Query(\"hello world\").limit(0, 10);\n    List<String> expectedResponse = Arrays.asList(\"QUERY PLAN\", \"DETAILS\");\n\n    when(commandObjects.ftExplainCLI(indexName, query)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.ftExplainCLI(indexName, query);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).ftExplainCLI(indexName, query);\n  }\n\n  @Test\n  public void testFtInfo() {\n    String indexName = \"myIndex\";\n    Map<String, Object> expectedResponse = Collections.singletonMap(\"index_definition\", Collections.singletonMap(\"key_type\", \"HASH\"));\n\n    when(commandObjects.ftInfo(indexName)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.ftInfo(indexName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).ftInfo(indexName);\n  }\n\n  @Test\n  public void testFtList() {\n    Set<String> expectedResponse = new HashSet<>(Arrays.asList(\"index1\", \"index2\"));\n\n    when(commandObjects.ftList()).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedResponse);\n\n    Set<String> result = jedis.ftList();\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).ftList();\n  }\n\n  @Test\n  public void testFtSearch() {\n    String indexName = \"myIndex\";\n    String query = \"hello world\";\n    SearchResult expectedResponse = mock(SearchResult.class);\n\n    when(commandObjects.ftSearch(indexName, query)).thenReturn(searchResultCommandObject);\n    when(commandExecutor.executeCommand(searchResultCommandObject)).thenReturn(expectedResponse);\n\n    SearchResult result = jedis.ftSearch(indexName, query);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(searchResultCommandObject);\n    verify(commandObjects).ftSearch(indexName, query);\n  }\n\n  @Test\n  public void testFtSearchWithParams() {\n    String indexName = \"myIndex\";\n    String query = \"hello world\";\n    FTSearchParams params = new FTSearchParams().noContent().limit(0, 10);\n    SearchResult expectedResponse = mock(SearchResult.class);\n\n    when(commandObjects.ftSearch(indexName, query, params)).thenReturn(searchResultCommandObject);\n    when(commandExecutor.executeCommand(searchResultCommandObject)).thenReturn(expectedResponse);\n\n    SearchResult result = jedis.ftSearch(indexName, query, params);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(searchResultCommandObject);\n    verify(commandObjects).ftSearch(indexName, query, params);\n  }\n\n  @Test\n  public void testFtSearchWithQueryObject() {\n    String indexName = \"myIndex\";\n    Query query = new Query(\"hello world\");\n    SearchResult expectedResponse = mock(SearchResult.class);\n\n    when(commandObjects.ftSearch(indexName, query)).thenReturn(searchResultCommandObject);\n    when(commandExecutor.executeCommand(searchResultCommandObject)).thenReturn(expectedResponse);\n\n    SearchResult result = jedis.ftSearch(indexName, query);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(searchResultCommandObject);\n    verify(commandObjects).ftSearch(indexName, query);\n  }\n\n  @Test\n  public void testFtSearchWithQueryObjectBinary() {\n    byte[] indexName = \"myIndex\".getBytes();\n    Query query = new Query(\"hello world\").limit(0, 10);\n    SearchResult expectedResponse = mock(SearchResult.class);\n\n    when(commandObjects.ftSearch(indexName, query)).thenReturn(searchResultCommandObject);\n    when(commandExecutor.executeCommand(searchResultCommandObject)).thenReturn(expectedResponse);\n\n    SearchResult result = jedis.ftSearch(indexName, query);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(searchResultCommandObject);\n    verify(commandObjects).ftSearch(indexName, query);\n  }\n\n  @Test\n  public void testFtSpellCheck() {\n    String index = \"myIndex\";\n    String query = \"hello world\";\n    Map<String, Map<String, Double>> expectedResponse = Collections.singletonMap(\"term1\", Collections.singletonMap(\"suggestion1\", 1.0));\n\n    when(commandObjects.ftSpellCheck(index, query)).thenReturn(mapStringMapStringDoubleCommandObject);\n    when(commandExecutor.executeCommand(mapStringMapStringDoubleCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Map<String, Double>> result = jedis.ftSpellCheck(index, query);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringMapStringDoubleCommandObject);\n    verify(commandObjects).ftSpellCheck(index, query);\n  }\n\n  @Test\n  public void testFtSpellCheckWithParams() {\n    String index = \"myIndex\";\n    String query = \"hello world\";\n    FTSpellCheckParams spellCheckParams = new FTSpellCheckParams().distance(1);\n    Map<String, Map<String, Double>> expectedResponse = Collections.singletonMap(\"term1\", Collections.singletonMap(\"suggestion1\", 1.0));\n\n    when(commandObjects.ftSpellCheck(index, query, spellCheckParams)).thenReturn(mapStringMapStringDoubleCommandObject);\n    when(commandExecutor.executeCommand(mapStringMapStringDoubleCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Map<String, Double>> result = jedis.ftSpellCheck(index, query, spellCheckParams);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringMapStringDoubleCommandObject);\n    verify(commandObjects).ftSpellCheck(index, query, spellCheckParams);\n  }\n\n  @Test\n  public void testFtSynDump() {\n    String indexName = \"myIndex\";\n    Map<String, List<String>> expectedResponse = Collections.singletonMap(\"group1\", Arrays.asList(\"term1\", \"term2\"));\n\n    when(commandObjects.ftSynDump(indexName)).thenReturn(mapStringListStringCommandObject);\n    when(commandExecutor.executeCommand(mapStringListStringCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, List<String>> result = jedis.ftSynDump(indexName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringListStringCommandObject);\n    verify(commandObjects).ftSynDump(indexName);\n  }\n\n  @Test\n  public void testFtSynUpdate() {\n    String indexName = \"myIndex\";\n    String synonymGroupId = \"group1\";\n    String[] terms = { \"term1\", \"term2\" };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.ftSynUpdate(indexName, synonymGroupId, terms)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.ftSynUpdate(indexName, synonymGroupId, terms);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).ftSynUpdate(indexName, synonymGroupId, terms);\n  }\n\n  @Test\n  public void testFtTagVals() {\n    String indexName = \"myIndex\";\n    String fieldName = \"myField\";\n    Set<String> expectedResponse = new HashSet<>(Arrays.asList(\"tag1\", \"tag2\"));\n\n    when(commandObjects.ftTagVals(indexName, fieldName)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedResponse);\n\n    Set<String> result = jedis.ftTagVals(indexName, fieldName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).ftTagVals(indexName, fieldName);\n  }\n\n  @Test\n  public void testFtSugAdd() {\n    String key = \"sugKey\";\n    String string = \"suggestion\";\n    double score = 1.0;\n    long expectedResponse = 1L;\n\n    when(commandObjects.ftSugAdd(key, string, score)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftSugAdd(key, string, score);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftSugAdd(key, string, score);\n  }\n\n  @Test\n  public void testFtSugAddIncr() {\n    String key = \"sugKey\";\n    String string = \"suggestion\";\n    double score = 1.0;\n    long expectedResponse = 2L;\n\n    when(commandObjects.ftSugAddIncr(key, string, score)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftSugAddIncr(key, string, score);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftSugAddIncr(key, string, score);\n  }\n\n  @Test\n  public void testFtSugDel() {\n    String key = \"sugKey\";\n    String string = \"suggestion\";\n    boolean expectedResponse = true;\n\n    when(commandObjects.ftSugDel(key, string)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.ftSugDel(key, string);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).ftSugDel(key, string);\n  }\n\n  @Test\n  public void testFtSugGet() {\n    String key = \"sugKey\";\n    String prefix = \"sug\";\n    List<String> expectedResponse = Arrays.asList(\"suggestion1\", \"suggestion2\");\n\n    when(commandObjects.ftSugGet(key, prefix)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.ftSugGet(key, prefix);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).ftSugGet(key, prefix);\n  }\n\n  @Test\n  public void testFtSugGetWithFuzzyAndMax() {\n    String key = \"sugKey\";\n    String prefix = \"sug\";\n    boolean fuzzy = true;\n    int max = 10;\n    List<String> expectedResponse = Arrays.asList(\"suggestion1\", \"suggestion2\");\n\n    when(commandObjects.ftSugGet(key, prefix, fuzzy, max)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.ftSugGet(key, prefix, fuzzy, max);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).ftSugGet(key, prefix, fuzzy, max);\n  }\n\n  @Test\n  public void testFtSugGetWithScores() {\n    String key = \"sugKey\";\n    String prefix = \"sug\";\n    List<Tuple> expectedResponse = Arrays.asList(new Tuple(\"suggestion1\", 1.0), new Tuple(\"suggestion2\", 0.8));\n\n    when(commandObjects.ftSugGetWithScores(key, prefix)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedResponse);\n\n    List<Tuple> result = jedis.ftSugGetWithScores(key, prefix);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).ftSugGetWithScores(key, prefix);\n  }\n\n  @Test\n  public void testFtSugGetWithScoresAndFuzzyMax() {\n    String key = \"sugKey\";\n    String prefix = \"sug\";\n    boolean fuzzy = true;\n    int max = 10;\n    List<Tuple> expectedResponse = Arrays.asList(new Tuple(\"suggestion1\", 1.0), new Tuple(\"suggestion2\", 0.8));\n\n    when(commandObjects.ftSugGetWithScores(key, prefix, fuzzy, max)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedResponse);\n\n    List<Tuple> result = jedis.ftSugGetWithScores(key, prefix, fuzzy, max);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).ftSugGetWithScores(key, prefix, fuzzy, max);\n  }\n\n  @Test\n  public void testFtSugLen() {\n    String key = \"sugKey\";\n    long expectedResponse = 42L;\n\n    when(commandObjects.ftSugLen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.ftSugLen(key);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).ftSugLen(key);\n  }\n\n  @Test\n  public void testFtProfileAggregate() {\n    String indexName = \"myIndex\";\n    FTProfileParams profileParams = new FTProfileParams();\n    AggregationBuilder aggr = new AggregationBuilder().groupBy(\"@field\");\n    Map.Entry<AggregationResult, ProfilingInfo> expectedResponse = new AbstractMap.SimpleEntry<>(\n        mock(AggregationResult.class), mock(ProfilingInfo.class));\n\n    when(commandObjects.ftProfileAggregate(indexName, profileParams, aggr)).thenReturn(entryAggregationResultMapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(entryAggregationResultMapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<AggregationResult, ProfilingInfo> result = jedis.ftProfileAggregate(indexName, profileParams, aggr);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entryAggregationResultMapStringObjectCommandObject);\n    verify(commandObjects).ftProfileAggregate(indexName, profileParams, aggr);\n  }\n\n  @Test\n  public void testFtProfileSearchWithQueryObject() {\n    String indexName = \"myIndex\";\n    FTProfileParams profileParams = new FTProfileParams();\n    Query query = new Query(\"hello world\").limit(0, 10);\n    Map.Entry<SearchResult, ProfilingInfo> expectedResponse = new AbstractMap.SimpleEntry<>(\n        mock(SearchResult.class), mock(ProfilingInfo.class));\n\n    when(commandObjects.ftProfileSearch(indexName, profileParams, query)).thenReturn(entrySearchResultMapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(entrySearchResultMapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<SearchResult, ProfilingInfo> result = jedis.ftProfileSearch(indexName, profileParams, query);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entrySearchResultMapStringObjectCommandObject);\n    verify(commandObjects).ftProfileSearch(indexName, profileParams, query);\n  }\n\n  @Test\n  public void testFtProfileSearchWithQueryAndSearchParams() {\n    String indexName = \"myIndex\";\n    FTProfileParams profileParams = new FTProfileParams();\n    String query = \"hello world\";\n    FTSearchParams searchParams = new FTSearchParams().noContent().limit(0, 10);\n    Map.Entry<SearchResult, ProfilingInfo> expectedResponse = new AbstractMap.SimpleEntry<>(\n        mock(SearchResult.class), mock(ProfilingInfo.class));\n\n    when(commandObjects.ftProfileSearch(indexName, profileParams, query, searchParams)).thenReturn(entrySearchResultMapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(entrySearchResultMapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<SearchResult, ProfilingInfo> result = jedis.ftProfileSearch(indexName, profileParams, query, searchParams);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entrySearchResultMapStringObjectCommandObject);\n    verify(commandObjects).ftProfileSearch(indexName, profileParams, query, searchParams);\n  }\n\n  @Test\n  public void testSetDefaultSearchDialect() {\n    int dialect = 1;\n\n    jedis.setDefaultSearchDialect(dialect);\n\n    verify(commandObjects).setDefaultSearchDialect(dialect);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisServerManagementCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\n\npublic class UnifiedJedisServerManagementCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testConfigSet() {\n    String parameter = \"param\";\n    String value = \"value\";\n\n    when(commandObjects.configSet(parameter, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.configSet(parameter, value);\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).configSet(parameter, value);\n  }\n\n  @Test\n  public void testDbSize() {\n    long expectedSize = 42L;\n\n    when(commandObjects.dbSize()).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedSize);\n\n    long result = jedis.dbSize();\n\n    assertThat(result, equalTo(expectedSize));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).dbSize();\n  }\n\n  @Test\n  public void testFlushAll() {\n    when(commandObjects.flushAll()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.flushAll();\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).flushAll();\n  }\n\n  @Test\n  public void testFlushDB() {\n    when(commandObjects.flushDB()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(\"OK\");\n\n    String result = jedis.flushDB();\n\n    assertThat(result, equalTo(\"OK\"));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).flushDB();\n  }\n\n  @Test\n  public void testMemoryUsage() {\n    String key = \"key1\";\n    Long expectedMemoryUsage = 1024L;\n\n    when(commandObjects.memoryUsage(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedMemoryUsage);\n\n    Long result = jedis.memoryUsage(key);\n\n    assertThat(result, equalTo(expectedMemoryUsage));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).memoryUsage(key);\n  }\n\n  @Test\n  public void testMemoryUsageWithSamples() {\n    String key = \"key1\";\n    int samples = 5;\n    Long expectedMemoryUsage = 2048L;\n\n    when(commandObjects.memoryUsage(key, samples)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedMemoryUsage);\n\n    Long result = jedis.memoryUsage(key, samples);\n\n    assertThat(result, equalTo(expectedMemoryUsage));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).memoryUsage(key, samples);\n  }\n\n  @Test\n  public void testMemoryUsageBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    Long expectedMemoryUsage = 512L;\n\n    when(commandObjects.memoryUsage(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedMemoryUsage);\n\n    Long result = jedis.memoryUsage(key);\n\n    assertThat(result, equalTo(expectedMemoryUsage));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).memoryUsage(key);\n  }\n\n  @Test\n  public void testMemoryUsageWithSamplesBinary() {\n    byte[] key = new byte[]{ 1, 2, 3 };\n    int samples = 5;\n    Long expectedMemoryUsage = 1024L;\n\n    when(commandObjects.memoryUsage(key, samples)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedMemoryUsage);\n\n    Long result = jedis.memoryUsage(key, samples);\n\n    assertThat(result, equalTo(expectedMemoryUsage));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).memoryUsage(key, samples);\n  }\n\n  @Test\n  public void testSlowlogReset() {\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.slowlogReset()).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.slowlogReset();\n\n    assertThat(result, equalTo(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).slowlogReset();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisSetCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.resps.ScanResult;\n\npublic class UnifiedJedisSetCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testSadd() {\n    String key = \"setKey\";\n    String[] members = { \"member1\", \"member2\" };\n    long expectedAdded = 2L; // Assuming both members were added\n\n    when(commandObjects.sadd(key, members)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.sadd(key, members);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sadd(key, members);\n  }\n\n  @Test\n  public void testSaddBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes() };\n    long expectedAdded = 2L; // Assuming both members were added\n\n    when(commandObjects.sadd(key, members)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.sadd(key, members);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sadd(key, members);\n  }\n\n  @Test\n  public void testScard() {\n    String key = \"setKey\";\n    long expectedCardinality = 3L; // Assuming the set has 3 members\n\n    when(commandObjects.scard(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.scard(key);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).scard(key);\n  }\n\n  @Test\n  public void testScardBinary() {\n    byte[] key = \"setKey\".getBytes();\n    long expectedCardinality = 3L; // Assuming the set has 3 members\n\n    when(commandObjects.scard(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.scard(key);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).scard(key);\n  }\n\n  @Test\n  public void testSdiff() {\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    Set<String> expectedDifference = new HashSet<>(Arrays.asList(\"member1\", \"member3\")); // Assuming these members are in setKey1 but not in setKey2\n\n    when(commandObjects.sdiff(keys)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedDifference);\n\n    Set<String> result = jedis.sdiff(keys);\n\n    assertThat(result, equalTo(expectedDifference));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).sdiff(keys);\n  }\n\n  @Test\n  public void testSdiffBinary() {\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    Set<byte[]> expectedDifference = new HashSet<>(Arrays.asList(\"member1\".getBytes(), \"member3\".getBytes())); // Assuming these members are in setKey1 but not in setKey2\n\n    when(commandObjects.sdiff(keys)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedDifference);\n\n    Set<byte[]> result = jedis.sdiff(keys);\n\n    assertThat(result, equalTo(expectedDifference));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).sdiff(keys);\n  }\n\n  @Test\n  public void testSdiffstore() {\n    String dstkey = \"destinationKey\";\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    long expectedStored = 2L; // Assuming two members were stored in the destination set\n\n    when(commandObjects.sdiffstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.sdiffstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sdiffstore(dstkey, keys);\n  }\n\n  @Test\n  public void testSdiffstoreBinary() {\n    byte[] dstkey = \"destinationKey\".getBytes();\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    long expectedStored = 2L; // Assuming two members were stored in the destination set\n\n    when(commandObjects.sdiffstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.sdiffstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sdiffstore(dstkey, keys);\n  }\n\n  @Test\n  public void testSinter() {\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    Set<String> expectedIntersection = new HashSet<>(Arrays.asList(\"member2\", \"member4\")); // Assuming these members are common to setKey1 and setKey2\n\n    when(commandObjects.sinter(keys)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedIntersection);\n\n    Set<String> result = jedis.sinter(keys);\n\n    assertThat(result, equalTo(expectedIntersection));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).sinter(keys);\n  }\n\n  @Test\n  public void testSinterBinary() {\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    Set<byte[]> expectedIntersection = new HashSet<>(Arrays.asList(\"member2\".getBytes(), \"member4\".getBytes())); // Assuming these members are common to setKey1 and setKey2\n\n    when(commandObjects.sinter(keys)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedIntersection);\n\n    Set<byte[]> result = jedis.sinter(keys);\n\n    assertThat(result, equalTo(expectedIntersection));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).sinter(keys);\n  }\n\n  @Test\n  public void testSintercard() {\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    long expectedCardinality = 2L; // Assuming there are two common members\n\n    when(commandObjects.sintercard(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.sintercard(keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sintercard(keys);\n  }\n\n  @Test\n  public void testSintercardBinary() {\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    long expectedCardinality = 2L; // Assuming there are two common members\n\n    when(commandObjects.sintercard(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.sintercard(keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sintercard(keys);\n  }\n\n  @Test\n  public void testSintercardWithLimit() {\n    int limit = 1;\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    long expectedCardinality = 1L; // Assuming the limit is set to 1 and there is at least one common member\n\n    when(commandObjects.sintercard(limit, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.sintercard(limit, keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sintercard(limit, keys);\n  }\n\n  @Test\n  public void testSintercardWithLimitBinary() {\n    int limit = 1;\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    long expectedCardinality = 1L; // Assuming the limit is set to 1 and there is at least one common member\n\n    when(commandObjects.sintercard(limit, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.sintercard(limit, keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sintercard(limit, keys);\n  }\n\n  @Test\n  public void testSinterstore() {\n    String dstkey = \"destinationKey\";\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    long expectedStored = 2L; // Assuming two members were stored in the destination set\n\n    when(commandObjects.sinterstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.sinterstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sinterstore(dstkey, keys);\n  }\n\n  @Test\n  public void testSinterstoreBinary() {\n    byte[] dstkey = \"destinationKey\".getBytes();\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    long expectedStored = 2L; // Assuming two members were stored in the destination set\n\n    when(commandObjects.sinterstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.sinterstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sinterstore(dstkey, keys);\n  }\n\n  @Test\n  public void testSismember() {\n    String key = \"setKey\";\n    String member = \"member1\";\n    boolean expectedIsMember = true; // Assuming the member is part of the set\n\n    when(commandObjects.sismember(key, member)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedIsMember);\n\n    boolean result = jedis.sismember(key, member);\n\n    assertThat(result, equalTo(expectedIsMember));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).sismember(key, member);\n  }\n\n  @Test\n  public void testSismemberBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    boolean expectedIsMember = true; // Assuming the member is part of the set\n\n    when(commandObjects.sismember(key, member)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedIsMember);\n\n    boolean result = jedis.sismember(key, member);\n\n    assertThat(result, equalTo(expectedIsMember));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).sismember(key, member);\n  }\n\n  @Test\n  public void testSmembers() {\n    String key = \"setKey\";\n    Set<String> expectedMembers = new HashSet<>(Arrays.asList(\"member1\", \"member2\"));\n\n    when(commandObjects.smembers(key)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedMembers);\n\n    Set<String> result = jedis.smembers(key);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).smembers(key);\n  }\n\n  @Test\n  public void testSmembersBinary() {\n    byte[] key = \"setKey\".getBytes();\n    Set<byte[]> expectedMembers = new HashSet<>(Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes()));\n\n    when(commandObjects.smembers(key)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedMembers);\n\n    Set<byte[]> result = jedis.smembers(key);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).smembers(key);\n  }\n\n  @Test\n  public void testSmismember() {\n    String key = \"setKey\";\n    String[] members = { \"member1\", \"member2\", \"member3\" };\n    List<Boolean> expectedMembership = Arrays.asList(true, false, true); // Assuming the first and last members are part of the set\n\n    when(commandObjects.smismember(key, members)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedMembership);\n\n    List<Boolean> result = jedis.smismember(key, members);\n\n    assertThat(result, equalTo(expectedMembership));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).smismember(key, members);\n  }\n\n  @Test\n  public void testSmismemberBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes() };\n    List<Boolean> expectedMembership = Arrays.asList(true, false, true); // Assuming the first and last members are part of the set\n\n    when(commandObjects.smismember(key, members)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedMembership);\n\n    List<Boolean> result = jedis.smismember(key, members);\n\n    assertThat(result, equalTo(expectedMembership));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).smismember(key, members);\n  }\n\n  @Test\n  public void testSmove() {\n    String srckey = \"sourceKey\";\n    String dstkey = \"destinationKey\";\n    String member = \"member1\";\n    long expectedMoved = 1L; // Assuming the member was successfully moved\n\n    when(commandObjects.smove(srckey, dstkey, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedMoved);\n\n    long result = jedis.smove(srckey, dstkey, member);\n\n    assertThat(result, equalTo(expectedMoved));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).smove(srckey, dstkey, member);\n  }\n\n  @Test\n  public void testSmoveBinary() {\n    byte[] srckey = \"sourceKey\".getBytes();\n    byte[] dstkey = \"destinationKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    long expectedMoved = 1L; // Assuming the member was successfully moved\n\n    when(commandObjects.smove(srckey, dstkey, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedMoved);\n\n    long result = jedis.smove(srckey, dstkey, member);\n\n    assertThat(result, equalTo(expectedMoved));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).smove(srckey, dstkey, member);\n  }\n\n  @Test\n  public void testSpop() {\n    String key = \"setKey\";\n    String expectedPopped = \"member1\"; // Assuming \"member1\" was popped\n\n    when(commandObjects.spop(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPopped);\n\n    String result = jedis.spop(key);\n\n    assertThat(result, equalTo(expectedPopped));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).spop(key);\n  }\n\n  @Test\n  public void testSpopBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[] expectedPopped = \"member1\".getBytes(); // Assuming \"member1\" was popped\n\n    when(commandObjects.spop(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPopped);\n\n    byte[] result = jedis.spop(key);\n\n    assertThat(result, equalTo(expectedPopped));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).spop(key);\n  }\n\n  @Test\n  public void testSpopCount() {\n    String key = \"setKey\";\n    long count = 2;\n    Set<String> expectedPopped = new HashSet<>(Arrays.asList(\"member1\", \"member2\")); // Assuming these members were popped\n\n    when(commandObjects.spop(key, count)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedPopped);\n\n    Set<String> result = jedis.spop(key, count);\n\n    assertThat(result, equalTo(expectedPopped));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).spop(key, count);\n  }\n\n  @Test\n  public void testSpopCountBinary() {\n    byte[] key = \"setKey\".getBytes();\n    long count = 2;\n    Set<byte[]> expectedPopped = new HashSet<>(Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes())); // Assuming these members were popped\n\n    when(commandObjects.spop(key, count)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedPopped);\n\n    Set<byte[]> result = jedis.spop(key, count);\n\n    assertThat(result, equalTo(expectedPopped));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).spop(key, count);\n  }\n\n  @Test\n  public void testSrandmember() {\n    String key = \"setKey\";\n    String expectedRandomMember = \"member1\"; // Assuming \"member1\" is randomly selected\n    when(commandObjects.srandmember(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedRandomMember);\n\n    String result = jedis.srandmember(key);\n\n    assertThat(result, equalTo(expectedRandomMember));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).srandmember(key);\n  }\n\n  @Test\n  public void testSrandmemberBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[] expectedRandomMember = \"member1\".getBytes(); // Assuming \"member1\" is randomly selected\n\n    when(commandObjects.srandmember(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedRandomMember);\n\n    byte[] result = jedis.srandmember(key);\n\n    assertThat(result, equalTo(expectedRandomMember));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).srandmember(key);\n  }\n\n  @Test\n  public void testSrandmemberCount() {\n    String key = \"setKey\";\n    int count = 2;\n    List<String> expectedRandomMembers = Arrays.asList(\"member1\", \"member2\"); // Assuming these members are randomly selected\n\n    when(commandObjects.srandmember(key, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedRandomMembers);\n\n    List<String> result = jedis.srandmember(key, count);\n\n    assertThat(result, equalTo(expectedRandomMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).srandmember(key, count);\n  }\n\n  @Test\n  public void testSrandmemberCountBinary() {\n    byte[] key = \"setKey\".getBytes();\n    int count = 2;\n    List<byte[]> expectedRandomMembers = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes()); // Assuming these members are randomly selected\n\n    when(commandObjects.srandmember(key, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedRandomMembers);\n\n    List<byte[]> result = jedis.srandmember(key, count);\n\n    assertThat(result, equalTo(expectedRandomMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).srandmember(key, count);\n  }\n\n  @Test\n  public void testSrem() {\n    String key = \"setKey\";\n    String[] members = { \"member1\", \"member2\" };\n    long expectedRemoved = 2L; // Assuming both members were removed\n\n    when(commandObjects.srem(key, members)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemoved);\n\n    long result = jedis.srem(key, members);\n\n    assertThat(result, equalTo(expectedRemoved));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).srem(key, members);\n  }\n\n  @Test\n  public void testSremBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes() };\n    long expectedRemoved = 2L; // Assuming both members were removed\n\n    when(commandObjects.srem(key, members)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemoved);\n\n    long result = jedis.srem(key, members);\n\n    assertThat(result, equalTo(expectedRemoved));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).srem(key, members);\n  }\n\n  @Test\n  public void testSscan() {\n    String key = \"setKey\";\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    List<String> scanResultData = Arrays.asList(\"member1\", \"member2\", \"member3\");\n    ScanResult<String> expectedScanResult = new ScanResult<>(cursor, scanResultData);\n\n    when(commandObjects.sscan(key, cursor, params)).thenReturn(scanResultStringCommandObject);\n    when(commandExecutor.executeCommand(scanResultStringCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<String> result = jedis.sscan(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultStringCommandObject);\n    verify(commandObjects).sscan(key, cursor, params);\n  }\n\n  @Test\n  public void testSscanBinary() {\n    byte[] key = \"setKey\".getBytes();\n    byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;\n    ScanParams params = new ScanParams().match(\"*\".getBytes()).count(10);\n    List<byte[]> scanResultData = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes());\n    ScanResult<byte[]> expectedScanResult = new ScanResult<>(cursor, scanResultData);\n\n    when(commandObjects.sscan(key, cursor, params)).thenReturn(scanResultBytesCommandObject);\n    when(commandExecutor.executeCommand(scanResultBytesCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<byte[]> result = jedis.sscan(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultBytesCommandObject);\n    verify(commandObjects).sscan(key, cursor, params);\n  }\n\n  @Test\n  public void testSunion() {\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    Set<String> expectedUnion = new HashSet<>(Arrays.asList(\"member1\", \"member2\", \"member3\", \"member4\")); // Assuming these members are in either setKey1 or setKey2\n\n    when(commandObjects.sunion(keys)).thenReturn(setStringCommandObject);\n    when(commandExecutor.executeCommand(setStringCommandObject)).thenReturn(expectedUnion);\n\n    Set<String> result = jedis.sunion(keys);\n\n    assertThat(result, equalTo(expectedUnion));\n\n    verify(commandExecutor).executeCommand(setStringCommandObject);\n    verify(commandObjects).sunion(keys);\n  }\n\n  @Test\n  public void testSunionBinary() {\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    Set<byte[]> expectedUnion = new HashSet<>(Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes(), \"member4\".getBytes())); // Assuming these members are in either setKey1 or setKey2\n\n    when(commandObjects.sunion(keys)).thenReturn(setBytesCommandObject);\n    when(commandExecutor.executeCommand(setBytesCommandObject)).thenReturn(expectedUnion);\n\n    Set<byte[]> result = jedis.sunion(keys);\n\n    assertThat(result, equalTo(expectedUnion));\n\n    verify(commandExecutor).executeCommand(setBytesCommandObject);\n    verify(commandObjects).sunion(keys);\n  }\n\n  @Test\n  public void testSunionstore() {\n    String dstkey = \"destinationKey\";\n    String[] keys = { \"setKey1\", \"setKey2\" };\n    long expectedStored = 4L; // Assuming four unique members were stored in the destination set\n\n    when(commandObjects.sunionstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.sunionstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sunionstore(dstkey, keys);\n  }\n\n  @Test\n  public void testSunionstoreBinary() {\n    byte[] dstkey = \"destinationKey\".getBytes();\n    byte[][] keys = { \"setKey1\".getBytes(), \"setKey2\".getBytes() };\n    long expectedStored = 4L; // Assuming four unique members were stored in the destination set\n\n    when(commandObjects.sunionstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStored);\n\n    long result = jedis.sunionstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStored));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).sunionstore(dstkey, keys);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisSortedSetCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.args.SortedSetOption;\nimport redis.clients.jedis.params.ScanParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\nimport redis.clients.jedis.params.ZParams;\nimport redis.clients.jedis.params.ZRangeParams;\nimport redis.clients.jedis.resps.ScanResult;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.KeyValue;\n\npublic class UnifiedJedisSortedSetCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testBzmpop() {\n    double timeout = 2.0;\n    SortedSetOption option = SortedSetOption.MAX;\n    String[] keys = { \"zset1\", \"zset2\" };\n    KeyValue<String, List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\", Collections.singletonList(new Tuple(\"member1\", 1.0)));\n\n    when(commandObjects.bzmpop(timeout, option, keys)).thenReturn(keyValueStringListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<String, List<Tuple>> result = jedis.bzmpop(timeout, option, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueStringListTupleCommandObject);\n    verify(commandObjects).bzmpop(timeout, option, keys);\n  }\n\n  @Test\n  public void testBzmpopBinary() {\n    double timeout = 2.0;\n    SortedSetOption option = SortedSetOption.MAX;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    KeyValue<byte[], List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\".getBytes(), Collections.singletonList(new Tuple(\"member1\", 1.0)));\n\n    when(commandObjects.bzmpop(timeout, option, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<byte[], List<Tuple>> result = jedis.bzmpop(timeout, option, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListTupleCommandObject);\n    verify(commandObjects).bzmpop(timeout, option, keys);\n  }\n\n  @Test\n  public void testBzmpopWithCount() {\n    double timeout = 2.0;\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n    String[] keys = { \"zset1\", \"zset2\" };\n    KeyValue<String, List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\", Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0)));\n\n    when(commandObjects.bzmpop(timeout, option, count, keys)).thenReturn(keyValueStringListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<String, List<Tuple>> result = jedis.bzmpop(timeout, option, count, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueStringListTupleCommandObject);\n    verify(commandObjects).bzmpop(timeout, option, count, keys);\n  }\n\n  @Test\n  public void testBzmpopWithCountBinary() {\n    double timeout = 2.0;\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    KeyValue<byte[], List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\".getBytes(), Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0)));\n\n    when(commandObjects.bzmpop(timeout, option, count, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<byte[], List<Tuple>> result = jedis.bzmpop(timeout, option, count, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListTupleCommandObject);\n    verify(commandObjects).bzmpop(timeout, option, count, keys);\n  }\n\n  @Test\n  public void testBzpopmax() {\n    double timeout = 2.0;\n    String[] keys = { \"zset1\", \"zset2\" };\n    Tuple expectedTuple = new Tuple(\"member1\", 1.0);\n    KeyValue<String, Tuple> expectedKeyValue = new KeyValue<>(\"zset1\", expectedTuple);\n\n    when(commandObjects.bzpopmax(timeout, keys)).thenReturn(keyValueStringTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringTupleCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, Tuple> result = jedis.bzpopmax(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringTupleCommandObject);\n    verify(commandObjects).bzpopmax(timeout, keys);\n  }\n\n  @Test\n  public void testBzpopmaxBinary() {\n    double timeout = 2.0;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    Tuple expectedTuple = new Tuple(\"member1\".getBytes(), 1.0);\n    KeyValue<byte[], Tuple> expectedKeyValue = new KeyValue<>(\"zset1\".getBytes(), expectedTuple);\n\n    when(commandObjects.bzpopmax(timeout, keys)).thenReturn(keyValueBytesTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesTupleCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], Tuple> result = jedis.bzpopmax(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesTupleCommandObject);\n    verify(commandObjects).bzpopmax(timeout, keys);\n  }\n\n  @Test\n  public void testBzpopmin() {\n    double timeout = 2.0;\n    String[] keys = { \"zset1\", \"zset2\" };\n    Tuple expectedTuple = new Tuple(\"member1\", 1.0);\n    KeyValue<String, Tuple> expectedKeyValue = new KeyValue<>(\"zset1\", expectedTuple);\n\n    when(commandObjects.bzpopmin(timeout, keys)).thenReturn(keyValueStringTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringTupleCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<String, Tuple> result = jedis.bzpopmin(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueStringTupleCommandObject);\n    verify(commandObjects).bzpopmin(timeout, keys);\n  }\n\n  @Test\n  public void testBzpopminBinary() {\n    double timeout = 2.0;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    Tuple expectedTuple = new Tuple(\"member1\".getBytes(), 1.0);\n    KeyValue<byte[], Tuple> expectedKeyValue = new KeyValue<>(\"zset1\".getBytes(), expectedTuple);\n\n    when(commandObjects.bzpopmin(timeout, keys)).thenReturn(keyValueBytesTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesTupleCommandObject)).thenReturn(expectedKeyValue);\n\n    KeyValue<byte[], Tuple> result = jedis.bzpopmin(timeout, keys);\n\n    assertThat(result, equalTo(expectedKeyValue));\n\n    verify(commandExecutor).executeCommand(keyValueBytesTupleCommandObject);\n    verify(commandObjects).bzpopmin(timeout, keys);\n  }\n\n  @Test\n  public void testZadd() {\n    String key = \"zsetKey\";\n    double score = 1.0;\n    String member = \"member1\";\n    long expectedAdded = 1L; // Assuming the member was successfully added\n\n    when(commandObjects.zadd(key, score, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, score, member);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, score, member);\n  }\n\n  @Test\n  public void testZaddBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double score = 1.0;\n    byte[] member = \"member1\".getBytes();\n    long expectedAdded = 1L; // Assuming the member was successfully added\n\n    when(commandObjects.zadd(key, score, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, score, member);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, score, member);\n  }\n\n  @Test\n  public void testZaddWithParams() {\n    String key = \"zsetKey\";\n    double score = 1.0;\n    String member = \"member1\";\n    ZAddParams params = ZAddParams.zAddParams().nx();\n    long expectedAdded = 1L; // Assuming the member was successfully added with NX flag\n\n    when(commandObjects.zadd(key, score, member, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, score, member, params);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, score, member, params);\n  }\n\n  @Test\n  public void testZaddWithParamsBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double score = 1.0;\n    byte[] member = \"member1\".getBytes();\n    ZAddParams params = ZAddParams.zAddParams().nx();\n    long expectedAdded = 1L; // Assuming the member was successfully added with NX flag\n\n    when(commandObjects.zadd(key, score, member, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, score, member, params);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, score, member, params);\n  }\n\n  @Test\n  public void testZaddMultiple() {\n    String key = \"zsetKey\";\n    Map<String, Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\", 1.0);\n    scoreMembers.put(\"member2\", 2.0);\n    long expectedAdded = 2L; // Assuming both members were successfully added\n\n    when(commandObjects.zadd(key, scoreMembers)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, scoreMembers);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, scoreMembers);\n  }\n\n  @Test\n  public void testZaddMultipleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    Map<byte[], Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\".getBytes(), 1.0);\n    scoreMembers.put(\"member2\".getBytes(), 2.0);\n    long expectedAdded = 2L; // Assuming both members were successfully added\n\n    when(commandObjects.zadd(key, scoreMembers)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, scoreMembers);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, scoreMembers);\n  }\n\n  @Test\n  public void testZaddMultipleWithParams() {\n    String key = \"zsetKey\";\n    Map<String, Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\", 1.0);\n    scoreMembers.put(\"member2\", 2.0);\n    ZAddParams params = ZAddParams.zAddParams().xx();\n    long expectedAdded = 2L; // Assuming both members were successfully added with XX flag\n\n    when(commandObjects.zadd(key, scoreMembers, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, scoreMembers, params);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, scoreMembers, params);\n  }\n\n  @Test\n  public void testZaddMultipleWithParamsBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    Map<byte[], Double> scoreMembers = new HashMap<>();\n    scoreMembers.put(\"member1\".getBytes(), 1.0);\n    scoreMembers.put(\"member2\".getBytes(), 2.0);\n    ZAddParams params = ZAddParams.zAddParams().xx();\n    long expectedAdded = 2L; // Assuming both members were successfully added with XX flag\n\n    when(commandObjects.zadd(key, scoreMembers, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAdded);\n\n    long result = jedis.zadd(key, scoreMembers, params);\n\n    assertThat(result, equalTo(expectedAdded));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zadd(key, scoreMembers, params);\n  }\n\n  @Test\n  public void testZaddIncr() {\n    String key = \"zsetKey\";\n    double score = 1.0;\n    String member = \"member1\";\n    ZAddParams params = ZAddParams.zAddParams().ch();\n    Double expectedNewScore = 2.0; // Assuming the member's score was incremented to 2.0\n\n    when(commandObjects.zaddIncr(key, score, member, params)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedNewScore);\n\n    Double result = jedis.zaddIncr(key, score, member, params);\n\n    assertThat(result, equalTo(expectedNewScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zaddIncr(key, score, member, params);\n  }\n\n  @Test\n  public void testZaddIncrBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double score = 1.0;\n    byte[] member = \"member1\".getBytes();\n    ZAddParams params = ZAddParams.zAddParams().ch();\n    Double expectedNewScore = 2.0; // Assuming the member's score was incremented to 2.0\n\n    when(commandObjects.zaddIncr(key, score, member, params)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedNewScore);\n\n    Double result = jedis.zaddIncr(key, score, member, params);\n\n    assertThat(result, equalTo(expectedNewScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zaddIncr(key, score, member, params);\n  }\n\n  @Test\n  public void testZcard() {\n    String key = \"zsetKey\";\n    long expectedCardinality = 5L; // Assuming the sorted set has 5 members\n\n    when(commandObjects.zcard(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.zcard(key);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zcard(key);\n  }\n\n  @Test\n  public void testZcardBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long expectedCardinality = 5L; // Assuming the sorted set has 5 members\n\n    when(commandObjects.zcard(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.zcard(key);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zcard(key);\n  }\n\n  @Test\n  public void testZcount() {\n    String key = \"zsetKey\";\n    String min = \"1\";\n    String max = \"2\";\n    long expectedCount = 3L; // Assuming there are 3 members within the score range\n\n    when(commandObjects.zcount(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.zcount(key, min, max);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zcount(key, min, max);\n  }\n\n  @Test\n  public void testZcountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"2\".getBytes();\n    long expectedCount = 3L; // Assuming there are 3 members within the score range\n\n    when(commandObjects.zcount(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.zcount(key, min, max);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zcount(key, min, max);\n  }\n\n  @Test\n  public void testZcountDouble() {\n    String key = \"zsetKey\";\n    double min = 1.0;\n    double max = 2.0;\n    long expectedCount = 3L; // Assuming there are 3 members within the score range\n\n    when(commandObjects.zcount(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.zcount(key, min, max);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zcount(key, min, max);\n  }\n\n  @Test\n  public void testZcountDoubleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double min = 1.0;\n    double max = 2.0;\n    long expectedCount = 3L; // Assuming there are 3 members within the score range\n\n    when(commandObjects.zcount(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.zcount(key, min, max);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zcount(key, min, max);\n  }\n\n  @Test\n  public void testZdiff() {\n    String[] keys = { \"zset1\", \"zset2\", \"zset3\" };\n    List<String> expectedDifference = Arrays.asList(\"member1\", \"member3\");\n\n    when(commandObjects.zdiff(keys)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedDifference);\n\n    List<String> result = jedis.zdiff(keys);\n\n    assertThat(result, equalTo(expectedDifference));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zdiff(keys);\n  }\n\n  @Test\n  public void testZdiffBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes(), \"zset3\".getBytes() };\n    List<byte[]> expectedDifference = Arrays.asList(\"member1\".getBytes(), \"member3\".getBytes());\n\n    when(commandObjects.zdiff(keys)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedDifference);\n\n    List<byte[]> result = jedis.zdiff(keys);\n\n    assertThat(result, equalTo(expectedDifference));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zdiff(keys);\n  }\n\n  @Test\n  public void testZdiffWithScores() {\n    String[] keys = { \"zset1\", \"zset2\", \"zset3\" };\n    List<Tuple> expectedDifferenceWithScores = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member3\", 3.0)\n    );\n\n    when(commandObjects.zdiffWithScores(keys)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedDifferenceWithScores);\n\n    List<Tuple> result = jedis.zdiffWithScores(keys);\n\n    assertThat(result, equalTo(expectedDifferenceWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zdiffWithScores(keys);\n  }\n\n  @Test\n  public void testZdiffWithScoresBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes(), \"zset3\".getBytes() };\n    List<Tuple> expectedDifferenceWithScores = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member3\".getBytes(), 3.0)\n    );\n\n    when(commandObjects.zdiffWithScores(keys)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedDifferenceWithScores);\n\n    List<Tuple> result = jedis.zdiffWithScores(keys);\n\n    assertThat(result, equalTo(expectedDifferenceWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zdiffWithScores(keys);\n  }\n\n  @Test\n  public void testZdiffStore() {\n    String dstkey = \"zsetDiff\";\n    String[] keys = { \"zset1\", \"zset2\", \"zset3\" };\n    long expectedStoredCount = 2L; // Assuming 2 elements were stored\n\n    when(commandObjects.zdiffStore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zdiffStore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zdiffStore(dstkey, keys);\n  }\n\n  @Test\n  public void testZdiffStoreBinary() {\n    byte[] dstkey = \"zsetDiff\".getBytes();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes(), \"zset3\".getBytes() };\n    long expectedStoredCount = 2L; // Assuming 2 elements were stored\n\n    when(commandObjects.zdiffStore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zdiffStore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zdiffStore(dstkey, keys);\n  }\n\n  @Test\n  public void testZdiffstore() {\n    String dstkey = \"zsetDiff\";\n    String[] keys = { \"zset1\", \"zset2\", \"zset3\" };\n    long expectedStoredCount = 2L; // Assuming 2 elements were stored\n\n    when(commandObjects.zdiffstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zdiffstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zdiffstore(dstkey, keys);\n  }\n\n  @Test\n  public void testZdiffstoreBinary() {\n    byte[] dstkey = \"zsetDiff\".getBytes();\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes(), \"zset3\".getBytes() };\n    long expectedStoredCount = 2L; // Assuming 2 elements were stored\n\n    when(commandObjects.zdiffstore(dstkey, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zdiffstore(dstkey, keys);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zdiffstore(dstkey, keys);\n  }\n\n  @Test\n  public void testZincrby() {\n    String key = \"zsetKey\";\n    double increment = 2.0;\n    String member = \"member1\";\n    double expectedScore = 3.0; // Assuming the member's score was incremented to 3.0\n\n    when(commandObjects.zincrby(key, increment, member)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedScore);\n\n    double result = jedis.zincrby(key, increment, member);\n\n    assertThat(result, equalTo(expectedScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zincrby(key, increment, member);\n  }\n\n  @Test\n  public void testZincrbyBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double increment = 2.0;\n    byte[] member = \"member1\".getBytes();\n    double expectedScore = 3.0; // Assuming the member's score was incremented to 3.0\n\n    when(commandObjects.zincrby(key, increment, member)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedScore);\n\n    double result = jedis.zincrby(key, increment, member);\n\n    assertThat(result, equalTo(expectedScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zincrby(key, increment, member);\n  }\n\n  @Test\n  public void testZincrbyWithParams() {\n    String key = \"zsetKey\";\n    double increment = 1.5;\n    String member = \"member1\";\n    ZIncrByParams params = ZIncrByParams.zIncrByParams().xx();\n    Double expectedNewScore = 4.5; // Assuming the member's score was incremented to 4.5 with XX flag\n\n    when(commandObjects.zincrby(key, increment, member, params)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedNewScore);\n\n    Double result = jedis.zincrby(key, increment, member, params);\n\n    assertThat(result, equalTo(expectedNewScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zincrby(key, increment, member, params);\n  }\n\n  @Test\n  public void testZincrbyWithParamsBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double increment = 1.5;\n    byte[] member = \"member1\".getBytes();\n    ZIncrByParams params = ZIncrByParams.zIncrByParams().xx();\n    Double expectedNewScore = 4.5; // Assuming the member's score was incremented to 4.5 with XX flag\n\n    when(commandObjects.zincrby(key, increment, member, params)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedNewScore);\n\n    Double result = jedis.zincrby(key, increment, member, params);\n\n    assertThat(result, equalTo(expectedNewScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zincrby(key, increment, member, params);\n  }\n\n  @Test\n  public void testZinter() {\n    ZParams params = new ZParams().weights(2, 3).aggregate(ZParams.Aggregate.SUM);\n    String[] keys = { \"zset1\", \"zset2\" };\n    List<String> expectedIntersection = Arrays.asList(\"member1\", \"member2\");\n\n    when(commandObjects.zinter(params, keys)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedIntersection);\n\n    List<String> result = jedis.zinter(params, keys);\n\n    assertThat(result, equalTo(expectedIntersection));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zinter(params, keys);\n  }\n\n  @Test\n  public void testZinterBinary() {\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    List<byte[]> expectedIntersection = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes());\n\n    when(commandObjects.zinter(params, keys)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedIntersection);\n\n    List<byte[]> result = jedis.zinter(params, keys);\n\n    assertThat(result, equalTo(expectedIntersection));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zinter(params, keys);\n  }\n\n  @Test\n  public void testZinterWithScores() {\n    ZParams params = new ZParams().weights(2, 3).aggregate(ZParams.Aggregate.SUM);\n    String[] keys = { \"zset1\", \"zset2\" };\n    List<Tuple> expectedIntersectionWithScores = Arrays.asList(\n        new Tuple(\"member1\", 5.0),\n        new Tuple(\"member2\", 9.0)\n    );\n\n    when(commandObjects.zinterWithScores(params, keys)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedIntersectionWithScores);\n\n    List<Tuple> result = jedis.zinterWithScores(params, keys);\n\n    assertThat(result, equalTo(expectedIntersectionWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zinterWithScores(params, keys);\n  }\n\n  @Test\n  public void testZinterWithScoresBinary() {\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    List<Tuple> expectedIntersectionWithScores = Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0));\n\n    when(commandObjects.zinterWithScores(params, keys)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedIntersectionWithScores);\n\n    List<Tuple> result = jedis.zinterWithScores(params, keys);\n\n    assertThat(result, equalTo(expectedIntersectionWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zinterWithScores(params, keys);\n  }\n\n  @Test\n  public void testZintercard() {\n    String[] keys = { \"zset1\", \"zset2\" };\n    long expectedCardinality = 2L;\n\n    when(commandObjects.zintercard(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.zintercard(keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zintercard(keys);\n  }\n\n  @Test\n  public void testZintercardBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    long expectedCardinality = 2L; // Assuming the cardinality of the intersection is 2\n\n    when(commandObjects.zintercard(keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.zintercard(keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zintercard(keys);\n  }\n\n  @Test\n  public void testZintercardWithLimit() {\n    String[] keys = { \"zset1\", \"zset2\" };\n    long limit = 1000L;\n    long expectedCardinality = 2L;\n\n    when(commandObjects.zintercard(limit, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCardinality);\n\n    long result = jedis.zintercard(limit, keys);\n\n    assertThat(result, equalTo(expectedCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zintercard(limit, keys);\n  }\n\n  @Test\n  public void testZintercardWithLimitBinary() {\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    long limit = 1000L;\n    long expectedIntersectionCardinality = 5L;\n\n    when(commandObjects.zintercard(limit, keys)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedIntersectionCardinality);\n\n    long result = jedis.zintercard(limit, keys);\n\n    assertThat(result, equalTo(expectedIntersectionCardinality));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zintercard(limit, keys);\n  }\n\n  @Test\n  public void testZinterstore() {\n    String dstkey = \"zsetInter\";\n    String[] sets = { \"zset1\", \"zset2\" };\n    long expectedStoredCount = 3L; // Assuming 3 elements were stored\n\n    when(commandObjects.zinterstore(dstkey, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zinterstore(dstkey, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zinterstore(dstkey, sets);\n  }\n\n  @Test\n  public void testZinterstoreBinary() {\n    byte[] dstkey = \"zsetInter\".getBytes();\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    long expectedStoredCount = 3L; // Assuming 3 elements were stored\n\n    when(commandObjects.zinterstore(dstkey, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zinterstore(dstkey, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zinterstore(dstkey, sets);\n  }\n\n  @Test\n  public void testZinterstoreWithParams() {\n    String dstkey = \"zsetInter\";\n    ZParams params = new ZParams().weights(2, 3).aggregate(ZParams.Aggregate.SUM);\n    String[] sets = { \"zset1\", \"zset2\" };\n    long expectedStoredCount = 3L; // Assuming 3 elements were stored with the specified weights and aggregation\n\n    when(commandObjects.zinterstore(dstkey, params, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zinterstore(dstkey, params, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zinterstore(dstkey, params, sets);\n  }\n\n  @Test\n  public void testZinterstoreWithParamsBinary() {\n    byte[] dstkey = \"zsetInter\".getBytes();\n    ZParams params = new ZParams().weights(2, 3).aggregate(ZParams.Aggregate.SUM);\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    long expectedStoredCount = 3L; // Assuming 3 elements were stored with the specified weights and aggregation\n\n    when(commandObjects.zinterstore(dstkey, params, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zinterstore(dstkey, params, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zinterstore(dstkey, params, sets);\n  }\n\n  @Test\n  public void testZlexcount() {\n    String key = \"zsetKey\";\n    String min = \"[a\";\n    String max = \"(b\";\n    long expectedCount = 5L; // Assuming there are 5 elements in the lex range\n\n    when(commandObjects.zlexcount(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.zlexcount(key, min, max);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zlexcount(key, min, max);\n  }\n\n  @Test\n  public void testZlexcountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[b\".getBytes();\n    long expectedCount = 5L; // Assuming there are 5 elements in the lex range\n\n    when(commandObjects.zlexcount(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedCount);\n\n    long result = jedis.zlexcount(key, min, max);\n\n    assertThat(result, equalTo(expectedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zlexcount(key, min, max);\n  }\n\n  @Test\n  public void testZmpop() {\n    SortedSetOption option = SortedSetOption.MAX;\n    String[] keys = { \"zset1\", \"zset2\" };\n    KeyValue<String, List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\", Collections.singletonList(new Tuple(\"member1\", 1.0)));\n\n    when(commandObjects.zmpop(option, keys)).thenReturn(keyValueStringListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<String, List<Tuple>> result = jedis.zmpop(option, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueStringListTupleCommandObject);\n    verify(commandObjects).zmpop(option, keys);\n  }\n\n  @Test\n  public void testZmpopBinary() {\n    SortedSetOption option = SortedSetOption.MAX;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    KeyValue<byte[], List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\".getBytes(), Collections.singletonList(new Tuple(\"member1\", 1.0)));\n\n    when(commandObjects.zmpop(option, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<byte[], List<Tuple>> result = jedis.zmpop(option, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListTupleCommandObject);\n    verify(commandObjects).zmpop(option, keys);\n  }\n\n  @Test\n  public void testZmpopWithCount() {\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n    String[] keys = { \"zset1\", \"zset2\" };\n    KeyValue<String, List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\", Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0)));\n\n    when(commandObjects.zmpop(option, count, keys)).thenReturn(keyValueStringListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueStringListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<String, List<Tuple>> result = jedis.zmpop(option, count, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueStringListTupleCommandObject);\n    verify(commandObjects).zmpop(option, count, keys);\n  }\n\n  @Test\n  public void testZmpopWithCountBinary() {\n    SortedSetOption option = SortedSetOption.MAX;\n    int count = 2;\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    KeyValue<byte[], List<Tuple>> expectedPopResult = new KeyValue<>(\"zset1\".getBytes(), Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0)));\n\n    when(commandObjects.zmpop(option, count, keys)).thenReturn(keyValueBytesListTupleCommandObject);\n    when(commandExecutor.executeCommand(keyValueBytesListTupleCommandObject)).thenReturn(expectedPopResult);\n\n    KeyValue<byte[], List<Tuple>> result = jedis.zmpop(option, count, keys);\n\n    assertThat(result, equalTo(expectedPopResult));\n\n    verify(commandExecutor).executeCommand(keyValueBytesListTupleCommandObject);\n    verify(commandObjects).zmpop(option, count, keys);\n  }\n\n  @Test\n  public void testZmscore() {\n    String key = \"zsetKey\";\n    String[] members = { \"member1\", \"member2\" };\n    List<Double> expectedScores = Arrays.asList(1.0, 2.0); // Assuming the members have scores of 1.0 and 2.0 respectively\n\n    when(commandObjects.zmscore(key, members)).thenReturn(listDoubleCommandObject);\n    when(commandExecutor.executeCommand(listDoubleCommandObject)).thenReturn(expectedScores);\n\n    List<Double> result = jedis.zmscore(key, members);\n\n    assertThat(result, equalTo(expectedScores));\n\n    verify(commandExecutor).executeCommand(listDoubleCommandObject);\n    verify(commandObjects).zmscore(key, members);\n  }\n\n  @Test\n  public void testZmscoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes() };\n    List<Double> expectedScores = Arrays.asList(1.0, 2.0); // Assuming the members have scores of 1.0 and 2.0 respectively\n\n    when(commandObjects.zmscore(key, members)).thenReturn(listDoubleCommandObject);\n    when(commandExecutor.executeCommand(listDoubleCommandObject)).thenReturn(expectedScores);\n\n    List<Double> result = jedis.zmscore(key, members);\n\n    assertThat(result, equalTo(expectedScores));\n\n    verify(commandExecutor).executeCommand(listDoubleCommandObject);\n    verify(commandObjects).zmscore(key, members);\n  }\n\n  @Test\n  public void testZpopmax() {\n    String key = \"zsetKey\";\n    Tuple expectedTuple = new Tuple(\"member1\", 2.0); // Assuming this member has the highest score\n\n    when(commandObjects.zpopmax(key)).thenReturn(tupleCommandObject);\n    when(commandExecutor.executeCommand(tupleCommandObject)).thenReturn(expectedTuple);\n\n    Tuple result = jedis.zpopmax(key);\n\n    assertThat(result, equalTo(expectedTuple));\n\n    verify(commandExecutor).executeCommand(tupleCommandObject);\n    verify(commandObjects).zpopmax(key);\n  }\n\n  @Test\n  public void testZpopmaxBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    Tuple expectedTuple = new Tuple(\"member1\".getBytes(), 2.0); // Assuming this member has the highest score\n\n    when(commandObjects.zpopmax(key)).thenReturn(tupleCommandObject);\n    when(commandExecutor.executeCommand(tupleCommandObject)).thenReturn(expectedTuple);\n\n    Tuple result = jedis.zpopmax(key);\n\n    assertThat(result, equalTo(expectedTuple));\n\n    verify(commandExecutor).executeCommand(tupleCommandObject);\n    verify(commandObjects).zpopmax(key);\n  }\n\n  @Test\n  public void testZpopmaxWithCount() {\n    String key = \"zsetKey\";\n    int count = 2;\n    List<Tuple> expectedTuples = Arrays.asList(\n        new Tuple(\"member1\", 2.0),\n        new Tuple(\"member2\", 1.5)\n    ); // Assuming these members have the highest scores\n\n    when(commandObjects.zpopmax(key, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedTuples);\n\n    List<Tuple> result = jedis.zpopmax(key, count);\n\n    assertThat(result, equalTo(expectedTuples));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zpopmax(key, count);\n  }\n\n  @Test\n  public void testZpopmaxWithCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    int count = 2;\n    List<Tuple> expectedTuples = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 2.0),\n        new Tuple(\"member2\".getBytes(), 1.5)\n    ); // Assuming these members have the highest scores\n\n    when(commandObjects.zpopmax(key, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedTuples);\n\n    List<Tuple> result = jedis.zpopmax(key, count);\n\n    assertThat(result, equalTo(expectedTuples));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zpopmax(key, count);\n  }\n\n  @Test\n  public void testZpopmin() {\n    String key = \"zsetKey\";\n    Tuple expectedTuple = new Tuple(\"member1\", 1.0); // Assuming this member has the lowest score\n\n    when(commandObjects.zpopmin(key)).thenReturn(tupleCommandObject);\n    when(commandExecutor.executeCommand(tupleCommandObject)).thenReturn(expectedTuple);\n\n    Tuple result = jedis.zpopmin(key);\n\n    assertThat(result, equalTo(expectedTuple));\n\n    verify(commandExecutor).executeCommand(tupleCommandObject);\n    verify(commandObjects).zpopmin(key);\n  }\n\n  @Test\n  public void testZpopminBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    Tuple expectedTuple = new Tuple(\"member1\".getBytes(), 1.0); // Assuming this member has the lowest score\n\n    when(commandObjects.zpopmin(key)).thenReturn(tupleCommandObject);\n    when(commandExecutor.executeCommand(tupleCommandObject)).thenReturn(expectedTuple);\n\n    Tuple result = jedis.zpopmin(key);\n\n    assertThat(result, equalTo(expectedTuple));\n\n    verify(commandExecutor).executeCommand(tupleCommandObject);\n    verify(commandObjects).zpopmin(key);\n  }\n\n  @Test\n  public void testZpopminWithCount() {\n    String key = \"zsetKey\";\n    int count = 2;\n    List<Tuple> expectedTuples = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 1.5)\n    ); // Assuming these members have the lowest scores\n\n    when(commandObjects.zpopmin(key, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedTuples);\n\n    List<Tuple> result = jedis.zpopmin(key, count);\n\n    assertThat(result, equalTo(expectedTuples));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zpopmin(key, count);\n  }\n\n  @Test\n  public void testZpopminWithCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    int count = 2;\n    List<Tuple> expectedTuples = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 1.5)\n    ); // Assuming these members have the lowest scores\n\n    when(commandObjects.zpopmin(key, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedTuples);\n\n    List<Tuple> result = jedis.zpopmin(key, count);\n\n    assertThat(result, equalTo(expectedTuples));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zpopmin(key, count);\n  }\n\n  @Test\n  public void testZrandmember() {\n    String key = \"zsetKey\";\n    String expectedMember = \"member1\"; // Assuming this member is randomly selected\n\n    when(commandObjects.zrandmember(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedMember);\n\n    String result = jedis.zrandmember(key);\n\n    assertThat(result, equalTo(expectedMember));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).zrandmember(key);\n  }\n\n  @Test\n  public void testZrandmemberBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] expectedMember = \"member1\".getBytes(); // Assuming this member is randomly selected\n\n    when(commandObjects.zrandmember(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedMember);\n\n    byte[] result = jedis.zrandmember(key);\n\n    assertArrayEquals(expectedMember, result);\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).zrandmember(key);\n  }\n\n  @Test\n  public void testZrandmemberWithCount() {\n    String key = \"zsetKey\";\n    long count = 2;\n    List<String> expectedMembers = Arrays.asList(\"member1\", \"member2\"); // Assuming these members are randomly selected\n\n    when(commandObjects.zrandmember(key, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrandmember(key, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrandmember(key, count);\n  }\n\n  @Test\n  public void testZrandmemberBytesWithCount() {\n    byte[] key = \"zsetKey\".getBytes();\n    long count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes()); // Assuming these members are randomly selected\n\n    when(commandObjects.zrandmember(key, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrandmember(key, count);\n\n    for (int i = 0; i < expectedMembers.size(); i++) {\n      assertArrayEquals(expectedMembers.get(i), result.get(i));\n    }\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrandmember(key, count);\n  }\n\n  @Test\n  public void testZrandmemberWithScores() {\n    String key = \"zsetKey\";\n    long count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 2.0)\n    ); // Assuming these members with scores are randomly selected\n\n    when(commandObjects.zrandmemberWithScores(key, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrandmemberWithScores(key, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrandmemberWithScores(key, count);\n  }\n\n  @Test\n  public void testZrandmemberWithScoresBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 2.0)\n    ); // Assuming these members with scores are randomly selected\n\n    when(commandObjects.zrandmemberWithScores(key, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrandmemberWithScores(key, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrandmemberWithScores(key, count);\n  }\n\n  @Test\n  public void testZrange() {\n    String key = \"zsetKey\";\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set\n    List<String> expectedMembers = Arrays.asList(\"member1\", \"member2\", \"member3\");\n\n    when(commandObjects.zrange(key, start, stop)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrange(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrange(key, start, stop);\n  }\n\n  @Test\n  public void testZrangeBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set\n    List<byte[]> expectedMembers = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes(), \"member3\".getBytes());\n\n    when(commandObjects.zrange(key, start, stop)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrange(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrange(key, start, stop);\n  }\n\n  @Test\n  public void testZrangeWithScores() {\n    String key = \"zsetKey\";\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set with their scores\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member3\", 3.0)\n    );\n\n    when(commandObjects.zrangeWithScores(key, start, stop)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeWithScores(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeWithScores(key, start, stop);\n  }\n\n  @Test\n  public void testZrangeWithScoresBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set with their scores\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member3\".getBytes(), 3.0)\n    );\n\n    when(commandObjects.zrangeWithScores(key, start, stop)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeWithScores(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeWithScores(key, start, stop);\n  }\n\n  @Test\n  public void testZrangeWithZRangeParams() {\n    String key = \"zsetKey\";\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(1, 3);\n    List<String> expectedMembers = Arrays.asList(\"member1\", \"member2\");\n\n    when(commandObjects.zrange(key, zRangeParams)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrange(key, zRangeParams);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrange(key, zRangeParams);\n  }\n\n  @Test\n  public void testZrangeWithZRangeParamsBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(1, 3);\n    List<byte[]> expectedMembers = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes());\n\n    when(commandObjects.zrange(key, zRangeParams)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrange(key, zRangeParams);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrange(key, zRangeParams);\n  }\n\n  @Test\n  public void testZrangeWithScoresWithZRangeParams() {\n    String key = \"zsetKey\";\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(1, 3);\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 2.0)\n    );\n\n    when(commandObjects.zrangeWithScores(key, zRangeParams)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeWithScores(key, zRangeParams);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeWithScores(key, zRangeParams);\n  }\n\n  @Test\n  public void testZrangeWithScoresWithZRangeParamsBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(1, 3);\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 2.0)\n    );\n\n    when(commandObjects.zrangeWithScores(key, zRangeParams)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeWithScores(key, zRangeParams);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeWithScores(key, zRangeParams);\n  }\n\n  @Test\n  public void testZrangeByLex() {\n    String key = \"zsetKey\";\n    String min = \"[a\";\n    String max = \"(b\";\n    List<String> expectedMembers = Arrays.asList(\"alpha\", \"beta\");\n\n    when(commandObjects.zrangeByLex(key, min, max)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrangeByLex(key, min, max);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrangeByLex(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByLexBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[b\".getBytes();\n    List<byte[]> expectedMembers = Arrays.asList(\"alpha\".getBytes(), \"beta\".getBytes());\n\n    when(commandObjects.zrangeByLex(key, min, max)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrangeByLex(key, min, max);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrangeByLex(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByLexWithOffsetCount() {\n    String key = \"zsetKey\";\n    String min = \"[a\";\n    String max = \"(b\";\n    int offset = 1;\n    int count = 2;\n    List<String> expectedMembers = Arrays.asList(\"beta\", \"gamma\");\n\n    when(commandObjects.zrangeByLex(key, min, max, offset, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrangeByLex(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrangeByLex(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByLexWithOffsetCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[b\".getBytes();\n    int offset = 1;\n    int count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"beta\".getBytes(), \"gamma\".getBytes());\n\n    when(commandObjects.zrangeByLex(key, min, max, offset, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrangeByLex(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrangeByLex(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScore() {\n    String key = \"zsetKey\";\n    String min = \"1\";\n    String max = \"3\";\n    List<String> expectedMembers = Arrays.asList(\"member1\", \"member2\");\n\n    when(commandObjects.zrangeByScore(key, min, max)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"3\".getBytes();\n    List<byte[]> expectedMembers = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes());\n\n    when(commandObjects.zrangeByScore(key, min, max)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreDouble() {\n    String key = \"zsetKey\";\n    double min = 1.0;\n    double max = 3.0;\n    List<String> expectedMembers = Arrays.asList(\"member1\", \"member2\");\n\n    when(commandObjects.zrangeByScore(key, min, max)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreDoubleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double min = 1.0;\n    double max = 3.0;\n    List<byte[]> expectedMembers = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes());\n\n    when(commandObjects.zrangeByScore(key, min, max)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreWithOffsetCount() {\n    String key = \"zsetKey\";\n    String min = \"1\";\n    String max = \"3\";\n    int offset = 1;\n    int count = 2;\n    List<String> expectedMembers = Arrays.asList(\"member2\", \"member3\");\n\n    when(commandObjects.zrangeByScore(key, min, max, offset, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrangeByScore(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreWithOffsetCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"3\".getBytes();\n    int offset = 1;\n    int count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"member2\".getBytes(), \"member3\".getBytes());\n\n    when(commandObjects.zrangeByScore(key, min, max, offset, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrangeByScore(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreDoubleWithOffsetCount() {\n    String key = \"zsetKey\";\n    double min = 1.0;\n    double max = 3.0;\n    int offset = 1;\n    int count = 2;\n    List<String> expectedMembers = Arrays.asList(\"member2\", \"member3\");\n\n    when(commandObjects.zrangeByScore(key, min, max, offset, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrangeByScore(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreDoubleWithOffsetCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double min = 1.0;\n    double max = 3.0;\n    int offset = 1;\n    int count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"member2\".getBytes(), \"member3\".getBytes());\n\n    when(commandObjects.zrangeByScore(key, min, max, offset, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrangeByScore(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrangeByScore(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScores() {\n    String key = \"zsetKey\";\n    String min = \"1\";\n    String max = \"3\";\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 2.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"3\".getBytes();\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 2.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDouble() {\n    String key = \"zsetKey\";\n    double min = 1.0;\n    double max = 3.0;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 2.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDoubleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double min = 1.0;\n    double max = 3.0;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 2.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresWithOffsetCount() {\n    String key = \"zsetKey\";\n    String min = \"1\";\n    String max = \"3\";\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member3\", 3.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresWithOffsetCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"3\".getBytes();\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member3\".getBytes(), 3.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDoubleWithOffsetCount() {\n    String key = \"zsetKey\";\n    double min = 1.0;\n    double max = 3.0;\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member3\", 3.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangeByScoreWithScoresDoubleWithOffsetCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double min = 1.0;\n    double max = 3.0;\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member3\".getBytes(), 3.0)\n    );\n\n    when(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrangeByScoreWithScores(key, min, max, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrangeByScoreWithScores(key, min, max, offset, count);\n  }\n\n  @Test\n  public void testZrangestore() {\n    String dest = \"destinationKey\";\n    String src = \"sourceKey\";\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(1, 3);\n    long expectedStoredCount = 2L; // Assuming 2 members were within the range and stored\n\n    when(commandObjects.zrangestore(dest, src, zRangeParams)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zrangestore(dest, src, zRangeParams);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrangestore(dest, src, zRangeParams);\n  }\n\n  @Test\n  public void testZrangestoreBinary() {\n    byte[] dest = \"destinationKey\".getBytes();\n    byte[] src = \"sourceKey\".getBytes();\n    ZRangeParams zRangeParams = ZRangeParams.zrangeParams(1, 3);\n    long expectedStoredCount = 2L; // Assuming 2 members were within the range and stored\n\n    when(commandObjects.zrangestore(dest, src, zRangeParams)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zrangestore(dest, src, zRangeParams);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrangestore(dest, src, zRangeParams);\n  }\n\n  @Test\n  public void testZrank() {\n    String key = \"zsetKey\";\n    String member = \"member1\";\n    Long expectedRank = 0L; // Assuming the member is the first in the sorted set\n\n    when(commandObjects.zrank(key, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRank);\n\n    Long result = jedis.zrank(key, member);\n\n    assertThat(result, equalTo(expectedRank));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrank(key, member);\n  }\n\n  @Test\n  public void testZrankBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    Long expectedRank = 0L; // Assuming the member is the first in the sorted set\n\n    when(commandObjects.zrank(key, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRank);\n\n    Long result = jedis.zrank(key, member);\n\n    assertThat(result, equalTo(expectedRank));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrank(key, member);\n  }\n\n  @Test\n  public void testZrankWithScore() {\n    String key = \"zsetKey\";\n    String member = \"member1\";\n    KeyValue<Long, Double> expectedRankWithScore = new KeyValue<>(0L, 1.0); // Assuming the member is the first with a score of 1.0\n\n    when(commandObjects.zrankWithScore(key, member)).thenReturn(keyValueLongDoubleCommandObject);\n    when(commandExecutor.executeCommand(keyValueLongDoubleCommandObject)).thenReturn(expectedRankWithScore);\n\n    KeyValue<Long, Double> result = jedis.zrankWithScore(key, member);\n\n    assertThat(result, equalTo(expectedRankWithScore));\n\n    verify(commandExecutor).executeCommand(keyValueLongDoubleCommandObject);\n    verify(commandObjects).zrankWithScore(key, member);\n  }\n\n  @Test\n  public void testZrankWithScoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    KeyValue<Long, Double> expectedRankWithScore = new KeyValue<>(0L, 1.0); // Assuming the member is the first with a score of 1.0\n\n    when(commandObjects.zrankWithScore(key, member)).thenReturn(keyValueLongDoubleCommandObject);\n    when(commandExecutor.executeCommand(keyValueLongDoubleCommandObject)).thenReturn(expectedRankWithScore);\n\n    KeyValue<Long, Double> result = jedis.zrankWithScore(key, member);\n\n    assertThat(result, equalTo(expectedRankWithScore));\n\n    verify(commandExecutor).executeCommand(keyValueLongDoubleCommandObject);\n    verify(commandObjects).zrankWithScore(key, member);\n  }\n\n  @Test\n  public void testZrem() {\n    String key = \"zsetKey\";\n    String[] members = { \"member1\", \"member2\" };\n    long expectedRemoved = 2L; // Assuming both members were successfully removed\n\n    when(commandObjects.zrem(key, members)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemoved);\n\n    long result = jedis.zrem(key, members);\n\n    assertThat(result, equalTo(expectedRemoved));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrem(key, members);\n  }\n\n  @Test\n  public void testZremBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[][] members = { \"member1\".getBytes(), \"member2\".getBytes() };\n    long expectedRemoved = 2L; // Assuming both members were successfully removed\n\n    when(commandObjects.zrem(key, members)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemoved);\n\n    long result = jedis.zrem(key, members);\n\n    assertThat(result, equalTo(expectedRemoved));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrem(key, members);\n  }\n\n  @Test\n  public void testZremrangeByLex() {\n    String key = \"zsetKey\";\n    String min = \"[a\";\n    String max = \"[b\";\n    long expectedRemovals = 2L; // Assuming 2 elements were removed\n\n    when(commandObjects.zremrangeByLex(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByLex(key, min, max);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByLex(key, min, max);\n  }\n\n  @Test\n  public void testZremrangeByLexBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    byte[] max = \"[b\".getBytes();\n    long expectedRemovals = 2L; // Assuming 2 elements were removed\n\n    when(commandObjects.zremrangeByLex(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByLex(key, min, max);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByLex(key, min, max);\n  }\n\n  @Test\n  public void testZremrangeByRank() {\n    String key = \"zsetKey\";\n    long start = 0;\n    long stop = 2;\n    long expectedRemovals = 3L; // Assuming 3 elements were removed\n\n    when(commandObjects.zremrangeByRank(key, start, stop)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByRank(key, start, stop);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByRank(key, start, stop);\n  }\n\n  @Test\n  public void testZremrangeByRankBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long start = 0;\n    long stop = 2;\n    long expectedRemovals = 3L; // Assuming 3 elements were removed\n\n    when(commandObjects.zremrangeByRank(key, start, stop)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByRank(key, start, stop);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByRank(key, start, stop);\n  }\n\n  @Test\n  public void testZremrangeByScore() {\n    String key = \"zsetKey\";\n    String min = \"1\";\n    String max = \"3\";\n    long expectedRemovals = 2L; // Assuming 2 elements were removed\n\n    when(commandObjects.zremrangeByScore(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZremrangeByScoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] min = \"1\".getBytes();\n    byte[] max = \"3\".getBytes();\n    long expectedRemovals = 2L; // Assuming 2 elements were removed\n\n    when(commandObjects.zremrangeByScore(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZremrangeByScoreDouble() {\n    String key = \"zsetKey\";\n    double min = 1.0;\n    double max = 3.0;\n    long expectedRemovals = 2L; // Assuming 2 elements were removed\n\n    when(commandObjects.zremrangeByScore(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZremrangeByScoreDoubleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double min = 1.0;\n    double max = 3.0;\n    long expectedRemovals = 2L; // Assuming 2 elements were removed\n\n    when(commandObjects.zremrangeByScore(key, min, max)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRemovals);\n\n    long result = jedis.zremrangeByScore(key, min, max);\n\n    assertThat(result, equalTo(expectedRemovals));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zremrangeByScore(key, min, max);\n  }\n\n  @Test\n  public void testZrevrange() {\n    String key = \"zsetKey\";\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set, in reverse order\n    List<String> expectedMembers = Arrays.asList(\"member3\", \"member2\", \"member1\");\n\n    when(commandObjects.zrevrange(key, start, stop)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrange(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrange(key, start, stop);\n  }\n\n  @Test\n  public void testZrevrangeBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set, in reverse order\n    List<byte[]> expectedMembers = Arrays.asList(\"member3\".getBytes(), \"member2\".getBytes(), \"member1\".getBytes());\n\n    when(commandObjects.zrevrange(key, start, stop)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrange(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrange(key, start, stop);\n  }\n\n  @Test\n  public void testZrevrangeWithScores() {\n    String key = \"zsetKey\";\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set with their scores, in reverse order\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member3\", 3.0),\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member1\", 1.0)\n    );\n\n    when(commandObjects.zrevrangeWithScores(key, start, stop)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeWithScores(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeWithScores(key, start, stop);\n  }\n\n  @Test\n  public void testZrevrangeWithScoresBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    long start = 0;\n    long stop = -1; // This typically means all elements in the sorted set with their scores, in reverse order\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member3\".getBytes(), 3.0),\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member1\".getBytes(), 1.0)\n    );\n    when(commandObjects.zrevrangeWithScores(key, start, stop)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeWithScores(key, start, stop);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeWithScores(key, start, stop);\n  }\n\n  @Test\n  public void testZrevrangeByLex() {\n    String key = \"zsetKey\";\n    String max = \"[z\";\n    String min = \"[a\";\n    List<String> expectedMembers = Arrays.asList(\"omega\", \"mu\", \"alpha\");\n\n    when(commandObjects.zrevrangeByLex(key, max, min)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrangeByLex(key, max, min);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrangeByLex(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByLexBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] max = \"[z\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    List<byte[]> expectedMembers = Arrays.asList(\"omega\".getBytes(), \"mu\".getBytes(), \"alpha\".getBytes());\n\n    when(commandObjects.zrevrangeByLex(key, max, min)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrangeByLex(key, max, min);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrangeByLex(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByLexWithOffsetCount() {\n    String key = \"zsetKey\";\n    String max = \"[z\";\n    String min = \"[a\";\n    int offset = 1;\n    int count = 2;\n    List<String> expectedMembers = Arrays.asList(\"mu\", \"alpha\");\n\n    when(commandObjects.zrevrangeByLex(key, max, min, offset, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrangeByLex(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrangeByLex(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByLexWithOffsetCountBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] max = \"[z\".getBytes();\n    byte[] min = \"[a\".getBytes();\n    int offset = 1;\n    int count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"mu\".getBytes(), \"alpha\".getBytes());\n\n    when(commandObjects.zrevrangeByLex(key, max, min, offset, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrangeByLex(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrangeByLex(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScore() {\n    String key = \"zsetKey\";\n    String max = \"3\";\n    String min = \"1\";\n    List<String> expectedMembers = Arrays.asList(\"member2\", \"member1\");\n\n    when(commandObjects.zrevrangeByScore(key, max, min)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrangeByScore(key, max, min);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] max = \"3\".getBytes();\n    byte[] min = \"1\".getBytes();\n    List<byte[]> expectedMembers = Arrays.asList(\"member2\".getBytes(), \"member1\".getBytes());\n\n    when(commandObjects.zrevrangeByScore(key, max, min)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrangeByScore(key, max, min);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreDouble() {\n    String key = \"zsetKey\";\n    double max = 3.0;\n    double min = 1.0;\n    List<String> expectedMembers = Arrays.asList(\"member2\", \"member1\");\n\n    when(commandObjects.zrevrangeByScore(key, max, min)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrangeByScore(key, max, min);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreDoubleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double max = 3.0;\n    double min = 1.0;\n    List<byte[]> expectedMembers = Arrays.asList(\"member2\".getBytes(), \"member1\".getBytes());\n\n    when(commandObjects.zrevrangeByScore(key, max, min)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrangeByScore(key, max, min);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreDoubleWithLimit() {\n    String key = \"zsetKey\";\n    double max = 3.0;\n    double min = 1.0;\n    int offset = 1;\n    int count = 2;\n    List<String> expectedMembers = Arrays.asList(\"member2\", \"member1\");\n\n    when(commandObjects.zrevrangeByScore(key, max, min, offset, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrangeByScore(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreDoubleWithLimitBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double max = 3.0;\n    double min = 1.0;\n    int offset = 1;\n    int count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"member2\".getBytes(), \"member1\".getBytes());\n\n    when(commandObjects.zrevrangeByScore(key, max, min, offset, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrangeByScore(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDouble() {\n    String key = \"zsetKey\";\n    double max = 3.0;\n    double min = 1.0;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member1\", 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDoubleBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double max = 3.0;\n    double min = 1.0;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member1\".getBytes(), 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithLimit() {\n    String key = \"zsetKey\";\n    String max = \"3\";\n    String min = \"1\";\n    int offset = 1;\n    int count = 2;\n    List<String> expectedMembers = Arrays.asList(\"member2\", \"member1\");\n\n    when(commandObjects.zrevrangeByScore(key, max, min, offset, count)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedMembers);\n\n    List<String> result = jedis.zrevrangeByScore(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithLimitBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] max = \"3\".getBytes();\n    byte[] min = \"1\".getBytes();\n    int offset = 1;\n    int count = 2;\n    List<byte[]> expectedMembers = Arrays.asList(\"member2\".getBytes(), \"member1\".getBytes());\n\n    when(commandObjects.zrevrangeByScore(key, max, min, offset, count)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedMembers);\n\n    List<byte[]> result = jedis.zrevrangeByScore(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembers));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zrevrangeByScore(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScores() {\n    String key = \"zsetKey\";\n    String max = \"3\";\n    String min = \"1\";\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member1\", 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] max = \"3\".getBytes();\n    byte[] min = \"1\".getBytes();\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member1\".getBytes(), 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresWithLimit() {\n    String key = \"zsetKey\";\n    String max = \"3\";\n    String min = \"1\";\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member1\", 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresWithLimitBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] max = \"3\".getBytes();\n    byte[] min = \"1\".getBytes();\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member1\".getBytes(), 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDoubleWithLimit() {\n    String key = \"zsetKey\";\n    double max = 3.0;\n    double min = 1.0;\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\", 2.0),\n        new Tuple(\"member1\", 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrangeByScoreWithScoresDoubleWithLimitBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    double max = 3.0;\n    double min = 1.0;\n    int offset = 1;\n    int count = 2;\n    List<Tuple> expectedMembersWithScores = Arrays.asList(\n        new Tuple(\"member2\".getBytes(), 2.0),\n        new Tuple(\"member1\".getBytes(), 1.0)\n    );\n\n    when(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedMembersWithScores);\n\n    List<Tuple> result = jedis.zrevrangeByScoreWithScores(key, max, min, offset, count);\n\n    assertThat(result, equalTo(expectedMembersWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zrevrangeByScoreWithScores(key, max, min, offset, count);\n  }\n\n  @Test\n  public void testZrevrank() {\n    String key = \"zsetKey\";\n    String member = \"member1\";\n    Long expectedRevRank = 10L; // Assuming the member is the eleventh from the end in the sorted set\n\n    when(commandObjects.zrevrank(key, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRevRank);\n\n    Long result = jedis.zrevrank(key, member);\n\n    assertThat(result, equalTo(expectedRevRank));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrevrank(key, member);\n  }\n\n  @Test\n  public void testZrevrankBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    Long expectedRevRank = 10L; // Assuming the member is the eleventh from the end in the sorted set\n\n    when(commandObjects.zrevrank(key, member)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedRevRank);\n\n    Long result = jedis.zrevrank(key, member);\n\n    assertThat(result, equalTo(expectedRevRank));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zrevrank(key, member);\n  }\n\n  @Test\n  public void testZrevrankWithScore() {\n    String key = \"zsetKey\";\n    String member = \"member1\";\n    KeyValue<Long, Double> expectedRevRankWithScore = new KeyValue<>(10L, 1.0); // Assuming the member is the eleventh from the end with a score of 1.0\n\n    when(commandObjects.zrevrankWithScore(key, member)).thenReturn(keyValueLongDoubleCommandObject);\n    when(commandExecutor.executeCommand(keyValueLongDoubleCommandObject)).thenReturn(expectedRevRankWithScore);\n\n    KeyValue<Long, Double> result = jedis.zrevrankWithScore(key, member);\n\n    assertThat(result, equalTo(expectedRevRankWithScore));\n\n    verify(commandExecutor).executeCommand(keyValueLongDoubleCommandObject);\n    verify(commandObjects).zrevrankWithScore(key, member);\n  }\n\n  @Test\n  public void testZrevrankWithScoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    KeyValue<Long, Double> expectedRevRankWithScore = new KeyValue<>(10L, 1.0); // Assuming the member is the eleventh from the end with a score of 1.0\n\n    when(commandObjects.zrevrankWithScore(key, member)).thenReturn(keyValueLongDoubleCommandObject);\n    when(commandExecutor.executeCommand(keyValueLongDoubleCommandObject)).thenReturn(expectedRevRankWithScore);\n\n    KeyValue<Long, Double> result = jedis.zrevrankWithScore(key, member);\n\n    assertThat(result, equalTo(expectedRevRankWithScore));\n\n    verify(commandExecutor).executeCommand(keyValueLongDoubleCommandObject);\n    verify(commandObjects).zrevrankWithScore(key, member);\n  }\n\n  @Test\n  public void testZscan() {\n    String key = \"zsetKey\";\n    String cursor = \"0\";\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    List<Tuple> expectedTuples = Arrays.asList(\n        new Tuple(\"member1\", 1.0),\n        new Tuple(\"member2\", 2.0)\n    );\n    ScanResult<Tuple> expectedScanResult = new ScanResult<>(cursor, expectedTuples);\n\n    when(commandObjects.zscan(key, cursor, params)).thenReturn(scanResultTupleCommandObject);\n    when(commandExecutor.executeCommand(scanResultTupleCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<Tuple> result = jedis.zscan(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultTupleCommandObject);\n    verify(commandObjects).zscan(key, cursor, params);\n  }\n\n  @Test\n  public void testZscanBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] cursor = \"0\".getBytes();\n    ScanParams params = new ScanParams().match(\"*\").count(10);\n    List<Tuple> expectedTuples = Arrays.asList(\n        new Tuple(\"member1\".getBytes(), 1.0),\n        new Tuple(\"member2\".getBytes(), 2.0)\n    );\n    ScanResult<Tuple> expectedScanResult = new ScanResult<>(cursor, expectedTuples);\n\n    when(commandObjects.zscan(key, cursor, params)).thenReturn(scanResultTupleCommandObject);\n    when(commandExecutor.executeCommand(scanResultTupleCommandObject)).thenReturn(expectedScanResult);\n\n    ScanResult<Tuple> result = jedis.zscan(key, cursor, params);\n\n    assertThat(result, equalTo(expectedScanResult));\n\n    verify(commandExecutor).executeCommand(scanResultTupleCommandObject);\n    verify(commandObjects).zscan(key, cursor, params);\n  }\n\n  @Test\n  public void testZscore() {\n    String key = \"zsetKey\";\n    String member = \"member1\";\n    Double expectedScore = 1.0; // Assuming the member has a score of 1.0\n\n    when(commandObjects.zscore(key, member)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedScore);\n\n    Double result = jedis.zscore(key, member);\n\n    assertThat(result, equalTo(expectedScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zscore(key, member);\n  }\n\n  @Test\n  public void testZscoreBinary() {\n    byte[] key = \"zsetKey\".getBytes();\n    byte[] member = \"member1\".getBytes();\n    Double expectedScore = 1.0; // Assuming the member has a score of 1.0\n\n    when(commandObjects.zscore(key, member)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedScore);\n\n    Double result = jedis.zscore(key, member);\n\n    assertThat(result, equalTo(expectedScore));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).zscore(key, member);\n  }\n\n  @Test\n  public void testZunion() {\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MIN);\n    String[] keys = { \"zset1\", \"zset2\" };\n    List<String> expectedUnion = Arrays.asList(\"member1\", \"member2\");\n\n    when(commandObjects.zunion(params, keys)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedUnion);\n\n    List<String> result = jedis.zunion(params, keys);\n\n    assertThat(result, equalTo(expectedUnion));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).zunion(params, keys);\n  }\n\n  @Test\n  public void testZunionBinary() {\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    List<byte[]> expectedUnion = Arrays.asList(\"member1\".getBytes(), \"member2\".getBytes());\n\n    when(commandObjects.zunion(params, keys)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedUnion);\n\n    List<byte[]> result = jedis.zunion(params, keys);\n\n    assertThat(result, equalTo(expectedUnion));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).zunion(params, keys);\n  }\n\n  @Test\n  public void testZunionWithScores() {\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    String[] keys = { \"zset1\", \"zset2\" };\n    List<Tuple> expectedUnionWithScores = Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0));\n\n    when(commandObjects.zunionWithScores(params, keys)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedUnionWithScores);\n\n    List<Tuple> result = jedis.zunionWithScores(params, keys);\n\n    assertThat(result, equalTo(expectedUnionWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zunionWithScores(params, keys);\n  }\n\n  @Test\n  public void testZunionWithScoresBinary() {\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    byte[][] keys = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    List<Tuple> expectedUnionWithScores = Arrays.asList(new Tuple(\"member1\", 1.0), new Tuple(\"member2\", 2.0));\n\n    when(commandObjects.zunionWithScores(params, keys)).thenReturn(listTupleCommandObject);\n    when(commandExecutor.executeCommand(listTupleCommandObject)).thenReturn(expectedUnionWithScores);\n\n    List<Tuple> result = jedis.zunionWithScores(params, keys);\n\n    assertThat(result, equalTo(expectedUnionWithScores));\n\n    verify(commandExecutor).executeCommand(listTupleCommandObject);\n    verify(commandObjects).zunionWithScores(params, keys);\n  }\n\n  @Test\n  public void testZunionstore() {\n    String dstkey = \"zsetUnion\";\n    String[] sets = { \"zset1\", \"zset2\" };\n    long expectedStoredCount = 3L;\n\n    when(commandObjects.zunionstore(dstkey, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zunionstore(dstkey, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zunionstore(dstkey, sets);\n  }\n\n  @Test\n  public void testZunionstoreBinary() {\n    byte[] dstkey = \"zsetUnion\".getBytes();\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    long expectedStoredCount = 3L;\n\n    when(commandObjects.zunionstore(dstkey, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zunionstore(dstkey, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zunionstore(dstkey, sets);\n  }\n\n  @Test\n  public void testZunionstoreWithParams() {\n    String dstkey = \"zsetUnion\";\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    String[] sets = { \"zset1\", \"zset2\" };\n    long expectedStoredCount = 3L;\n\n    when(commandObjects.zunionstore(dstkey, params, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zunionstore(dstkey, params, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zunionstore(dstkey, params, sets);\n  }\n\n  @Test\n  public void testZunionstoreWithParamsBinary() {\n    byte[] dstkey = \"zsetUnion\".getBytes();\n    ZParams params = new ZParams().weights(1, 2).aggregate(ZParams.Aggregate.MAX);\n    byte[][] sets = { \"zset1\".getBytes(), \"zset2\".getBytes() };\n    long expectedStoredCount = 3L;\n\n    when(commandObjects.zunionstore(dstkey, params, sets)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedStoredCount);\n\n    long result = jedis.zunionstore(dstkey, params, sets);\n\n    assertThat(result, equalTo(expectedStoredCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).zunionstore(dstkey, params, sets);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisStreamCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.params.XAddParams;\nimport redis.clients.jedis.params.XAutoClaimParams;\nimport redis.clients.jedis.params.XClaimParams;\nimport redis.clients.jedis.params.XPendingParams;\nimport redis.clients.jedis.params.XReadGroupParams;\nimport redis.clients.jedis.params.XReadParams;\nimport redis.clients.jedis.params.XTrimParams;\nimport redis.clients.jedis.resps.StreamConsumerInfo;\nimport redis.clients.jedis.resps.StreamConsumersInfo;\nimport redis.clients.jedis.resps.StreamEntry;\nimport redis.clients.jedis.resps.StreamFullInfo;\nimport redis.clients.jedis.resps.StreamGroupInfo;\nimport redis.clients.jedis.resps.StreamInfo;\nimport redis.clients.jedis.resps.StreamPendingEntry;\nimport redis.clients.jedis.resps.StreamPendingSummary;\n\npublic class UnifiedJedisStreamCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testXack() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    StreamEntryID[] ids = { new StreamEntryID(\"0-0\"), new StreamEntryID(\"0-1\") };\n    long expectedAcked = 2L;\n\n    when(commandObjects.xack(key, group, ids)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAcked);\n\n    long result = jedis.xack(key, group, ids);\n\n    assertThat(result, equalTo(expectedAcked));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xack(key, group, ids);\n  }\n\n  @Test\n  public void testXackBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] group = \"mygroup\".getBytes();\n    byte[][] ids = { \"0-0\".getBytes(), \"0-1\".getBytes() };\n    long expectedAcked = 2L;\n\n    when(commandObjects.xack(key, group, ids)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedAcked);\n\n    long result = jedis.xack(key, group, ids);\n\n    assertThat(result, equalTo(expectedAcked));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xack(key, group, ids);\n  }\n\n  @Test\n  public void testXadd() {\n    String key = \"mystream\";\n    StreamEntryID id = new StreamEntryID(\"0-0\");\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    StreamEntryID expectedEntryID = new StreamEntryID(\"0-1\");\n\n    when(commandObjects.xadd(key, id, hash)).thenReturn(streamEntryIdCommandObject);\n    when(commandExecutor.executeCommand(streamEntryIdCommandObject)).thenReturn(expectedEntryID);\n\n    StreamEntryID result = jedis.xadd(key, id, hash);\n\n    assertThat(result, equalTo(expectedEntryID));\n\n    verify(commandExecutor).executeCommand(streamEntryIdCommandObject);\n    verify(commandObjects).xadd(key, id, hash);\n  }\n\n  @Test\n  public void testXaddBinary() {\n    byte[] key = \"mystream\".getBytes();\n    XAddParams params = new XAddParams().id(\"0-1\");\n    Map<byte[], byte[]> hash = new HashMap<>();\n    hash.put(\"field1\".getBytes(), \"value1\".getBytes());\n    byte[] expectedEntryId = \"0-1\".getBytes();\n\n    when(commandObjects.xadd(key, params, hash)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedEntryId);\n\n    byte[] result = jedis.xadd(key, params, hash);\n\n    assertThat(result, equalTo(expectedEntryId));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).xadd(key, params, hash);\n  }\n\n  @Test\n  public void testXaddWithParams() {\n    String key = \"mystream\";\n    XAddParams params = new XAddParams();\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    StreamEntryID expectedEntryID = new StreamEntryID(\"0-1\");\n\n    when(commandObjects.xadd(key, params, hash)).thenReturn(streamEntryIdCommandObject);\n    when(commandExecutor.executeCommand(streamEntryIdCommandObject)).thenReturn(expectedEntryID);\n\n    StreamEntryID result = jedis.xadd(key, params, hash);\n\n    assertThat(result, equalTo(expectedEntryID));\n\n    verify(commandExecutor).executeCommand(streamEntryIdCommandObject);\n    verify(commandObjects).xadd(key, params, hash);\n  }\n\n  @Test\n  public void testXautoclaim() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    String consumerName = \"myconsumer\";\n    long minIdleTime = 10000L;\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    XAutoClaimParams params = new XAutoClaimParams();\n    StreamEntryID nextStart = new StreamEntryID(\"0-1\");\n    List<StreamEntry> claimedEntries = new ArrayList<>();\n    AbstractMap.SimpleImmutableEntry<StreamEntryID, List<StreamEntry>> expectedResponse = new AbstractMap.SimpleImmutableEntry<>(nextStart, claimedEntries);\n\n    when(commandObjects.xautoclaim(key, group, consumerName, minIdleTime, start, params)).thenReturn(entryStreamEntryIdListStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(entryStreamEntryIdListStreamEntryCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<StreamEntryID, List<StreamEntry>> result = jedis.xautoclaim(key, group, consumerName, minIdleTime, start, params);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entryStreamEntryIdListStreamEntryCommandObject);\n    verify(commandObjects).xautoclaim(key, group, consumerName, minIdleTime, start, params);\n  }\n\n  @Test\n  public void testXautoclaimBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] consumerName = \"myconsumer\".getBytes();\n    long minIdleTime = 10000L;\n    byte[] start = \"0-0\".getBytes();\n    XAutoClaimParams params = new XAutoClaimParams();\n    List<Object> expectedAutoClaimResult = new ArrayList<>();\n\n    when(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedAutoClaimResult);\n\n    List<Object> result = jedis.xautoclaim(key, groupName, consumerName, minIdleTime, start, params);\n\n    assertThat(result, equalTo(expectedAutoClaimResult));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xautoclaim(key, groupName, consumerName, minIdleTime, start, params);\n  }\n\n  @Test\n  public void testXautoclaimJustId() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    String consumerName = \"myconsumer\";\n    long minIdleTime = 10000L;\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    XAutoClaimParams params = new XAutoClaimParams();\n    StreamEntryID nextStart = new StreamEntryID(\"0-1\");\n    List<StreamEntryID> claimedEntryIds = Arrays.asList(new StreamEntryID(\"0-0\"), new StreamEntryID(\"0-1\"));\n    AbstractMap.SimpleImmutableEntry<StreamEntryID, List<StreamEntryID>> expectedResponse = new AbstractMap.SimpleImmutableEntry<>(nextStart, claimedEntryIds);\n\n    when(commandObjects.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params)).thenReturn(entryStreamEntryIdListStreamEntryIdCommandObject);\n    when(commandExecutor.executeCommand(entryStreamEntryIdListStreamEntryIdCommandObject)).thenReturn(expectedResponse);\n\n    Map.Entry<StreamEntryID, List<StreamEntryID>> result = jedis.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(entryStreamEntryIdListStreamEntryIdCommandObject);\n    verify(commandObjects).xautoclaimJustId(key, group, consumerName, minIdleTime, start, params);\n  }\n\n  @Test\n  public void testXautoclaimJustIdBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] consumerName = \"myconsumer\".getBytes();\n    long minIdleTime = 10000L;\n    byte[] start = \"0-0\".getBytes();\n    XAutoClaimParams params = new XAutoClaimParams();\n    List<Object> expectedAutoClaimResult = new ArrayList<>();\n\n    when(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedAutoClaimResult);\n\n    List<Object> result = jedis.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params);\n\n    assertThat(result, equalTo(expectedAutoClaimResult));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params);\n  }\n\n  @Test\n  public void testXclaim() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    String consumerName = \"myconsumer\";\n    long minIdleTime = 10000L;\n    XClaimParams params = new XClaimParams();\n    StreamEntryID[] ids = { new StreamEntryID(\"0-0\"), new StreamEntryID(\"0-1\") };\n    List<StreamEntry> expectedEntries = new ArrayList<>();\n\n    when(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xclaim(key, group, consumerName, minIdleTime, params, ids);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xclaim(key, group, consumerName, minIdleTime, params, ids);\n  }\n\n  @Test\n  public void testXclaimBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] group = \"mygroup\".getBytes();\n    byte[] consumerName = \"myconsumer\".getBytes();\n    long minIdleTime = 10000L;\n    XClaimParams params = new XClaimParams();\n    byte[][] ids = { \"0-0\".getBytes(), \"0-1\".getBytes() };\n    List<byte[]> expectedClaimedIds = Arrays.asList(\"0-0\".getBytes(), \"0-1\".getBytes());\n\n    when(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedClaimedIds);\n\n    List<byte[]> result = jedis.xclaim(key, group, consumerName, minIdleTime, params, ids);\n\n    assertThat(result, equalTo(expectedClaimedIds));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).xclaim(key, group, consumerName, minIdleTime, params, ids);\n  }\n\n  @Test\n  public void testXclaimJustId() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    String consumerName = \"myconsumer\";\n    long minIdleTime = 10000L;\n    XClaimParams params = new XClaimParams();\n    StreamEntryID[] ids = { new StreamEntryID(\"0-0\"), new StreamEntryID(\"0-1\") };\n    List<StreamEntryID> expectedEntryIds = Arrays.asList(ids);\n\n    when(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids)).thenReturn(listStreamEntryIdCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryIdCommandObject)).thenReturn(expectedEntryIds);\n\n    List<StreamEntryID> result = jedis.xclaimJustId(key, group, consumerName, minIdleTime, params, ids);\n\n    assertThat(result, equalTo(expectedEntryIds));\n\n    verify(commandExecutor).executeCommand(listStreamEntryIdCommandObject);\n    verify(commandObjects).xclaimJustId(key, group, consumerName, minIdleTime, params, ids);\n  }\n\n  @Test\n  public void testXclaimJustIdBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] group = \"mygroup\".getBytes();\n    byte[] consumerName = \"myconsumer\".getBytes();\n    long minIdleTime = 10000L;\n    XClaimParams params = new XClaimParams();\n    byte[][] ids = { \"0-0\".getBytes(), \"0-1\".getBytes() };\n    List<byte[]> expectedClaimedIds = Arrays.asList(\"0-0\".getBytes(), \"0-1\".getBytes());\n\n    when(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedClaimedIds);\n\n    List<byte[]> result = jedis.xclaimJustId(key, group, consumerName, minIdleTime, params, ids);\n\n    assertThat(result, equalTo(expectedClaimedIds));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).xclaimJustId(key, group, consumerName, minIdleTime, params, ids);\n  }\n\n  @Test\n  public void testXdel() {\n    String key = \"mystream\";\n    StreamEntryID[] ids = { new StreamEntryID(\"0-0\"), new StreamEntryID(\"0-1\") };\n    long expectedDeletedCount = 2L; // Assuming the entries were successfully deleted\n\n    when(commandObjects.xdel(key, ids)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedDeletedCount);\n\n    long result = jedis.xdel(key, ids);\n\n    assertThat(result, equalTo(expectedDeletedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xdel(key, ids);\n  }\n\n  @Test\n  public void testXdelBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[][] ids = { \"0-0\".getBytes(), \"0-1\".getBytes() };\n    long expectedDeleted = 2L;\n\n    when(commandObjects.xdel(key, ids)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedDeleted);\n\n    long result = jedis.xdel(key, ids);\n\n    assertThat(result, equalTo(expectedDeleted));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xdel(key, ids);\n  }\n\n  @Test\n  public void testXgroupCreate() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    StreamEntryID id = new StreamEntryID(\"0-0\");\n    boolean makeStream = true;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.xgroupCreate(key, groupName, id, makeStream)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.xgroupCreate(key, groupName, id, makeStream);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).xgroupCreate(key, groupName, id, makeStream);\n  }\n\n  @Test\n  public void testXgroupCreateBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] id = \"0-0\".getBytes();\n    boolean makeStream = true;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.xgroupCreate(key, groupName, id, makeStream)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.xgroupCreate(key, groupName, id, makeStream);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).xgroupCreate(key, groupName, id, makeStream);\n  }\n\n  @Test\n  public void testXgroupCreateConsumer() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    String consumerName = \"myconsumer\";\n    boolean expectedResponse = true; // Assuming the consumer was successfully created\n\n    when(commandObjects.xgroupCreateConsumer(key, groupName, consumerName)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.xgroupCreateConsumer(key, groupName, consumerName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).xgroupCreateConsumer(key, groupName, consumerName);\n  }\n\n  @Test\n  public void testXgroupCreateConsumerBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] consumerName = \"myconsumer\".getBytes();\n    boolean expectedResponse = true;\n\n    when(commandObjects.xgroupCreateConsumer(key, groupName, consumerName)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(expectedResponse);\n\n    boolean result = jedis.xgroupCreateConsumer(key, groupName, consumerName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).xgroupCreateConsumer(key, groupName, consumerName);\n  }\n\n  @Test\n  public void testXgroupDelConsumer() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    String consumerName = \"myconsumer\";\n    long expectedDeletedCount = 1L; // Assuming the consumer was successfully deleted\n\n    when(commandObjects.xgroupDelConsumer(key, groupName, consumerName)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedDeletedCount);\n\n    long result = jedis.xgroupDelConsumer(key, groupName, consumerName);\n\n    assertThat(result, equalTo(expectedDeletedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xgroupDelConsumer(key, groupName, consumerName);\n  }\n\n  @Test\n  public void testXgroupDelConsumerBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] consumerName = \"myconsumer\".getBytes();\n    long expectedDeleted = 1L;\n\n    when(commandObjects.xgroupDelConsumer(key, groupName, consumerName)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedDeleted);\n\n    long result = jedis.xgroupDelConsumer(key, groupName, consumerName);\n\n    assertThat(result, equalTo(expectedDeleted));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xgroupDelConsumer(key, groupName, consumerName);\n  }\n\n  @Test\n  public void testXgroupDestroy() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    long expectedResponse = 1L; // Assuming the group was successfully destroyed\n\n    when(commandObjects.xgroupDestroy(key, groupName)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.xgroupDestroy(key, groupName);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xgroupDestroy(key, groupName);\n  }\n\n  @Test\n  public void testXgroupDestroyBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    long expectedDestroyed = 1L;\n\n    when(commandObjects.xgroupDestroy(key, groupName)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedDestroyed);\n\n    long result = jedis.xgroupDestroy(key, groupName);\n\n    assertThat(result, equalTo(expectedDestroyed));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xgroupDestroy(key, groupName);\n  }\n\n  @Test\n  public void testXgroupSetID() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    StreamEntryID id = new StreamEntryID(\"0-0\");\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.xgroupSetID(key, groupName, id)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.xgroupSetID(key, groupName, id);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).xgroupSetID(key, groupName, id);\n  }\n\n  @Test\n  public void testXgroupSetIDBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] id = \"0-1\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.xgroupSetID(key, groupName, id)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.xgroupSetID(key, groupName, id);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).xgroupSetID(key, groupName, id);\n  }\n\n  @Test\n  public void testXinfoConsumers() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    List<StreamConsumersInfo> expectedConsumers = Collections.singletonList(mock(StreamConsumersInfo.class));\n\n    when(commandObjects.xinfoConsumers(key, group)).thenReturn(listStreamConsumersInfoCommandObject);\n    when(commandExecutor.executeCommand(listStreamConsumersInfoCommandObject)).thenReturn(expectedConsumers);\n\n    List<StreamConsumersInfo> result = jedis.xinfoConsumers(key, group);\n\n    assertThat(result, equalTo(expectedConsumers));\n\n    verify(commandExecutor).executeCommand(listStreamConsumersInfoCommandObject);\n    verify(commandObjects).xinfoConsumers(key, group);\n  }\n\n  @Test\n  public void testXinfoConsumersBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] group = \"mygroup\".getBytes();\n    List<Object> expectedConsumersInfo = new ArrayList<>();\n\n    when(commandObjects.xinfoConsumers(key, group)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedConsumersInfo);\n\n    List<Object> result = jedis.xinfoConsumers(key, group);\n\n    assertThat(result, equalTo(expectedConsumersInfo));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xinfoConsumers(key, group);\n  }\n\n  @Test\n  public void testXinfoConsumers2() {\n    String key = \"mystream\";\n    String group = \"mygroup\";\n    List<StreamConsumerInfo> expectedConsumerInfos = Collections.singletonList(mock(StreamConsumerInfo.class));\n\n    when(commandObjects.xinfoConsumers2(key, group)).thenReturn(listStreamConsumerInfoCommandObject);\n    when(commandExecutor.executeCommand(listStreamConsumerInfoCommandObject)).thenReturn(expectedConsumerInfos);\n\n    List<StreamConsumerInfo> result = jedis.xinfoConsumers2(key, group);\n\n    assertThat(result, equalTo(expectedConsumerInfos));\n\n    verify(commandExecutor).executeCommand(listStreamConsumerInfoCommandObject);\n    verify(commandObjects).xinfoConsumers2(key, group);\n  }\n\n  @Test\n  public void testXinfoGroups() {\n    String key = \"mystream\";\n    List<StreamGroupInfo> expectedGroups = Collections.singletonList(mock(StreamGroupInfo.class));\n\n    when(commandObjects.xinfoGroups(key)).thenReturn(listStreamGroupInfoCommandObject);\n    when(commandExecutor.executeCommand(listStreamGroupInfoCommandObject)).thenReturn(expectedGroups);\n\n    List<StreamGroupInfo> result = jedis.xinfoGroups(key);\n\n    assertThat(result, equalTo(expectedGroups));\n\n    verify(commandExecutor).executeCommand(listStreamGroupInfoCommandObject);\n    verify(commandObjects).xinfoGroups(key);\n  }\n\n  @Test\n  public void testXinfoGroupsBinary() {\n    byte[] key = \"mystream\".getBytes();\n    List<Object> expectedGroupsInfo = new ArrayList<>();\n\n    when(commandObjects.xinfoGroups(key)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedGroupsInfo);\n\n    List<Object> result = jedis.xinfoGroups(key);\n\n    assertThat(result, equalTo(expectedGroupsInfo));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xinfoGroups(key);\n  }\n\n  @Test\n  public void testXinfoStream() {\n    String key = \"mystream\";\n    StreamInfo expectedStreamInfo = mock(StreamInfo.class);\n\n    when(commandObjects.xinfoStream(key)).thenReturn(streamInfoCommandObject);\n    when(commandExecutor.executeCommand(streamInfoCommandObject)).thenReturn(expectedStreamInfo);\n\n    StreamInfo result = jedis.xinfoStream(key);\n\n    assertThat(result, sameInstance(expectedStreamInfo));\n\n    verify(commandExecutor).executeCommand(streamInfoCommandObject);\n    verify(commandObjects).xinfoStream(key);\n  }\n\n  @Test\n  public void testXinfoStreamBinary() {\n    byte[] key = \"mystream\".getBytes();\n    Object expectedStreamInfo = new Object();\n\n    when(commandObjects.xinfoStream(key)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedStreamInfo);\n\n    Object result = jedis.xinfoStream(key);\n\n    assertThat(result, sameInstance(expectedStreamInfo));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).xinfoStream(key);\n  }\n\n  @Test\n  public void testXinfoStreamFull() {\n    String key = \"mystream\";\n    StreamFullInfo expectedStreamFullInfo = mock(StreamFullInfo.class);\n\n    when(commandObjects.xinfoStreamFull(key)).thenReturn(streamFullInfoCommandObject);\n    when(commandExecutor.executeCommand(streamFullInfoCommandObject)).thenReturn(expectedStreamFullInfo);\n\n    StreamFullInfo result = jedis.xinfoStreamFull(key);\n\n    assertThat(result, sameInstance(expectedStreamFullInfo));\n\n    verify(commandExecutor).executeCommand(streamFullInfoCommandObject);\n    verify(commandObjects).xinfoStreamFull(key);\n  }\n\n  @Test\n  public void testXinfoStreamFullBinary() {\n    byte[] key = \"mystream\".getBytes();\n    Object expectedStreamInfoFull = new Object();\n\n    when(commandObjects.xinfoStreamFull(key)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedStreamInfoFull);\n\n    Object result = jedis.xinfoStreamFull(key);\n\n    assertThat(result, sameInstance(expectedStreamInfoFull));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).xinfoStreamFull(key);\n  }\n\n  @Test\n  public void testXinfoStreamFullWithCount() {\n    String key = \"mystream\";\n    int count = 10;\n    StreamFullInfo expectedStreamFullInfo = mock(StreamFullInfo.class);\n\n    when(commandObjects.xinfoStreamFull(key, count)).thenReturn(streamFullInfoCommandObject);\n    when(commandExecutor.executeCommand(streamFullInfoCommandObject)).thenReturn(expectedStreamFullInfo);\n\n    StreamFullInfo result = jedis.xinfoStreamFull(key, count);\n\n    assertThat(result, sameInstance(expectedStreamFullInfo));\n\n    verify(commandExecutor).executeCommand(streamFullInfoCommandObject);\n    verify(commandObjects).xinfoStreamFull(key, count);\n  }\n\n  @Test\n  public void testXinfoStreamFullWithCountBinary() {\n    byte[] key = \"mystream\".getBytes();\n    int count = 10;\n    Object expectedStreamInfoFull = new Object();\n\n    when(commandObjects.xinfoStreamFull(key, count)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedStreamInfoFull);\n\n    Object result = jedis.xinfoStreamFull(key, count);\n\n    assertThat(result, sameInstance(expectedStreamInfoFull));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).xinfoStreamFull(key, count);\n  }\n\n  @Test\n  public void testXlen() {\n    String key = \"mystream\";\n    long expectedLength = 10L;\n\n    when(commandObjects.xlen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.xlen(key);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xlen(key);\n  }\n\n  @Test\n  public void testXlenBinary() {\n    byte[] key = \"mystream\".getBytes();\n    long expectedLength = 100L;\n\n    when(commandObjects.xlen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.xlen(key);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xlen(key);\n  }\n\n  @Test\n  public void testXpending() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    StreamPendingSummary expectedSummary = new StreamPendingSummary(10L,\n        new StreamEntryID(\"0-0\"), new StreamEntryID(\"0-1\"), Collections.emptyMap());\n\n    when(commandObjects.xpending(key, groupName)).thenReturn(streamPendingSummaryCommandObject);\n    when(commandExecutor.executeCommand(streamPendingSummaryCommandObject)).thenReturn(expectedSummary);\n\n    StreamPendingSummary result = jedis.xpending(key, groupName);\n\n    assertThat(result, equalTo(expectedSummary));\n\n    verify(commandExecutor).executeCommand(streamPendingSummaryCommandObject);\n    verify(commandObjects).xpending(key, groupName);\n  }\n\n  @Test\n  public void testXpendingBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    Object expectedPendingInfo = new Object();\n\n    when(commandObjects.xpending(key, groupName)).thenReturn(objectCommandObject);\n    when(commandExecutor.executeCommand(objectCommandObject)).thenReturn(expectedPendingInfo);\n\n    Object result = jedis.xpending(key, groupName);\n\n    assertThat(result, sameInstance(expectedPendingInfo));\n\n    verify(commandExecutor).executeCommand(objectCommandObject);\n    verify(commandObjects).xpending(key, groupName);\n  }\n\n  @Test\n  public void testXpendingWithParams() {\n    String key = \"mystream\";\n    String groupName = \"mygroup\";\n    XPendingParams params = new XPendingParams();\n    List<StreamPendingEntry> expectedPendingEntries = new ArrayList<>();\n\n    when(commandObjects.xpending(key, groupName, params)).thenReturn(listStreamPendingEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamPendingEntryCommandObject)).thenReturn(expectedPendingEntries);\n\n    List<StreamPendingEntry> result = jedis.xpending(key, groupName, params);\n\n    assertThat(result, equalTo(expectedPendingEntries));\n\n    verify(commandExecutor).executeCommand(listStreamPendingEntryCommandObject);\n    verify(commandObjects).xpending(key, groupName, params);\n  }\n\n  @Test\n  public void testXpendingWithParamsBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] groupName = \"mygroup\".getBytes();\n    XPendingParams params = new XPendingParams().count(10);\n    List<Object> expectedPendingList = new ArrayList<>();\n\n    when(commandObjects.xpending(key, groupName, params)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedPendingList);\n\n    List<Object> result = jedis.xpending(key, groupName, params);\n\n    assertThat(result, equalTo(expectedPendingList));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xpending(key, groupName, params);\n  }\n\n  @Test\n  public void testXrange() {\n    String key = \"mystream\";\n    String start = \"-\";\n    String end = \"+\";\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-1\"), hash));\n\n    when(commandObjects.xrange(key, start, end)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrange(key, start, end);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrange(key, start, end);\n  }\n\n  @Test\n  public void testXrangeBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] start = \"0-0\".getBytes();\n    byte[] end = \"+\".getBytes();\n    List<Object> expectedRange = Arrays.asList(\n        new StreamEntry(new StreamEntryID(\"0-0\"), Collections.singletonMap(\"field1\", \"value1\")),\n        new StreamEntry(new StreamEntryID(\"0-1\"), Collections.singletonMap(\"field2\", \"value2\")));\n\n    when(commandObjects.xrange(key, start, end)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedRange);\n\n    List<Object> result = jedis.xrange(key, start, end);\n\n    assertThat(result, equalTo(expectedRange));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xrange(key, start, end);\n  }\n\n  @Test\n  public void testXrangeWithCount() {\n    String key = \"mystream\";\n    String start = \"-\";\n    String end = \"+\";\n    int count = 10;\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-1\"), hash));\n\n    when(commandObjects.xrange(key, start, end, count)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrange(key, start, end, count);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrange(key, start, end, count);\n  }\n\n  @Test\n  public void testXrangeWithCountBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] start = \"0-0\".getBytes();\n    byte[] end = \"+\".getBytes();\n    int count = 2;\n    List<Object> expectedRange = Arrays.asList(\n        new StreamEntry(new StreamEntryID(\"0-0\"), Collections.singletonMap(\"field1\", \"value1\")),\n        new StreamEntry(new StreamEntryID(\"0-1\"), Collections.singletonMap(\"field2\", \"value2\")));\n\n    when(commandObjects.xrange(key, start, end, count)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedRange);\n\n    List<Object> result = jedis.xrange(key, start, end, count);\n\n    assertThat(result, equalTo(expectedRange));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xrange(key, start, end, count);\n  }\n\n  @Test\n  public void testXrangeIds() {\n    String key = \"mystream\";\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    StreamEntryID end = new StreamEntryID(\"0-1\");\n    List<StreamEntry> expectedEntries = new ArrayList<>();\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    expectedEntries.add(new StreamEntry(new StreamEntryID(\"0-1\"), hash));\n\n    when(commandObjects.xrange(key, start, end)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrange(key, start, end);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrange(key, start, end);\n  }\n\n  @Test\n  public void testXrangeIdsWithCount() {\n    String key = \"mystream\";\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    StreamEntryID end = new StreamEntryID(\"0-1\");\n    int count = 10;\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-1\"), hash));\n\n    when(commandObjects.xrange(key, start, end, count)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrange(key, start, end, count);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrange(key, start, end, count);\n  }\n\n  @Test\n  public void testXread() {\n    XReadParams xReadParams = new XReadParams().count(2).block(0);\n    Map<String, StreamEntryID> streams = Collections.singletonMap(\"mystream\", new StreamEntryID(\"0-0\"));\n    List<Map.Entry<String, List<StreamEntry>>> expectedEntries = new ArrayList<>();\n\n    when(commandObjects.xread(xReadParams, streams)).thenReturn(listEntryStringListStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listEntryStringListStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<Map.Entry<String, List<StreamEntry>>> result = jedis.xread(xReadParams, streams);\n\n    assertThat(result, equalTo(expectedEntries));\n    verify(commandExecutor).executeCommand(listEntryStringListStreamEntryCommandObject);\n    verify(commandObjects).xread(xReadParams, streams);\n  }\n\n  @Test\n  public void testXreadBinary() {\n    XReadParams xReadParams = new XReadParams().count(2).block(0);\n    Map.Entry<byte[], byte[]> stream1 = new AbstractMap.SimpleEntry<>(\"mystream\".getBytes(), \"0-0\".getBytes());\n    List<Object> expectedReadResult = new ArrayList<>();\n\n    when(commandObjects.xread(xReadParams, stream1)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedReadResult);\n\n    List<Object> result = jedis.xread(xReadParams, stream1);\n\n    assertThat(result, equalTo(expectedReadResult));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xread(xReadParams, stream1);\n  }\n\n  @Test\n  public void testXreadAsMap() {\n    XReadParams xReadParams = new XReadParams().count(2).block(0);\n    Map<String, StreamEntryID> stream = Collections.singletonMap(\"mystream\", new StreamEntryID(\"0-0\"));\n    Map<String, List<StreamEntry>> expectedResult = new HashMap<>();\n\n    when(commandObjects.xreadAsMap(xReadParams, stream)).thenReturn(mapStringListStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(mapStringListStreamEntryCommandObject)).thenReturn(expectedResult);\n\n    Map<String, List<StreamEntry>> result = jedis.xreadAsMap(xReadParams, stream);\n\n    assertThat(result, sameInstance(expectedResult));\n    verify(commandExecutor).executeCommand(mapStringListStreamEntryCommandObject);\n    verify(commandObjects).xreadAsMap(xReadParams, stream);\n  }\n\n  @Test\n  public void testXreadGroup() {\n    String groupName = \"mygroup\";\n    String consumer = \"myconsumer\";\n    XReadGroupParams xReadGroupParams = new XReadGroupParams().count(2).block(0);\n    Map<String, StreamEntryID> streams = Collections.singletonMap(\"mystream\", new StreamEntryID(\"0-0\"));\n    List<Map.Entry<String, List<StreamEntry>>> expectedEntries = new ArrayList<>();\n\n    when(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams)).thenReturn(listEntryStringListStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listEntryStringListStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<Map.Entry<String, List<StreamEntry>>> result = jedis.xreadGroup(groupName, consumer, xReadGroupParams, streams);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listEntryStringListStreamEntryCommandObject);\n    verify(commandObjects).xreadGroup(groupName, consumer, xReadGroupParams, streams);\n  }\n\n  @Test\n  public void testXreadGroupBinary() {\n    byte[] groupName = \"mygroup\".getBytes();\n    byte[] consumer = \"myconsumer\".getBytes();\n    XReadGroupParams xReadGroupParams = new XReadGroupParams().count(2).block(0);\n    Map.Entry<byte[], byte[]> stream1 = new AbstractMap.SimpleEntry<>(\"mystream\".getBytes(), \"0-0\".getBytes());\n    List<Object> expectedReadGroupResult = new ArrayList<>();\n\n    when(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, stream1)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedReadGroupResult);\n\n    List<Object> result = jedis.xreadGroup(groupName, consumer, xReadGroupParams, stream1);\n\n    assertThat(result, equalTo(expectedReadGroupResult));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xreadGroup(groupName, consumer, xReadGroupParams, stream1);\n  }\n\n  @Test\n  public void testXreadGroupAsMap() {\n    String groupName = \"mygroup\";\n    String consumer = \"myconsumer\";\n    XReadGroupParams xReadGroupParams = new XReadGroupParams().count(2).block(0);\n    Map<String, StreamEntryID> stream1 = Collections.singletonMap(\"mystream\", new StreamEntryID());\n    Map<String, List<StreamEntry>> expectedReadGroupAsMapResult = new HashMap<>();\n\n    when(commandObjects.xreadGroupAsMap(groupName, consumer, xReadGroupParams, stream1)).thenReturn(mapStringListStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(mapStringListStreamEntryCommandObject)).thenReturn(expectedReadGroupAsMapResult);\n\n    Map<String, List<StreamEntry>> result = jedis.xreadGroupAsMap(groupName, consumer, xReadGroupParams, stream1);\n\n    assertThat(result, sameInstance(expectedReadGroupAsMapResult));\n\n    verify(commandExecutor).executeCommand(mapStringListStreamEntryCommandObject);\n    verify(commandObjects).xreadGroupAsMap(groupName, consumer, xReadGroupParams, stream1);\n  }\n\n  @Test\n  public void testXrevrange() {\n    String key = \"mystream\";\n    String end = \"+\";\n    String start = \"-\";\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-0\"), hash));\n\n    when(commandObjects.xrevrange(key, end, start)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrevrange(key, end, start);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrevrange(key, end, start);\n  }\n\n  @Test\n  public void testXrevrangeBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] end = \"+\".getBytes();\n    byte[] start = \"0-0\".getBytes();\n    List<Object> expectedReverseRange = Arrays.asList(\n        new StreamEntry(new StreamEntryID(\"0-1\"), Collections.singletonMap(\"field2\", \"value2\")),\n        new StreamEntry(new StreamEntryID(\"0-0\"), Collections.singletonMap(\"field1\", \"value1\")));\n\n    when(commandObjects.xrevrange(key, end, start)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedReverseRange);\n\n    List<Object> result = jedis.xrevrange(key, end, start);\n\n    assertThat(result, equalTo(expectedReverseRange));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xrevrange(key, end, start);\n  }\n\n  @Test\n  public void testXrevrangeWithCount() {\n    String key = \"mystream\";\n    String end = \"+\";\n    String start = \"-\";\n    int count = 10;\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-0\"), hash));\n\n    when(commandObjects.xrevrange(key, end, start, count)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrevrange(key, end, start, count);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrevrange(key, end, start, count);\n  }\n\n  @Test\n  public void testXrevrangeWithCountBinary() {\n    byte[] key = \"mystream\".getBytes();\n    byte[] end = \"+\".getBytes();\n    byte[] start = \"0-0\".getBytes();\n    int count = 1;\n    List<Object> expectedReverseRange = Collections.singletonList(\n        new StreamEntry(new StreamEntryID(\"0-1\"), Collections.singletonMap(\"field2\", \"value2\")));\n\n    when(commandObjects.xrevrange(key, end, start, count)).thenReturn(listObjectCommandObject);\n    when(commandExecutor.executeCommand(listObjectCommandObject)).thenReturn(expectedReverseRange);\n\n    List<Object> result = jedis.xrevrange(key, end, start, count);\n\n    assertThat(result, equalTo(expectedReverseRange));\n\n    verify(commandExecutor).executeCommand(listObjectCommandObject);\n    verify(commandObjects).xrevrange(key, end, start, count);\n  }\n\n  @Test\n  public void testXrevrangeIds() {\n    String key = \"mystream\";\n    StreamEntryID end = new StreamEntryID(\"0-1\");\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-0\"), hash));\n\n    when(commandObjects.xrevrange(key, end, start)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrevrange(key, end, start);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrevrange(key, end, start);\n  }\n\n  @Test\n  public void testXrevrangeIdsWithCount() {\n    String key = \"mystream\";\n    StreamEntryID end = new StreamEntryID(\"0-1\");\n    StreamEntryID start = new StreamEntryID(\"0-0\");\n    int count = 10;\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"field1\", \"value1\");\n    hash.put(\"field2\", \"value2\");\n    List<StreamEntry> expectedEntries = Collections.singletonList(new StreamEntry(new StreamEntryID(\"0-0\"), hash));\n\n    when(commandObjects.xrevrange(key, end, start, count)).thenReturn(listStreamEntryCommandObject);\n    when(commandExecutor.executeCommand(listStreamEntryCommandObject)).thenReturn(expectedEntries);\n\n    List<StreamEntry> result = jedis.xrevrange(key, end, start, count);\n\n    assertThat(result, equalTo(expectedEntries));\n\n    verify(commandExecutor).executeCommand(listStreamEntryCommandObject);\n    verify(commandObjects).xrevrange(key, end, start, count);\n  }\n\n  @Test\n  public void testXtrim() {\n    String key = \"mystream\";\n    long maxLen = 1000L;\n    boolean approximate = false;\n    long expectedTrimmedCount = 10L; // Assuming 10 entries were trimmed\n\n    when(commandObjects.xtrim(key, maxLen, approximate)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedTrimmedCount);\n\n    long result = jedis.xtrim(key, maxLen, approximate);\n\n    assertThat(result, equalTo(expectedTrimmedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xtrim(key, maxLen, approximate);\n  }\n\n  @Test\n  public void testXtrimBinary() {\n    byte[] key = \"mystream\".getBytes();\n    long maxLen = 1000L;\n    boolean approximateLength = true;\n    long expectedTrimmed = 10L;\n\n    when(commandObjects.xtrim(key, maxLen, approximateLength)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedTrimmed);\n\n    long result = jedis.xtrim(key, maxLen, approximateLength);\n\n    assertThat(result, equalTo(expectedTrimmed));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xtrim(key, maxLen, approximateLength);\n  }\n\n  @Test\n  public void testXtrimWithParams() {\n    String key = \"mystream\";\n    XTrimParams params = new XTrimParams().maxLen(1000L);\n    long expectedTrimmedCount = 10L; // Assuming 10 entries were trimmed\n\n    when(commandObjects.xtrim(key, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedTrimmedCount);\n\n    long result = jedis.xtrim(key, params);\n\n    assertThat(result, equalTo(expectedTrimmedCount));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xtrim(key, params);\n  }\n\n  @Test\n  public void testXtrimWithParamsBinary() {\n    byte[] key = \"mystream\".getBytes();\n    XTrimParams params = new XTrimParams().maxLen(1000L);\n    long expectedTrimmed = 10L;\n\n    when(commandObjects.xtrim(key, params)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedTrimmed);\n\n    long result = jedis.xtrim(key, params);\n\n    assertThat(result, equalTo(expectedTrimmed));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).xtrim(key, params);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisStringCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.params.GetExParams;\nimport redis.clients.jedis.params.LCSParams;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.MSetExParams;\n\nimport redis.clients.jedis.resps.LCSMatchResult;\n\npublic class UnifiedJedisStringCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testAppend() {\n    String key = \"key\";\n    String value = \"value\";\n    long expectedLength = 10L; // Assuming the new length of the string is 10 after append\n\n    when(commandObjects.append(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.append(key, value);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).append(key, value);\n  }\n\n  @Test\n  public void testAppendBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    long expectedLength = 10L; // Assuming the new length of the string is 10 after append\n\n    when(commandObjects.append(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.append(key, value);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).append(key, value);\n  }\n\n  @Test\n  public void testDecr() {\n    String key = \"key\";\n    long expectedValue = -1L; // Assuming the key was decremented successfully\n\n    when(commandObjects.decr(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.decr(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).decr(key);\n  }\n\n  @Test\n  public void testDecrBinary() {\n    byte[] key = \"key\".getBytes();\n    long expectedValue = -1L; // Assuming the key was decremented successfully\n\n    when(commandObjects.decr(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.decr(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).decr(key);\n  }\n\n  @Test\n  public void testDecrBy() {\n    String key = \"key\";\n    long decrement = 2L;\n    long expectedValue = -2L; // Assuming the key was decremented by 2 successfully\n\n    when(commandObjects.decrBy(key, decrement)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.decrBy(key, decrement);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).decrBy(key, decrement);\n  }\n\n  @Test\n  public void testDecrByBinary() {\n    byte[] key = \"key\".getBytes();\n    long decrement = 2L;\n    long expectedValue = -2L; // Assuming the key was decremented by 2 successfully\n\n    when(commandObjects.decrBy(key, decrement)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.decrBy(key, decrement);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).decrBy(key, decrement);\n  }\n\n  @Test\n  public void testGet() {\n    String key = \"key\";\n    String expectedValue = \"value\";\n\n    when(commandObjects.get(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedValue);\n\n    String result = jedis.get(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).get(key);\n  }\n\n  @Test\n  public void testGetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] expectedValue = \"value\".getBytes();\n\n    when(commandObjects.get(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedValue);\n\n    byte[] result = jedis.get(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).get(key);\n  }\n\n  @Test\n  public void testGetDel() {\n    String key = \"key\";\n    String expectedValue = \"value\";\n\n    when(commandObjects.getDel(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedValue);\n\n    String result = jedis.getDel(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).getDel(key);\n  }\n\n  @Test\n  public void testGetDelBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] expectedValue = \"value\".getBytes();\n\n    when(commandObjects.getDel(key)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedValue);\n\n    byte[] result = jedis.getDel(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).getDel(key);\n  }\n\n  @Test\n  public void testGetEx() {\n    String key = \"key\";\n    GetExParams params = new GetExParams().ex(10);\n    String expectedValue = \"value\";\n\n    when(commandObjects.getEx(key, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedValue);\n\n    String result = jedis.getEx(key, params);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).getEx(key, params);\n  }\n\n  @Test\n  public void testGetExBinary() {\n    byte[] key = \"key\".getBytes();\n    GetExParams params = new GetExParams().ex(10);\n    byte[] expectedValue = \"value\".getBytes();\n\n    when(commandObjects.getEx(key, params)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedValue);\n\n    byte[] result = jedis.getEx(key, params);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).getEx(key, params);\n  }\n\n  @Test\n  public void testGetrange() {\n    String key = \"key\";\n    long startOffset = 0L;\n    long endOffset = 10L;\n    String expectedResponse = \"value\";\n\n    when(commandObjects.getrange(key, startOffset, endOffset)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.getrange(key, startOffset, endOffset);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).getrange(key, startOffset, endOffset);\n  }\n\n  @Test\n  public void testGetrangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long startOffset = 0L;\n    long endOffset = 10L;\n    byte[] expectedResponse = \"value\".getBytes();\n\n    when(commandObjects.getrange(key, startOffset, endOffset)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedResponse);\n\n    byte[] result = jedis.getrange(key, startOffset, endOffset);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).getrange(key, startOffset, endOffset);\n  }\n\n  @Test\n  public void testGetSet() {\n    String key = \"key\";\n    String value = \"newValue\";\n    String expectedPreviousValue = \"oldValue\";\n\n    when(commandObjects.getSet(key, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPreviousValue);\n\n    String result = jedis.getSet(key, value);\n\n    assertThat(result, equalTo(expectedPreviousValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).getSet(key, value);\n  }\n\n  @Test\n  public void testGetSetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"newValue\".getBytes();\n    byte[] expectedPreviousValue = \"oldValue\".getBytes();\n\n    when(commandObjects.getSet(key, value)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPreviousValue);\n\n    byte[] result = jedis.getSet(key, value);\n\n    assertThat(result, equalTo(expectedPreviousValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).getSet(key, value);\n  }\n\n  @Test\n  public void testIncr() {\n    String key = \"key\";\n    long expectedValue = 1L;\n\n    when(commandObjects.incr(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.incr(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).incr(key);\n  }\n\n  @Test\n  public void testIncrBinary() {\n    byte[] key = \"key\".getBytes();\n    long expectedValue = 1L; // Assuming the key was incremented successfully\n\n    when(commandObjects.incr(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.incr(key);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).incr(key);\n  }\n\n  @Test\n  public void testIncrBy() {\n    String key = \"key\";\n    long increment = 2L;\n    long expectedValue = 3L; // Assuming the key was incremented by 2 successfully\n\n    when(commandObjects.incrBy(key, increment)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.incrBy(key, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).incrBy(key, increment);\n  }\n\n  @Test\n  public void testIncrByBinary() {\n    byte[] key = \"key\".getBytes();\n    long increment = 2L;\n    long expectedValue = 3L; // Assuming the key was incremented by 2 successfully\n\n    when(commandObjects.incrBy(key, increment)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedValue);\n\n    long result = jedis.incrBy(key, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).incrBy(key, increment);\n  }\n\n  @Test\n  public void testIncrByFloat() {\n    String key = \"key\";\n    double increment = 2.5;\n    double expectedValue = 3.5; // Assuming the key was incremented by 2.5 successfully\n\n    when(commandObjects.incrByFloat(key, increment)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedValue);\n\n    double result = jedis.incrByFloat(key, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).incrByFloat(key, increment);\n  }\n\n  @Test\n  public void testIncrByFloatBinary() {\n    byte[] key = \"key\".getBytes();\n    double increment = 2.5;\n    double expectedValue = 3.5; // Assuming the key was incremented by 2.5 successfully\n\n    when(commandObjects.incrByFloat(key, increment)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedValue);\n\n    double result = jedis.incrByFloat(key, increment);\n\n    assertThat(result, equalTo(expectedValue));\n\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).incrByFloat(key, increment);\n  }\n\n  @Test\n  public void testLcs() {\n    String keyA = \"keyA\";\n    String keyB = \"keyB\";\n    LCSParams params = new LCSParams().withMatchLen();\n    LCSMatchResult expectedResult = new LCSMatchResult(5); // Assuming the LCS length is 5\n\n    when(commandObjects.lcs(keyA, keyB, params)).thenReturn(lcsMatchResultCommandObject);\n    when(commandExecutor.executeCommand(lcsMatchResultCommandObject)).thenReturn(expectedResult);\n\n    LCSMatchResult result = jedis.lcs(keyA, keyB, params);\n\n    assertThat(result, equalTo(expectedResult));\n\n    verify(commandExecutor).executeCommand(lcsMatchResultCommandObject);\n    verify(commandObjects).lcs(keyA, keyB, params);\n  }\n\n  @Test\n  public void testLcsBinary() {\n    byte[] keyA = \"keyA\".getBytes();\n    byte[] keyB = \"keyB\".getBytes();\n    LCSParams params = new LCSParams().withMatchLen();\n    LCSMatchResult expectedResult = new LCSMatchResult(5); // Assuming the LCS length is 5\n\n    when(commandObjects.lcs(keyA, keyB, params)).thenReturn(lcsMatchResultCommandObject);\n    when(commandExecutor.executeCommand(lcsMatchResultCommandObject)).thenReturn(expectedResult);\n\n    LCSMatchResult result = jedis.lcs(keyA, keyB, params);\n\n    assertThat(result, equalTo(expectedResult));\n\n    verify(commandExecutor).executeCommand(lcsMatchResultCommandObject);\n    verify(commandObjects).lcs(keyA, keyB, params);\n  }\n\n  @Test\n  public void testMget() {\n    String[] keys = { \"key1\", \"key2\", \"key3\" };\n    List<String> expectedValues = Arrays.asList(\"value1\", \"value2\", \"value3\");\n\n    when(commandObjects.mget(keys)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedValues);\n\n    List<String> result = jedis.mget(keys);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).mget(keys);\n  }\n\n  @Test\n  public void testMgetBinary() {\n    byte[][] keys = { \"key1\".getBytes(), \"key2\".getBytes(), \"key3\".getBytes() };\n    List<byte[]> expectedValues = Arrays.asList(\"value1\".getBytes(), \"value2\".getBytes(), \"value3\".getBytes());\n\n    when(commandObjects.mget(keys)).thenReturn(listBytesCommandObject);\n    when(commandExecutor.executeCommand(listBytesCommandObject)).thenReturn(expectedValues);\n\n    List<byte[]> result = jedis.mget(keys);\n\n    assertThat(result, equalTo(expectedValues));\n\n    verify(commandExecutor).executeCommand(listBytesCommandObject);\n    verify(commandObjects).mget(keys);\n  }\n\n  @Test\n  public void testMset() {\n    String[] keysvalues = { \"key1\", \"value1\", \"key2\", \"value2\" };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.mset(keysvalues)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.mset(keysvalues);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).mset(keysvalues);\n  }\n\n  @Test\n  public void testMsetBinary() {\n    byte[][] keysvalues = { \"key1\".getBytes(), \"value1\".getBytes(), \"key2\".getBytes(), \"value2\".getBytes() };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.mset(keysvalues)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.mset(keysvalues);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).mset(keysvalues);\n  }\n\n  @Test\n  public void testMsetnx() {\n    String[] keysvalues = { \"key1\", \"value1\", \"key2\", \"value2\" };\n    long expectedResponse = 1L; // Assuming the keys were set successfully\n\n    when(commandObjects.msetnx(keysvalues)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.msetnx(keysvalues);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).msetnx(keysvalues);\n  }\n\n  @Test\n  public void testMsetnxBinary() {\n    byte[][] keysvalues = { \"key1\".getBytes(), \"value1\".getBytes(), \"key2\".getBytes(), \"value2\".getBytes() };\n    long expectedResponse = 1L; // Assuming the keys were set successfully\n\n    when(commandObjects.msetnx(keysvalues)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.msetnx(keysvalues);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).msetnx(keysvalues);\n  }\n\n\n  @Test\n  public void testMsetexDelegates() {\n    MSetExParams params = new MSetExParams().nx().ex(5);\n    String[] keysvalues = { \"k1\", \"v1\", \"k2\", \"v2\" };\n\n    when(commandObjects.msetex(params, keysvalues)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(true);\n\n    boolean result = jedis.msetex(params, keysvalues);\n\n    assertThat(result, equalTo(true));\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).msetex(params, keysvalues);\n  }\n\n  @Test\n  public void testMsetexBinaryDelegates() {\n    MSetExParams params = new MSetExParams().xx().keepTtl();\n    byte[][] keysvalues = { \"k1\".getBytes(), \"v1\".getBytes(), \"k2\".getBytes(), \"v2\".getBytes() };\n\n    when(commandObjects.msetex(params, keysvalues)).thenReturn(booleanCommandObject);\n    when(commandExecutor.executeCommand(booleanCommandObject)).thenReturn(false);\n\n    boolean result = jedis.msetex(params, keysvalues);\n\n    assertThat(result, equalTo(false));\n    verify(commandExecutor).executeCommand(booleanCommandObject);\n    verify(commandObjects).msetex(params, keysvalues);\n  }\n\n  @Test\n  public void testPsetex() {\n    String key = \"key\";\n    long milliseconds = 1000L;\n    String value = \"value\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.psetex(key, milliseconds, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.psetex(key, milliseconds, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).psetex(key, milliseconds, value);\n  }\n\n  @Test\n  public void testPsetexBinary() {\n    byte[] key = \"key\".getBytes();\n    long milliseconds = 1000L;\n    byte[] value = \"value\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.psetex(key, milliseconds, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.psetex(key, milliseconds, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).psetex(key, milliseconds, value);\n  }\n\n  @Test\n  public void testSet() {\n    String key = \"key\";\n    String value = \"value\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.set(key, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.set(key, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).set(key, value);\n  }\n\n  @Test\n  public void testSetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.set(key, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.set(key, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).set(key, value);\n  }\n\n  @Test\n  public void testSetWithParams() {\n    String key = \"key\";\n    String value = \"value\";\n    SetParams params = new SetParams().nx().ex(10);\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.set(key, value, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.set(key, value, params);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).set(key, value, params);\n  }\n\n  @Test\n  public void testSetWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    SetParams params = new SetParams().nx().ex(10);\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.set(key, value, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.set(key, value, params);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).set(key, value, params);\n  }\n\n  @Test\n  public void testSetGet() {\n    String key = \"key\";\n    String value = \"value\";\n    String expectedPreviousValue = \"previousValue\";\n\n    when(commandObjects.setGet(key, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPreviousValue);\n\n    String result = jedis.setGet(key, value);\n\n    assertThat(result, equalTo(expectedPreviousValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).setGet(key, value);\n  }\n\n  @Test\n  public void testSetGetBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    byte[] expectedPreviousValue = \"previousValue\".getBytes();\n\n    when(commandObjects.setGet(key, value)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPreviousValue);\n\n    byte[] result = jedis.setGet(key, value);\n\n    assertThat(result, equalTo(expectedPreviousValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).setGet(key, value);\n  }\n\n  @Test\n  public void testSetGetWithParams() {\n    String key = \"key\";\n    String value = \"value\";\n    SetParams params = new SetParams().nx().ex(10);\n    String expectedPreviousValue = \"previousValue\";\n\n    when(commandObjects.setGet(key, value, params)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedPreviousValue);\n\n    String result = jedis.setGet(key, value, params);\n\n    assertThat(result, equalTo(expectedPreviousValue));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).setGet(key, value, params);\n  }\n\n  @Test\n  public void testSetGetWithParamsBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    SetParams params = new SetParams().nx().ex(10);\n    byte[] expectedPreviousValue = \"previousValue\".getBytes();\n\n    when(commandObjects.setGet(key, value, params)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedPreviousValue);\n\n    byte[] result = jedis.setGet(key, value, params);\n\n    assertThat(result, equalTo(expectedPreviousValue));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).setGet(key, value, params);\n  }\n\n  @Test\n  public void testSetex() {\n    String key = \"key\";\n    long seconds = 60L;\n    String value = \"value\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.setex(key, seconds, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.setex(key, seconds, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).setex(key, seconds, value);\n  }\n\n  @Test\n  public void testSetexBinary() {\n    byte[] key = \"key\".getBytes();\n    long seconds = 60L;\n    byte[] value = \"value\".getBytes();\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.setex(key, seconds, value)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.setex(key, seconds, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).setex(key, seconds, value);\n  }\n\n  @Test\n  public void testSetnx() {\n    String key = \"key\";\n    String value = \"value\";\n    long expectedResponse = 1L; // Assuming the key was set successfully\n\n    when(commandObjects.setnx(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.setnx(key, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).setnx(key, value);\n  }\n\n  @Test\n  public void testSetnxBinary() {\n    byte[] key = \"key\".getBytes();\n    byte[] value = \"value\".getBytes();\n    long expectedResponse = 1L; // Assuming the key was set successfully\n\n    when(commandObjects.setnx(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.setnx(key, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).setnx(key, value);\n  }\n\n  @Test\n  public void testSetrange() {\n    String key = \"key\";\n    long offset = 10L;\n    String value = \"value\";\n    long expectedResponse = value.length();\n\n    when(commandObjects.setrange(key, offset, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.setrange(key, offset, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).setrange(key, offset, value);\n  }\n\n  @Test\n  public void testSetrangeBinary() {\n    byte[] key = \"key\".getBytes();\n    long offset = 10L;\n    byte[] value = \"value\".getBytes();\n    long expectedResponse = value.length;\n\n    when(commandObjects.setrange(key, offset, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.setrange(key, offset, value);\n\n    assertThat(result, equalTo(expectedResponse));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).setrange(key, offset, value);\n  }\n\n  @Test\n  public void testStrlen() {\n    String key = \"key\";\n    long expectedLength = 5L; // Assuming the length of the string value is 5\n\n    when(commandObjects.strlen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.strlen(key);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).strlen(key);\n  }\n\n  @Test\n  public void testStrlenBinary() {\n    byte[] key = \"key\".getBytes();\n    long expectedLength = 5L; // Assuming the length of the string value is 5\n\n    when(commandObjects.strlen(key)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedLength);\n\n    long result = jedis.strlen(key);\n\n    assertThat(result, equalTo(expectedLength));\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).strlen(key);\n  }\n\n  @Test\n  public void testSubstr() {\n    String key = \"key\";\n    int start = 0;\n    int end = 3;\n    String expectedSubstring = \"valu\";\n\n    when(commandObjects.substr(key, start, end)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedSubstring);\n\n    String result = jedis.substr(key, start, end);\n\n    assertThat(result, equalTo(expectedSubstring));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).substr(key, start, end);\n  }\n\n  @Test\n  public void testSubstrBinary() {\n    byte[] key = \"key\".getBytes();\n    int start = 0;\n    int end = 3;\n    byte[] expectedSubstring = \"valu\".getBytes();\n\n    when(commandObjects.substr(key, start, end)).thenReturn(bytesCommandObject);\n    when(commandExecutor.executeCommand(bytesCommandObject)).thenReturn(expectedSubstring);\n\n    byte[] result = jedis.substr(key, start, end);\n\n    assertThat(result, equalTo(expectedSubstring));\n\n    verify(commandExecutor).executeCommand(bytesCommandObject);\n    verify(commandObjects).substr(key, start, end);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTDigestCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.bloom.TDigestMergeParams;\n\npublic class UnifiedJedisTDigestCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testTdigestAdd() {\n    String key = \"testTDigest\";\n    double[] values = { 1.0, 2.0, 3.0 };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tdigestAdd(key, values)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tdigestAdd(key, values);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tdigestAdd(key, values);\n  }\n\n  @Test\n  public void testTdigestByRank() {\n    String key = \"testTDigest\";\n    long[] ranks = { 1, 2 };\n    List<Double> expectedResponse = Arrays.asList(0.1, 0.2);\n\n    when(commandObjects.tdigestByRank(key, ranks)).thenReturn(listDoubleCommandObject);\n    when(commandExecutor.executeCommand(listDoubleCommandObject)).thenReturn(expectedResponse);\n\n    List<Double> result = jedis.tdigestByRank(key, ranks);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(listDoubleCommandObject);\n    verify(commandObjects).tdigestByRank(key, ranks);\n  }\n\n  @Test\n  public void testTdigestByRevRank() {\n    String key = \"testTDigest\";\n    long[] ranks = { 1, 2 };\n    List<Double> expectedResponse = Arrays.asList(9.9, 9.8);\n\n    when(commandObjects.tdigestByRevRank(key, ranks)).thenReturn(listDoubleCommandObject);\n    when(commandExecutor.executeCommand(listDoubleCommandObject)).thenReturn(expectedResponse);\n\n    List<Double> result = jedis.tdigestByRevRank(key, ranks);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(listDoubleCommandObject);\n    verify(commandObjects).tdigestByRevRank(key, ranks);\n  }\n\n  @Test\n  public void testTdigestCDF() {\n    String key = \"testTDigest\";\n    double[] values = { 0.5, 0.9 };\n    List<Double> expectedResponse = Arrays.asList(0.1, 0.95);\n\n    when(commandObjects.tdigestCDF(key, values)).thenReturn(listDoubleCommandObject);\n    when(commandExecutor.executeCommand(listDoubleCommandObject)).thenReturn(expectedResponse);\n\n    List<Double> result = jedis.tdigestCDF(key, values);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(listDoubleCommandObject);\n    verify(commandObjects).tdigestCDF(key, values);\n  }\n\n  @Test\n  public void testTdigestCreate() {\n    String key = \"testTDigest\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tdigestCreate(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tdigestCreate(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tdigestCreate(key);\n  }\n\n  @Test\n  public void testTdigestCreateWithCompression() {\n    String key = \"testTDigest\";\n    int compression = 100;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tdigestCreate(key, compression)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tdigestCreate(key, compression);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tdigestCreate(key, compression);\n  }\n\n  @Test\n  public void testTdigestInfo() {\n    String key = \"testTDigest\";\n    Map<String, Object> expectedResponse = new HashMap<>();\n    expectedResponse.put(\"compression\", 100);\n    expectedResponse.put(\"capacity\", 1000);\n    expectedResponse.put(\"merged_nodes\", 500);\n    expectedResponse.put(\"unmerged_nodes\", 50);\n    expectedResponse.put(\"total_compressions\", 10);\n\n    when(commandObjects.tdigestInfo(key)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.tdigestInfo(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).tdigestInfo(key);\n  }\n\n  @Test\n  public void testTdigestMax() {\n    String key = \"testTDigest\";\n    double expectedResponse = 10.0;\n\n    when(commandObjects.tdigestMax(key)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedResponse);\n\n    double result = jedis.tdigestMax(key);\n\n    assertThat(result, equalTo(expectedResponse));\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).tdigestMax(key);\n  }\n\n  @Test\n  public void testTdigestMerge() {\n    String destinationKey = \"destTDigest\";\n    String[] sourceKeys = { \"sourceTDigest1\", \"sourceTDigest2\" };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tdigestMerge(destinationKey, sourceKeys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tdigestMerge(destinationKey, sourceKeys);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tdigestMerge(destinationKey, sourceKeys);\n  }\n\n  @Test\n  public void testTdigestMergeWithParams() {\n    TDigestMergeParams mergeParams = new TDigestMergeParams().compression(200);\n    String destinationKey = \"destTDigest\";\n    String[] sourceKeys = { \"sourceTDigest1\", \"sourceTDigest2\" };\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tdigestMerge(mergeParams, destinationKey, sourceKeys)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tdigestMerge(mergeParams, destinationKey, sourceKeys);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tdigestMerge(mergeParams, destinationKey, sourceKeys);\n  }\n\n  @Test\n  public void testTdigestMin() {\n    String key = \"testTDigest\";\n    double expectedResponse = 0.1;\n\n    when(commandObjects.tdigestMin(key)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedResponse);\n\n    double result = jedis.tdigestMin(key);\n\n    assertThat(result, equalTo(expectedResponse));\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).tdigestMin(key);\n  }\n\n  @Test\n  public void testTdigestQuantile() {\n    String key = \"testTDigest\";\n    double[] quantiles = { 0.1, 0.5, 0.9 };\n    List<Double> expectedResponse = Arrays.asList(1.0, 2.0, 3.0);\n\n    when(commandObjects.tdigestQuantile(key, quantiles)).thenReturn(listDoubleCommandObject);\n    when(commandExecutor.executeCommand(listDoubleCommandObject)).thenReturn(expectedResponse);\n\n    List<Double> result = jedis.tdigestQuantile(key, quantiles);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(listDoubleCommandObject);\n    verify(commandObjects).tdigestQuantile(key, quantiles);\n  }\n\n  @Test\n  public void testTdigestRank() {\n    String key = \"testTDigest\";\n    double[] values = { 1.0, 2.0 };\n    List<Long> expectedResponse = Arrays.asList(10L, 20L);\n\n    when(commandObjects.tdigestRank(key, values)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.tdigestRank(key, values);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).tdigestRank(key, values);\n  }\n\n  @Test\n  public void testTdigestReset() {\n    String key = \"testTDigest\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tdigestReset(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tdigestReset(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tdigestReset(key);\n  }\n\n  @Test\n  public void testTdigestRevRank() {\n    String key = \"testTDigest\";\n    double[] values = { 1.0, 2.0 };\n    List<Long> expectedResponse = Arrays.asList(90L, 80L);\n\n    when(commandObjects.tdigestRevRank(key, values)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.tdigestRevRank(key, values);\n\n    assertThat(result, sameInstance(expectedResponse));\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).tdigestRevRank(key, values);\n  }\n\n  @Test\n  public void testTdigestTrimmedMean() {\n    String key = \"testTDigest\";\n    double lowCutQuantile = 0.1;\n    double highCutQuantile = 0.9;\n    double expectedResponse = 5.0;\n\n    when(commandObjects.tdigestTrimmedMean(key, lowCutQuantile, highCutQuantile)).thenReturn(doubleCommandObject);\n    when(commandExecutor.executeCommand(doubleCommandObject)).thenReturn(expectedResponse);\n\n    double result = jedis.tdigestTrimmedMean(key, lowCutQuantile, highCutQuantile);\n\n    assertThat(result, equalTo(expectedResponse));\n    verify(commandExecutor).executeCommand(doubleCommandObject);\n    verify(commandObjects).tdigestTrimmedMean(key, lowCutQuantile, highCutQuantile);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.AbstractMap;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.timeseries.*;\n\npublic class UnifiedJedisTimeSeriesCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testTsAdd() {\n    String key = \"testKey\";\n    double value = 123.45;\n    long expectedResponse = 1582605077000L; // Timestamp of the added value\n\n    when(commandObjects.tsAdd(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsAdd(key, value);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsAdd(key, value);\n  }\n\n  @Test\n  public void testTsAddWithTimestamp() {\n    String key = \"testKey\";\n    long timestamp = 1582605077000L;\n    double value = 123.45;\n    long expectedResponse = timestamp; // Timestamp of the added value\n\n    when(commandObjects.tsAdd(key, timestamp, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsAdd(key, timestamp, value);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsAdd(key, timestamp, value);\n  }\n\n  @Test\n  public void testTsAddWithTimestampAndParams() {\n    String key = \"testKey\";\n    long timestamp = 1582605077000L;\n    double value = 123.45;\n    TSCreateParams createParams = new TSCreateParams().retention(86400000L); // 1 day retention\n    long expectedResponse = timestamp; // Timestamp of the added value\n\n    when(commandObjects.tsAdd(key, timestamp, value, createParams)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsAdd(key, timestamp, value, createParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsAdd(key, timestamp, value, createParams);\n  }\n\n  @Test\n  public void testTsAddWithParams() {\n    String key = \"testKey\";\n    long timestamp = 1582605077000L;\n    double value = 123.45;\n    TSAddParams createParams = mock(TSAddParams.class);\n    long expectedResponse = timestamp; // Timestamp of the added value\n\n    when(commandObjects.tsAdd(key, timestamp, value, createParams)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsAdd(key, timestamp, value, createParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsAdd(key, timestamp, value, createParams);\n  }\n\n  @Test\n  public void testTsAlter() {\n    String key = \"testKey\";\n    TSAlterParams alterParams = new TSAlterParams().retention(86400000L); // 1 day retention\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tsAlter(key, alterParams)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tsAlter(key, alterParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tsAlter(key, alterParams);\n  }\n\n  @Test\n  public void testTsCreate() {\n    String key = \"testKey\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tsCreate(key)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tsCreate(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tsCreate(key);\n  }\n\n  @Test\n  public void testTsCreateWithParams() {\n    String key = \"testKey\";\n    TSCreateParams createParams = new TSCreateParams().retention(86400000L); // 1 day retention\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tsCreate(key, createParams)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tsCreate(key, createParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tsCreate(key, createParams);\n  }\n\n  @Test\n  public void testTsCreateRule() {\n    String sourceKey = \"sourceKey\";\n    String destKey = \"destKey\";\n    AggregationType aggregationType = AggregationType.AVG;\n    long timeBucket = 60000L; // 1 minute\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, timeBucket)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tsCreateRule(sourceKey, destKey, aggregationType, timeBucket);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tsCreateRule(sourceKey, destKey, aggregationType, timeBucket);\n  }\n\n  @Test\n  public void testTsCreateRuleWithAlignTimestamp() {\n    String sourceKey = \"sourceKey\";\n    String destKey = \"destKey\";\n    AggregationType aggregationType = AggregationType.AVG;\n    long bucketDuration = 60000L; // 1 minute\n    long alignTimestamp = 1582600000000L;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp);\n  }\n\n  @Test\n  public void testTsDecrBy() {\n    String key = \"testKey\";\n    double value = 1.5;\n    long expectedResponse = -1L; // Assuming the decrement results in a total of -1\n\n    when(commandObjects.tsDecrBy(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsDecrBy(key, value);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsDecrBy(key, value);\n  }\n\n  @Test\n  public void testTsDecrByWithTimestamp() {\n    String key = \"testKey\";\n    double value = 1.5;\n    long timestamp = 1582605077000L;\n    long expectedResponse = 5L;\n\n    when(commandObjects.tsDecrBy(key, value, timestamp)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsDecrBy(key, value, timestamp);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsDecrBy(key, value, timestamp);\n  }\n\n  @Test\n  public void testTsDecrByWithParams() {\n    String key = \"testKey\";\n    double value = 1.5;\n    TSDecrByParams decrByParams = mock(TSDecrByParams.class);\n    long expectedResponse = 5L;\n\n    when(commandObjects.tsDecrBy(key, value, decrByParams)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsDecrBy(key, value, decrByParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsDecrBy(key, value, decrByParams);\n  }\n\n  @Test\n  public void testTsDel() {\n    String key = \"testKey\";\n    long fromTimestamp = 1582605077000L;\n    long toTimestamp = 1582605079000L;\n    long expectedResponse = 2L; // Number of deleted entries\n\n    when(commandObjects.tsDel(key, fromTimestamp, toTimestamp)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsDel(key, fromTimestamp, toTimestamp);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsDel(key, fromTimestamp, toTimestamp);\n  }\n\n  @Test\n  public void testTsDeleteRule() {\n    String sourceKey = \"sourceKey\";\n    String destKey = \"destKey\";\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.tsDeleteRule(sourceKey, destKey)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.tsDeleteRule(sourceKey, destKey);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).tsDeleteRule(sourceKey, destKey);\n  }\n\n  @Test\n  public void testTsGet() {\n    String key = \"testKey\";\n    TSElement expectedResponse = new TSElement(1582605077000L, 123.45);\n\n    when(commandObjects.tsGet(key)).thenReturn(tsElementCommandObject);\n    when(commandExecutor.executeCommand(tsElementCommandObject)).thenReturn(expectedResponse);\n\n    TSElement result = jedis.tsGet(key);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(tsElementCommandObject);\n    verify(commandObjects).tsGet(key);\n  }\n\n  @Test\n  public void testTsGetWithParams() {\n    String key = \"testKey\";\n    TSGetParams getParams = new TSGetParams().latest();\n    TSElement expectedResponse = new TSElement(1582605077000L, 123.45);\n\n    when(commandObjects.tsGet(key, getParams)).thenReturn(tsElementCommandObject);\n    when(commandExecutor.executeCommand(tsElementCommandObject)).thenReturn(expectedResponse);\n\n    TSElement result = jedis.tsGet(key, getParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(tsElementCommandObject);\n    verify(commandObjects).tsGet(key, getParams);\n  }\n\n  @Test\n  public void testTsIncrBy() {\n    String key = \"testKey\";\n    double value = 2.5;\n    long expectedResponse = 5L; // Assuming the increment results in a total of 5\n\n    when(commandObjects.tsIncrBy(key, value)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsIncrBy(key, value);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsIncrBy(key, value);\n  }\n\n  @Test\n  public void testTsIncrByWithTimestamp() {\n    String key = \"testKey\";\n    double value = 2.5;\n    long timestamp = 1582605077000L;\n    long expectedResponse = 5L;\n\n    when(commandObjects.tsIncrBy(key, value, timestamp)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsIncrBy(key, value, timestamp);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsIncrBy(key, value, timestamp);\n  }\n\n  @Test\n  public void testTsIncrByWithParams() {\n    String key = \"testKey\";\n    double value = 2.5;\n    TSIncrByParams incrByParams = mock(TSIncrByParams.class);\n    long expectedResponse = 5L;\n\n    when(commandObjects.tsIncrBy(key, value, incrByParams)).thenReturn(longCommandObject);\n    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);\n\n    long result = jedis.tsIncrBy(key, value, incrByParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(longCommandObject);\n    verify(commandObjects).tsIncrBy(key, value, incrByParams);\n  }\n\n  @Test\n  public void testTsInfo() {\n    String key = \"testKey\";\n    TSInfo expectedResponse = mock(TSInfo.class);\n\n    when(commandObjects.tsInfo(key)).thenReturn(tsInfoCommandObject);\n    when(commandExecutor.executeCommand(tsInfoCommandObject)).thenReturn(expectedResponse);\n\n    TSInfo result = jedis.tsInfo(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(tsInfoCommandObject);\n    verify(commandObjects).tsInfo(key);\n  }\n\n  @Test\n  public void testTsInfoDebug() {\n    String key = \"testKey\";\n    TSInfo expectedResponse = mock(TSInfo.class);\n\n    when(commandObjects.tsInfoDebug(key)).thenReturn(tsInfoCommandObject);\n    when(commandExecutor.executeCommand(tsInfoCommandObject)).thenReturn(expectedResponse);\n\n    TSInfo result = jedis.tsInfoDebug(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(tsInfoCommandObject);\n    verify(commandObjects).tsInfoDebug(key);\n  }\n\n  @Test\n  public void testTsMAdd() {\n    Map.Entry<String, TSElement> entry1 = new AbstractMap.SimpleEntry<>(\"key1\", new TSElement(1582605077000L, 123.45));\n    Map.Entry<String, TSElement> entry2 = new AbstractMap.SimpleEntry<>(\"key2\", new TSElement(1582605078000L, 234.56));\n    List<Long> expectedResponse = Arrays.asList(1582605077000L, 1582605078000L); // Timestamps of the added values\n\n    when(commandObjects.tsMAdd(entry1, entry2)).thenReturn(listLongCommandObject);\n    when(commandExecutor.executeCommand(listLongCommandObject)).thenReturn(expectedResponse);\n\n    List<Long> result = jedis.tsMAdd(entry1, entry2);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listLongCommandObject);\n    verify(commandObjects).tsMAdd(entry1, entry2);\n  }\n\n  @Test\n  public void testTsMGet() {\n    TSMGetParams multiGetParams = new TSMGetParams().withLabels();\n    String[] filters = { \"sensor=temperature\" };\n    Map<String, TSMGetElement> expectedResponse = new HashMap<>();\n\n    when(commandObjects.tsMGet(multiGetParams, filters)).thenReturn(mapStringTsmGetElementCommandObject);\n    when(commandExecutor.executeCommand(mapStringTsmGetElementCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, TSMGetElement> result = jedis.tsMGet(multiGetParams, filters);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(mapStringTsmGetElementCommandObject);\n    verify(commandObjects).tsMGet(multiGetParams, filters);\n  }\n\n  @Test\n  public void testTsMRange() {\n    long fromTimestamp = 1582600000000L;\n    long toTimestamp = 1582605077000L;\n    String[] filters = { \"sensor=temperature\" };\n    Map<String, TSMRangeElements> expectedResponse = new HashMap<>();\n\n    when(commandObjects.tsMRange(fromTimestamp, toTimestamp, filters)).thenReturn(mapStringTsmRangeElementsCommandObject);\n    when(commandExecutor.executeCommand(mapStringTsmRangeElementsCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, TSMRangeElements> result = jedis.tsMRange(fromTimestamp, toTimestamp, filters);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(mapStringTsmRangeElementsCommandObject);\n    verify(commandObjects).tsMRange(fromTimestamp, toTimestamp, filters);\n  }\n\n  @Test\n  public void testTsMRangeWithParams() {\n    TSMRangeParams multiRangeParams = TSMRangeParams.multiRangeParams(1582600000000L, 1582605077000L).filter(\"sensor=temperature\");\n    Map<String, TSMRangeElements> expectedResponse = new HashMap<>();\n\n    when(commandObjects.tsMRange(multiRangeParams)).thenReturn(mapStringTsmRangeElementsCommandObject);\n    when(commandExecutor.executeCommand(mapStringTsmRangeElementsCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, TSMRangeElements> result = jedis.tsMRange(multiRangeParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(mapStringTsmRangeElementsCommandObject);\n    verify(commandObjects).tsMRange(multiRangeParams);\n  }\n\n  @Test\n  public void testTsMRevRange() {\n    long fromTimestamp = 1582600000000L;\n    long toTimestamp = 1582605077000L;\n    String[] filters = { \"sensor=temperature\" };\n    Map<String, TSMRangeElements> expectedResponse = new HashMap<>();\n\n    when(commandObjects.tsMRevRange(fromTimestamp, toTimestamp, filters)).thenReturn(mapStringTsmRangeElementsCommandObject);\n    when(commandExecutor.executeCommand(mapStringTsmRangeElementsCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, TSMRangeElements> result = jedis.tsMRevRange(fromTimestamp, toTimestamp, filters);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(mapStringTsmRangeElementsCommandObject);\n    verify(commandObjects).tsMRevRange(fromTimestamp, toTimestamp, filters);\n  }\n\n  @Test\n  public void testTsMRevRangeWithParams() {\n    TSMRangeParams multiRangeParams =\n        TSMRangeParams.multiRangeParams(1582600000000L, 1582605077000L).filter(\"sensor=temperature\");\n    Map<String, TSMRangeElements> expectedResponse = new HashMap<>();\n\n    when(commandObjects.tsMRevRange(multiRangeParams)).thenReturn(mapStringTsmRangeElementsCommandObject);\n    when(commandExecutor.executeCommand(mapStringTsmRangeElementsCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, TSMRangeElements> result = jedis.tsMRevRange(multiRangeParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(mapStringTsmRangeElementsCommandObject);\n    verify(commandObjects).tsMRevRange(multiRangeParams);\n  }\n\n  @Test\n  public void testTsQueryIndex() {\n    String[] filters = { \"sensor=temperature\", \"location=warehouse\" };\n    List<String> expectedResponse = Arrays.asList(\"series1\", \"series2\");\n\n    when(commandObjects.tsQueryIndex(filters)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.tsQueryIndex(filters);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).tsQueryIndex(filters);\n  }\n\n  @Test\n  public void testTsRange() {\n    String key = \"testKey\";\n    long fromTimestamp = 1582600000000L;\n    long toTimestamp = 1582605077000L;\n    List<TSElement> expectedResponse = Collections.singletonList(new TSElement(fromTimestamp, 123.45));\n\n    when(commandObjects.tsRange(key, fromTimestamp, toTimestamp)).thenReturn(listTsElementCommandObject);\n    when(commandExecutor.executeCommand(listTsElementCommandObject)).thenReturn(expectedResponse);\n\n    List<TSElement> result = jedis.tsRange(key, fromTimestamp, toTimestamp);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listTsElementCommandObject);\n    verify(commandObjects).tsRange(key, fromTimestamp, toTimestamp);\n  }\n\n  @Test\n  public void testTsRangeWithParams() {\n    String key = \"testKey\";\n    TSRangeParams rangeParams = TSRangeParams.rangeParams(1582600000000L, 1582605077000L);\n    List<TSElement> expectedResponse = Arrays.asList(\n        new TSElement(1582600000000L, 123.45),\n        new TSElement(1582605077000L, 234.56));\n\n    when(commandObjects.tsRange(key, rangeParams)).thenReturn(listTsElementCommandObject);\n    when(commandExecutor.executeCommand(listTsElementCommandObject)).thenReturn(expectedResponse);\n\n    List<TSElement> result = jedis.tsRange(key, rangeParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listTsElementCommandObject);\n    verify(commandObjects).tsRange(key, rangeParams);\n  }\n\n  @Test\n  public void testTsRevRange() {\n    String key = \"testKey\";\n    long fromTimestamp = 1582600000000L;\n    long toTimestamp = 1582605077000L;\n    List<TSElement> expectedResponse = Collections.singletonList(new TSElement(toTimestamp, 234.56));\n\n    when(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp)).thenReturn(listTsElementCommandObject);\n    when(commandExecutor.executeCommand(listTsElementCommandObject)).thenReturn(expectedResponse);\n\n    List<TSElement> result = jedis.tsRevRange(key, fromTimestamp, toTimestamp);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listTsElementCommandObject);\n    verify(commandObjects).tsRevRange(key, fromTimestamp, toTimestamp);\n  }\n\n  @Test\n  public void testTsRevRangeWithParams() {\n    String key = \"testKey\";\n    TSRangeParams rangeParams = TSRangeParams.rangeParams(1582600000000L, 1582605077000L);\n    List<TSElement> expectedResponse = Arrays.asList(\n        new TSElement(1582605077000L, 234.56),\n        new TSElement(1582600000000L, 123.45));\n\n    when(commandObjects.tsRevRange(key, rangeParams)).thenReturn(listTsElementCommandObject);\n    when(commandExecutor.executeCommand(listTsElementCommandObject)).thenReturn(expectedResponse);\n\n    List<TSElement> result = jedis.tsRevRange(key, rangeParams);\n\n    assertEquals(expectedResponse, result);\n\n    verify(commandExecutor).executeCommand(listTsElementCommandObject);\n    verify(commandObjects).tsRevRange(key, rangeParams);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTopKCommandsTest.java",
    "content": "package redis.clients.jedis.mocked.unified;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\npublic class UnifiedJedisTopKCommandsTest extends UnifiedJedisMockedTestBase {\n\n  @Test\n  public void testTopkAdd() {\n    String key = \"testTopK\";\n    String[] items = { \"item1\", \"item2\" };\n    List<String> expectedResponse = Arrays.asList(\"item3\", \"item4\");\n\n    when(commandObjects.topkAdd(key, items)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.topkAdd(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).topkAdd(key, items);\n  }\n\n  @Test\n  public void testTopkIncrBy() {\n    String key = \"testTopK\";\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"item1\", 1L);\n    itemIncrements.put(\"item2\", 2L);\n    List<String> expectedResponse = Arrays.asList(\"item3\", \"item4\");\n\n    when(commandObjects.topkIncrBy(key, itemIncrements)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.topkIncrBy(key, itemIncrements);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).topkIncrBy(key, itemIncrements);\n  }\n\n  @Test\n  public void testTopkInfo() {\n    String key = \"testTopK\";\n    Map<String, Object> expectedResponse = new HashMap<>();\n    expectedResponse.put(\"k\", 10L);\n    expectedResponse.put(\"width\", 50L);\n    expectedResponse.put(\"depth\", 5L);\n    expectedResponse.put(\"decay\", 0.9);\n\n    when(commandObjects.topkInfo(key)).thenReturn(mapStringObjectCommandObject);\n    when(commandExecutor.executeCommand(mapStringObjectCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Object> result = jedis.topkInfo(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringObjectCommandObject);\n    verify(commandObjects).topkInfo(key);\n  }\n\n  @Test\n  public void testTopkList() {\n    String key = \"testTopK\";\n    List<String> expectedResponse = Arrays.asList(\"item1\", \"item2\", \"item3\");\n\n    when(commandObjects.topkList(key)).thenReturn(listStringCommandObject);\n    when(commandExecutor.executeCommand(listStringCommandObject)).thenReturn(expectedResponse);\n\n    List<String> result = jedis.topkList(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listStringCommandObject);\n    verify(commandObjects).topkList(key);\n  }\n\n  @Test\n  public void testTopkListWithCount() {\n    String key = \"testTopK\";\n    Map<String, Long> expectedResponse = new HashMap<>();\n    expectedResponse.put(\"item1\", 1L);\n    expectedResponse.put(\"item2\", 2L);\n\n    when(commandObjects.topkListWithCount(key)).thenReturn(mapStringLongCommandObject);\n    when(commandExecutor.executeCommand(mapStringLongCommandObject)).thenReturn(expectedResponse);\n\n    Map<String, Long> result = jedis.topkListWithCount(key);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(mapStringLongCommandObject);\n    verify(commandObjects).topkListWithCount(key);\n  }\n\n  @Test\n  public void testTopkQuery() {\n    String key = \"testTopK\";\n    String[] items = { \"item1\", \"item2\" };\n    List<Boolean> expectedResponse = Arrays.asList(true, false);\n\n    when(commandObjects.topkQuery(key, items)).thenReturn(listBooleanCommandObject);\n    when(commandExecutor.executeCommand(listBooleanCommandObject)).thenReturn(expectedResponse);\n\n    List<Boolean> result = jedis.topkQuery(key, items);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(listBooleanCommandObject);\n    verify(commandObjects).topkQuery(key, items);\n  }\n\n  @Test\n  public void testTopkReserve() {\n    String key = \"testTopK\";\n    long topk = 10L;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.topkReserve(key, topk)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.topkReserve(key, topk);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).topkReserve(key, topk);\n  }\n\n  @Test\n  public void testTopkReserveWidth() {\n    String key = \"testTopK\";\n    long topk = 10L;\n    long width = 50L;\n    long depth = 5L;\n    double decay = 0.9;\n    String expectedResponse = \"OK\";\n\n    when(commandObjects.topkReserve(key, topk, width, depth, decay)).thenReturn(stringCommandObject);\n    when(commandExecutor.executeCommand(stringCommandObject)).thenReturn(expectedResponse);\n\n    String result = jedis.topkReserve(key, topk, width, depth, decay);\n\n    assertThat(result, sameInstance(expectedResponse));\n\n    verify(commandExecutor).executeCommand(stringCommandObject);\n    verify(commandObjects).topkReserve(key, topk, width, depth, decay);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/ConsolidatedAccessControlListCommandsTest.java",
    "content": "package redis.clients.jedis.modules;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport java.util.Locale;\nimport java.util.function.Consumer;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.bloom.RedisBloomProtocol.*;\nimport redis.clients.jedis.commands.ProtocolCommand;\nimport redis.clients.jedis.exceptions.JedisAccessControlException;\nimport redis.clients.jedis.json.JsonProtocol.JsonCommand;\nimport redis.clients.jedis.search.SearchProtocol.SearchCommand;\nimport redis.clients.jedis.search.schemafields.TextField;\nimport redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesCommand;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@SinceRedisVersion(value = \"7.9.0\")\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\n@ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\npublic class ConsolidatedAccessControlListCommandsTest extends RedisModuleCommandsTestBase {\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  public static final String USER_NAME = \"moduser\";\n  public static final String USER_PASSWORD = \"secret\";\n\n  public ConsolidatedAccessControlListCommandsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  @AfterEach\n  @Override\n  public void tearDown() throws Exception {\n    try {\n      jedis.aclDelUser(USER_NAME);\n    } catch (Exception e) { }\n    super.tearDown();\n  }\n\n  @Test\n  public void listACLCategoriesTest() {\n    assertThat(jedis.aclCat(),\n        Matchers.hasItems(\"bloom\", \"cuckoo\", \"cms\", \"topk\", \"tdigest\",\n            \"search\", \"timeseries\", \"json\"));\n  }\n\n  @Test\n  public void grantBloomCommandTest() {\n    grantModuleCommandTest(BloomFilterCommand.RESERVE, client -> client.bfReserve(\"foo\", 0.01, 10_000));\n  }\n\n  @Test\n  public void grantBloomCommandCatTest() {\n    grantModuleCommandCatTest(\"bloom\", BloomFilterCommand.RESERVE, client -> client.bfReserve(\"foo\", 0.01, 10_000));\n  }\n\n  @Test\n  public void grantCuckooCommandTest() {\n    grantModuleCommandTest(CuckooFilterCommand.RESERVE, client -> client.cfReserve(\"foo\", 10_000));\n  }\n\n  @Test\n  public void grantCuckooCommandCatTest() {\n    grantModuleCommandCatTest(\"cuckoo\", CuckooFilterCommand.RESERVE, client -> client.cfReserve(\"foo\", 10_000));\n  }\n\n  @Test\n  public void grantCmsCommandTest() {\n    grantModuleCommandTest(CountMinSketchCommand.INITBYDIM, client -> client.cmsInitByDim(\"foo\", 16, 4));\n  }\n\n  @Test\n  public void grantCmsCommandCatTest() {\n    grantModuleCommandCatTest(\"cms\", CountMinSketchCommand.INITBYDIM, client -> client.cmsInitByDim(\"foo\", 16, 4));\n  }\n\n  @Test\n  public void grantTopkCommandTest() {\n    grantModuleCommandTest(TopKCommand.RESERVE, client -> client.topkReserve(\"foo\", 1000));\n  }\n\n  @Test\n  public void grantTopkCommandCatTest() {\n    grantModuleCommandCatTest(\"topk\", TopKCommand.RESERVE, client -> client.topkReserve(\"foo\", 1000));\n  }\n\n  @Test\n  public void grantTdigestCommandTest() {\n    grantModuleCommandTest(TDigestCommand.CREATE, client -> client.tdigestCreate(\"foo\"));\n  }\n\n  @Test\n  public void grantTdigestCommandCatTest() {\n    grantModuleCommandCatTest(\"tdigest\", TDigestCommand.CREATE, client -> client.tdigestCreate(\"foo\"));\n  }\n\n  @Test\n  public void grantSearchCommandTest() {\n    grantModuleCommandTest(SearchCommand.CREATE,\n        client -> client.ftCreate(\"foo\", TextField.of(\"bar\")));\n  }\n\n  @Test\n  public void grantSearchCommandCatTest() {\n    grantModuleCommandCatTest(\"search\", SearchCommand.CREATE,\n        client -> client.ftCreate(\"foo\", TextField.of(\"bar\")));\n  }\n\n  @Test\n  public void grantTimeseriesCommandTest() {\n    grantModuleCommandTest(TimeSeriesCommand.CREATE, client -> client.tsCreate(\"foo\"));\n  }\n\n  @Test\n  public void grantTimeseriesCommandCatTest() {\n    grantModuleCommandCatTest(\"timeseries\", TimeSeriesCommand.CREATE, client -> client.tsCreate(\"foo\"));\n  }\n\n  @Test\n  public void grantJsonCommandTest() {\n    grantModuleCommandTest(JsonCommand.GET, client -> client.jsonGet(\"foo\"));\n  }\n\n  @Test\n  public void grantJsonCommandCatTest() {\n    grantModuleCommandCatTest(\"json\", JsonCommand.GET, client -> client.jsonGet(\"foo\"));\n  }\n\n  private void grantModuleCommandTest(ProtocolCommand command, Consumer<UnifiedJedis> operation) {\n    // create and enable an user with permission to all keys but no commands\n    jedis.aclSetUser(USER_NAME, \">\" + USER_PASSWORD, \"on\", \"~*\");\n\n    // client object with new user\n    try (UnifiedJedis client = RedisClient.builder().hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(\n            DefaultJedisClientConfig.builder().user(USER_NAME).password(USER_PASSWORD).build())\n        .build()) {\n\n      // user can't execute commands\n      JedisAccessControlException noperm = assertThrows(JedisAccessControlException.class,\n          () -> operation.accept(client), \"Should throw a NOPERM exception\");\n      assertThat(noperm.getMessage(), Matchers.oneOf(getNopermErrorMessage(false, command),\n          getNopermErrorMessage(true, command)));\n\n      // permit user to commands\n      jedis.aclSetUser(USER_NAME, \"+\" + SafeEncoder.encode(command.getRaw()));\n\n      // user can now execute commands\n      operation.accept(client);\n    }\n  }\n\n  private void grantModuleCommandCatTest(String category, ProtocolCommand command,\n      Consumer<UnifiedJedis> operation) {\n    // create and enable an user with permission to all keys but no commands\n    jedis.aclSetUser(USER_NAME, \">\" + USER_PASSWORD, \"on\", \"~*\");\n\n    // client object with new user\n    try (UnifiedJedis client = RedisClient.builder().hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(\n            DefaultJedisClientConfig.builder().user(USER_NAME).password(USER_PASSWORD).build())\n        .build()) {\n\n      // user can't execute category commands\n      JedisAccessControlException noperm = assertThrows(JedisAccessControlException.class,\n          () -> operation.accept(client), \"Should throw a NOPERM exception\");\n      assertThat(noperm.getMessage(), Matchers.oneOf(getNopermErrorMessage(false, command),\n          getNopermErrorMessage(true, command)));\n\n      // permit user to category commands\n      jedis.aclSetUser(USER_NAME, \"+@\" + category);\n\n      // user can now execute category commands\n      operation.accept(client);\n    }\n  }\n\n  private static String getNopermErrorMessage(boolean commandNameUpperCase, ProtocolCommand protocolCommand) {\n    String command = SafeEncoder.encode(protocolCommand.getRaw());\n    return String.format(\"NOPERM User %s has no permissions to run the '%s' command\",\n          USER_NAME, commandNameUpperCase ? command.toUpperCase(Locale.ENGLISH) : command.toLowerCase(Locale.ENGLISH));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/ConsolidatedConfigurationCommandsTest.java",
    "content": "package redis.clients.jedis.modules;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.aMapWithSize;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport java.util.Collections;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@SinceRedisVersion(value = \"7.9.0\")\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class ConsolidatedConfigurationCommandsTest extends RedisModuleCommandsTestBase {\n\n  public ConsolidatedConfigurationCommandsTest(RedisProtocol redisProtocol) {\n    super(redisProtocol);\n  }\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  @Test\n  public void setSearchConfigGloballyTest() {\n    final String configParam = \"search-default-dialect\";\n    // confirm default\n    assertEquals(Collections.singletonMap(configParam, \"1\"), jedis.configGet(configParam));\n\n    try {\n      assertEquals(\"OK\", jedis.configSet(configParam, \"2\"));\n      assertEquals(Collections.singletonMap(configParam, \"2\"), jedis.configGet(configParam));\n\n    } finally {\n      // restore to default\n      assertEquals(\"OK\", jedis.configSet(configParam, \"1\"));\n    }\n  }\n\n  @Test\n  public void setReadOnlySearchConfigTest() {\n    JedisDataException de = assertThrows(JedisDataException.class,\n        () -> jedis.configSet(\"search-max-doctablesize\", \"10\"));\n    assertThat(de.getMessage(), Matchers.not(Matchers.emptyOrNullString()));\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void getSearchConfigSettingTest() {\n    assertThat(jedis.configGet(\"search-timeout\"), aMapWithSize(1));\n  }\n\n  @Test\n  public void getTSConfigSettingTest() {\n    assertThat(jedis.configGet(\"ts-retention-policy\"), aMapWithSize(1));\n  }\n\n  @Test\n  public void getBFConfigSettingTest() {\n    assertThat(jedis.configGet(\"bf-error-rate\"), aMapWithSize(1));\n  }\n\n  @Test\n  public void getCFConfigSettingTest() {\n    assertThat(jedis.configGet(\"cf-initial-size\"), aMapWithSize(1));\n  }\n\n  @Test\n  public void getAllConfigSettings() {\n    assertThat(jedis.configGet(\"*\").size(), Matchers.greaterThan(0));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java",
    "content": "package redis.clients.jedis.modules;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.commands.CommandsTestsParameters;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n\n\n@Tag(\"integration\")\npublic abstract class RedisModuleCommandsTestBase {\n\n  protected static String preferredEndpointId = \"modules-docker\";\n\n  @RegisterExtension\n  public RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(preferredEndpointId));\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  protected static EndpointConfig endpoint;\n\n  /**\n   * Input data for parameterized tests. In principle all subclasses of this\n   * class should be parameterized tests, to run with several versions of RESP.\n   *\n   * @see CommandsTestsParameters#respVersions()\n   */\n  protected final RedisProtocol protocol;\n\n  protected Jedis jedis;\n  protected UnifiedJedis client;\n\n  /**\n   * The RESP protocol is to be injected by the subclasses, usually via JUnit\n   * parameterized tests, because most of the subclassed tests are meant to be\n   * executed against multiple RESP versions. For the special cases where a single\n   * RESP version is relevant, we still force the subclass to be explicit and\n   * call this constructor.\n   *\n   * @param protocol The RESP protocol to use during the tests.\n   */\n  public RedisModuleCommandsTestBase(RedisProtocol protocol) {\n    this.protocol = protocol;\n  }\n\n  // BeforeClass\n  public static void prepare() {\n    endpoint = Endpoints.getRedisEndpoint(preferredEndpointId);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    if (endpoint == null) {\n      return;\n    }\n    jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().protocol(protocol).timeoutMillis(500).build());\n    jedis.flushAll();\n    client = RedisClient.builder().hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(endpoint.getClientConfigBuilder().protocol(protocol).build()).build();\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    if (endpoint == null) {\n      return;\n    }\n    client.close();\n    jedis.close();\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/RedisModulesPipelineTest.java",
    "content": "package redis.clients.jedis.modules;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\nimport static redis.clients.jedis.json.Path.ROOT_PATH;\nimport static redis.clients.jedis.modules.json.JsonObjects.Baz;\nimport static redis.clients.jedis.modules.json.JsonObjects.IRLObject;\nimport static redis.clients.jedis.search.RediSearchUtil.toStringMap;\n\nimport com.google.gson.Gson;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Collections;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.Pipeline;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.Response;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.*;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisModulesPipelineTest extends RedisModuleCommandsTestBase {\n\n  private static final Gson gson = new Gson();\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public RedisModulesPipelineTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void search() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0).addTextField(\"body\", 1.0);\n    String index = \"testindex\";\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"body\", \"lorem ipsum\");\n\n//    Connection c = createConnection();\n//    Pipeline p = new Pipeline(c);\n    Pipeline p = (Pipeline) client.pipelined();\n\n    Response<String> create = p.ftCreate(index, IndexOptions.defaultOptions(), sc);\n    Response<String> alter = p.ftAlter(index, new Schema().addTextField(\"foo\", 1.0));\n    p.hset(\"doc1\", toStringMap(fields));\n    p.hset(\"doc2\", toStringMap(fields));\n    Response<SearchResult> searchResult = p.ftSearch(index, new Query(\"hello world\"));\n//    Response<SearchResult> searchBytesResult = p.ftSearch(index.getBytes(), new Query(\"hello world\")); // not RESP3 supported\n    Response<AggregationResult> aggregateResult = p.ftAggregate(index, new AggregationBuilder().groupBy(\"@title\"));\n    Response<String> explain = p.ftExplain(index, new Query(\"@title:title_val\"));\n    Response<List<String>> explainCLI = p.ftExplainCLI(index, new Query(\"@title:title_val\"));\n    Response<Map<String, Object>> info = p.ftInfo(index);\n//    // @org.junit.Ignore\n//    Response<String> configSet = p.ftConfigSet(\"timeout\", \"100\");\n//    Response<Map<String, Object>> configGet = p.ftConfigGet(\"*\");\n//    Response<String> configSetIndex = p.ftConfigSet(index, \"timeout\", \"100\");\n//    Response<Map<String, Object>> configGetIndex = p.ftConfigGet(index, \"*\");\n    Response<String> synUpdate = p.ftSynUpdate(index, \"foo\", \"bar\");\n    Response<Map<String, List<String>>> synDump = p.ftSynDump(index);\n\n    p.sync();\n//    c.close();\n\n    assertEquals(\"OK\", create.get());\n    assertEquals(\"OK\", alter.get());\n    assertEquals(\"OK\", alter.get());\n    assertEquals(2, searchResult.get().getTotalResults());\n//    assertEquals(2, searchBytesResult.get().getTotalResults());\n    assertEquals(1, aggregateResult.get().getTotalResults());\n    assertNotNull(explain.get());\n    assertNotNull(explainCLI.get().get(0));\n    assertEquals(index, info.get().get(\"index_name\"));\n//    // @org.junit.Ignore\n//    assertEquals(\"OK\", configSet.get());\n//    assertEquals(\"100\", configGet.get().get(\"TIMEOUT\"));\n//    assertEquals(\"OK\", configSetIndex.get());\n//    assertEquals(\"100\", configGetIndex.get().get(\"TIMEOUT\"));\n    assertEquals(\"OK\", synUpdate.get());\n    Map<String, List<String>> expected = new HashMap<>();\n    expected.put(\"bar\", Collections.singletonList(\"foo\"));\n    assertEquals(expected, synDump.get());\n  }\n\n  @Test\n  public void jsonV1() {\n    assumeFalse(protocol == RedisProtocol.RESP3);\n\n    Map<String, String> hm1 = new HashMap<>();\n    hm1.put(\"hello\", \"world\");\n    hm1.put(\"oh\", \"snap\");\n\n    Map<String, Object> hm2 = new HashMap<>();\n    hm2.put(\"array\", new String[]{\"a\", \"b\", \"c\"});\n    hm2.put(\"boolean\", true);\n    hm2.put(\"number\", 3);\n\n    Baz baz1 = new Baz(\"quuz1\", \"grault1\", \"waldo1\");\n    Baz baz2 = new Baz(\"quuz2\", \"grault2\", \"waldo2\");\n    Baz baz3 = new Baz(\"quuz3\", \"grault3\", \"waldo3\");\n\n//    Connection c = createConnection();\n//    Pipeline p = new Pipeline(c);\n    Pipeline p = (Pipeline) client.pipelined();\n\n    Response<String> set1 = p.jsonSet(\"foo\", Path.ROOT_PATH, hm1);\n    Response<Object> get = p.jsonGet(\"foo\");\n    Response<Map> getObject = p.jsonGet(\"foo\", Map.class);\n    Response<Object> getWithPath = p.jsonGet(\"foo\", Path.ROOT_PATH);\n    Response<Map> getObjectWithPath = p.jsonGet(\"foo\", Map.class, Path.ROOT_PATH);\n    Response<List<JSONArray>> mget = p.jsonMGet(\"foo\");\n    p.jsonSet(\"baz\", new JSONObject(gson.toJson(baz1)));\n    Response<List<Baz>> mgetClass = p.jsonMGet(Path.ROOT_PATH, Baz.class, \"baz\");\n    Response<Long> strLenPath = p.jsonStrLen(\"foo\", new Path(\"hello\"));\n    Response<Long> strAppPath = p.jsonStrAppend(\"foo\", new Path(\"hello\"), \"!\");\n    Response<Long> delPath = p.jsonDel(\"foo\", new Path(\"hello\"));\n    Response<Long> delKey = p.jsonDel(\"foo\");\n    Response<String> set2 = p.jsonSet(\"foo\", Path.ROOT_PATH, hm2, new JsonSetParams().nx());\n    Response<Object> popPath = p.jsonArrPop(\"foo\", new Path(\"array\"));\n    Response<Object> indexPop = p.jsonArrPop(\"foo\", new Path(\"array\"), 2);\n    Response<Long> append = p.jsonArrAppend(\"foo\", new Path(\"array\"), \"b\", \"c\", \"d\");\n    Response<Long> index = p.jsonArrIndex(\"foo\", new Path(\"array\"), \"c\");\n    Response<Long> insert = p.jsonArrInsert(\"foo\", new Path(\"array\"), 0, \"x\");\n    Response<Long> arrLenWithPath = p.jsonArrLen(\"foo\", new Path(\"array\"));\n    Response<Long> trim = p.jsonArrTrim(\"foo\", new Path(\"array\"), 1, 4);\n    Response<String> toggle = p.jsonToggle(\"foo\", new Path(\"boolean\"));\n    Response<Class<?>> type = p.jsonType(\"foo\", new Path(\"boolean\"));\n    Response<Class<?>> keyType = p.jsonType(\"foo\");\n    Response<Long> clearPath = p.jsonClear(\"foo\", new Path(\"boolean\"));\n    Response<Long> clearKey = p.jsonClear(\"foo\");\n    Response<String> set3 = p.jsonSet(\"foo\", Path.ROOT_PATH, \"newStr\");\n    Response<Long> strLen = p.jsonStrLen(\"foo\");\n    Response<Long> strApp = p.jsonStrAppend(\"foo\", \"?\");\n    Response<String> set4 = p.jsonSetWithEscape(\"obj\", new IRLObject());\n    p.jsonSet(\"arr\", ROOT_PATH, new int[]{ 0, 1, 2, 3 });\n    Response<Object> pop = p.jsonArrPop(\"arr\");\n    Response<Long> arrLen = p.jsonArrLen(\"arr\");\n    p.jsonSet(\"baz\", ROOT_PATH, new Baz[]{ baz1, baz2, baz3 });\n    Response<Baz> popClass = p.jsonArrPop(\"baz\", Baz.class);\n    Response<Baz> popClassWithPath = p.jsonArrPop(\"baz\", Baz.class, Path.ROOT_PATH);\n    Response<Baz> popClassWithIndex = p.jsonArrPop(\"baz\", Baz.class, Path.ROOT_PATH, 0);\n\n    p.sync();\n//    c.close();\n\n    assertEquals(\"OK\", set1.get());\n    assertEquals(hm1, get.get());\n    assertEquals(hm1, getObject.get());\n    assertEquals(hm1, getWithPath.get());\n    assertEquals(hm1, getObjectWithPath.get());\n    assertEquals(1, mget.get().size());\n    assertEquals(baz1, mgetClass.get().get(0));\n    assertEquals(Long.valueOf(5), strLenPath.get());\n    assertEquals(Long.valueOf(6), strAppPath.get());\n    assertEquals(Long.valueOf(1), delPath.get());\n    assertEquals(Long.valueOf(1), delKey.get());\n    assertEquals(\"OK\", set2.get());\n    assertEquals(\"c\", popPath.get());\n    assertEquals(\"b\", indexPop.get());\n    assertEquals(Long.valueOf(4), append.get());\n    assertEquals(Long.valueOf(2), index.get());\n    assertEquals(Long.valueOf(5), insert.get());\n    assertEquals(Long.valueOf(5), arrLenWithPath.get());\n    assertEquals(Long.valueOf(4), trim.get());\n    assertEquals(\"false\", toggle.get());\n    assertEquals(boolean.class, type.get());\n    assertEquals(Object.class, keyType.get());\n    assertEquals(Long.valueOf(0), clearPath.get());\n    assertEquals(Long.valueOf(1), clearKey.get());\n    assertEquals(\"OK\", set3.get());\n    assertEquals(Long.valueOf(6), strLen.get());\n    assertEquals(Long.valueOf(7), strApp.get());\n    assertEquals(\"OK\", set4.get());\n    assertEquals(3.0, pop.get());\n    assertEquals(Long.valueOf(3), arrLen.get());\n    assertEquals(baz3, popClass.get());\n    assertEquals(baz2, popClassWithPath.get());\n    assertEquals(baz1, popClassWithIndex.get());\n  }\n\n  @Test\n  public void jsonV2() {\n    Map<String, String> hm1 = new HashMap<>();\n    hm1.put(\"hello\", \"world\");\n    hm1.put(\"oh\", \"snap\");\n\n    Map<String, Object> hm2 = new HashMap<>();\n    hm2.put(\"array\", new String[]{\"a\", \"b\", \"c\"});\n    hm2.put(\"boolean\", true);\n    hm2.put(\"number\", 3);\n\n//    Connection c = createConnection();\n//    Pipeline p = new Pipeline(c);\n    Pipeline p = (Pipeline) client.pipelined();\n\n    Response<String> setWithEscape = p.jsonSetWithEscape(\"foo\", Path2.ROOT_PATH, hm1);\n    Response<Object> get = p.jsonGet(\"foo\",  Path2.ROOT_PATH);\n    Response<List<JSONArray>> mget = p.jsonMGet(Path2.ROOT_PATH, \"foo\");\n    Response<List<Long>> strLen = p.jsonStrLen(\"foo\", new Path2(\"hello\"));\n    Response<List<Long>> strApp = p.jsonStrAppend(\"foo\", new Path2(\"hello\"), \"!\");\n    Response<Long> del = p.jsonDel(\"foo\", new Path2(\"hello\"));\n    Response<String> set = p.jsonSet(\"bar\", Path2.ROOT_PATH, gson.toJson(\"strung\"));\n    Response<String> setWithParams = p.jsonSet(\"foo\", Path2.ROOT_PATH, gson.toJson(hm2), new JsonSetParams().xx());\n    Response<String> setWithEscapeWithParams = p.jsonSetWithEscape(\"foo\", Path2.ROOT_PATH, hm2, new JsonSetParams().xx());\n    Response<List<Object>> pop = p.jsonArrPop(\"foo\", new Path2(\"array\"));\n    Response<List<Object>> popWithIndex = p.jsonArrPop(\"foo\", new Path2(\"array\"), 2);\n    Response<List<Long>> append = p.jsonArrAppend(\"foo\", Path2.of(\"$.array\"), gson.toJson(\"b\"), gson.toJson(\"d\"));\n    Response<List<Long>> appendWithEscape = p.jsonArrAppendWithEscape(\"foo\", Path2.of(\"$.array\"), \"e\");\n    Response<List<Long>> index = p.jsonArrIndex(\"foo\", Path2.of(\"$.array\"), gson.toJson(\"b\"));\n    Response<List<Long>> indexWithEscape = p.jsonArrIndexWithEscape(\"foo\", Path2.of(\"$.array\"), \"b\");\n    Response<List<Long>> insert = p.jsonArrInsert(\"foo\", new Path2(\"array\"), 0, gson.toJson(\"x\"));\n    Response<List<Long>> insertWithEscape = p.jsonArrInsertWithEscape(\"foo\", new Path2(\"array\"), 0, \"x\");\n    Response<List<Long>> arrLen = p.jsonArrLen(\"foo\", new Path2(\"array\"));\n    Response<List<Long>> trim = p.jsonArrTrim(\"foo\", new Path2(\"array\"), 1, 2);\n    Response<List<Boolean>> toggle = p.jsonToggle(\"foo\", new Path2(\"boolean\"));\n    Response<List<Class<?>>> type = p.jsonType(\"foo\", new Path2(\"boolean\"));\n    Response<Long> clear = p.jsonClear(\"foo\", new Path2(\"array\"));\n\n    p.sync();\n//    c.close();\n\n    assertEquals(\"OK\", setWithEscape.get());\n    assertNotNull(get.get());\n    assertEquals(1, mget.get().size());\n    assertEquals(Long.valueOf(5), strLen.get().get(0));\n    assertEquals(1, strApp.get().size());\n    assertEquals(Long.valueOf(1), del.get());\n    assertEquals(\"OK\", set.get());\n    assertEquals(\"OK\", setWithParams.get());\n    assertEquals(\"OK\", setWithEscapeWithParams.get());\n    assertEquals(\"c\", pop.get().get(0));\n    assertEquals(\"b\", popWithIndex.get().get(0));\n    assertEquals(Long.valueOf(3), append.get().get(0));\n    assertEquals(Long.valueOf(4), appendWithEscape.get().get(0));\n    assertEquals(Long.valueOf(1), index.get().get(0));\n    assertEquals(Long.valueOf(1), indexWithEscape.get().get(0));\n    assertEquals(Long.valueOf(5), insert.get().get(0));\n    assertEquals(Long.valueOf(6), insertWithEscape.get().get(0));\n    assertEquals(Long.valueOf(6), arrLen.get().get(0));\n    assertEquals(Long.valueOf(2), trim.get().get(0));\n    assertEquals(false, toggle.get().get(0));\n    assertEquals(boolean.class, type.get().get(0));\n    assertEquals(Long.valueOf(1), clear.get());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/bloom/BloomTest.java",
    "content": "package redis.clients.jedis.modules.bloom;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.bloom.BFInsertParams;\nimport redis.clients.jedis.bloom.BFReserveParams;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class BloomTest extends RedisModuleCommandsTestBase {\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public BloomTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void reserveBasic() {\n    client.bfReserve(\"myBloom\", 0.001, 100L);\n    assertTrue(client.bfAdd(\"myBloom\", \"val1\"));\n    assertTrue(client.bfExists(\"myBloom\", \"val1\"));\n    assertFalse(client.bfExists(\"myBloom\", \"val2\"));\n  }\n\n  @Test\n  public void reserveValidateZeroCapacity() {\n    assertThrows(JedisDataException.class, () -> client.bfReserve(\"myBloom\", 0.001, 0));\n  }\n\n  @Test\n  public void reserveValidateZeroError() {\n    assertThrows(JedisDataException.class, () -> client.bfReserve(\"myBloom\", 0d, 100L));\n  }\n\n  @Test\n  public void reserveAlreadyExists() {\n    client.bfReserve(\"myBloom\", 0.1, 100);\n    assertThrows(JedisDataException.class, () -> client.bfReserve(\"myBloom\", 0.1, 100));\n  }\n\n  @Test\n  public void reserveV2() {\n    client.bfReserve(\"reserve-basic\", 0.001, 2);\n    assertEquals(Arrays.asList(true), client.bfInsert(\"reserve-basic\", \"a\"));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"reserve-basic\", \"b\"));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"reserve-basic\", \"c\"));\n  }\n\n  @Test\n  public void reserveEmptyParams() {\n    client.bfReserve(\"empty-param\", 0.001, 2, BFReserveParams.reserveParams());\n    assertEquals(Arrays.asList(true), client.bfInsert(\"empty-param\", \"a\"));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"empty-param\", \"b\"));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"empty-param\", \"c\"));\n  }\n\n  @Test\n  public void reserveNonScaling() {\n    client.bfReserve(\"nonscaling\", 0.001, 2, BFReserveParams.reserveParams().nonScaling());\n    assertEquals(Arrays.asList(true), client.bfInsert(\"nonscaling\", \"a\"));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"nonscaling\", \"b\"));\n    assertEquals(Arrays.asList((Boolean) null), client.bfInsert(\"nonscaling\", \"c\"));\n  }\n\n  @Test\n  public void reserveExpansion() {\n    // bf.reserve bfexpansion 0.001 1000 expansion 4\n    client.bfReserve(\"bfexpansion\", 0.001, 1000, BFReserveParams.reserveParams().expansion(4));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"bfexpansion\", \"a\"));\n    assertEquals(Arrays.asList(true), client.bfInsert(\"bfexpansion\",\n        BFInsertParams.insertParams().noCreate(), \"b\"));\n  }\n\n  @Test\n  public void addExistsString() {\n    assertTrue(client.bfAdd(\"newFilter\", \"foo\"));\n    assertTrue(client.bfExists(\"newFilter\", \"foo\"));\n    assertFalse(client.bfExists(\"newFilter\", \"bar\"));\n    assertFalse(client.bfAdd(\"newFilter\", \"foo\"));\n  }\n\n  @Test\n  public void testExistsNonExist() {\n    assertFalse(client.bfExists(\"nonExist\", \"foo\"));\n  }\n\n  @Test\n  public void addExistsMulti() {\n    List<Boolean> rv = client.bfMAdd(\"newFilter\", \"foo\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(true, true, true), rv);\n\n    rv = client.bfMAdd(\"newFilter\", \"newElem\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(true, false, false), rv);\n  }\n\n  @Test\n  public void testExample() {\n    // Simple bloom filter using default module settings\n    client.bfAdd(\"simpleBloom\", \"Mark\");\n    // Does \"Mark\" now exist?\n    client.bfExists(\"simpleBloom\", \"Mark\"); // true\n    client.bfExists(\"simpleBloom\", \"Farnsworth\"); // False\n\n    // If you have a long list of items to check/add, you can use the\n    // \"multi\" methods\n    client.bfMAdd(\"simpleBloom\", \"foo\", \"bar\", \"baz\", \"bat\", \"bag\");\n\n    // Check if they exist:\n    List<Boolean> rv = client.bfMExists(\"simpleBloom\", \"foo\", \"bar\", \"baz\", \"bat\", \"Mark\", \"nonexist\");\n    // All items except the last one will be 'true'\n    assertEquals(Arrays.asList(true, true, true, true, true, false), rv);\n\n    // Reserve a \"customized\" bloom filter\n    client.bfReserve(\"specialBloom\", 0.0001, 10000);\n    client.bfAdd(\"specialBloom\", \"foo\");\n  }\n\n  @Test\n  public void testInsert() {\n    client.bfInsert(\"b1\", new BFInsertParams().capacity(1L), \"1\");\n    assertTrue(client.bfExists(\"b1\", \"1\"));\n\n    // returning an error if the filter does not already exist\n    JedisDataException jde = assertThrows(JedisDataException.class,\n        () -> client.bfInsert(\"b2\", new BFInsertParams().noCreate(), \"1\"),\n        \"Should error if the filter does not already exist.\");\n    assertEquals(\"ERR not found\", jde.getMessage());\n\n    client.bfInsert(\"b3\", new BFInsertParams().capacity(1L).error(0.0001), \"2\");\n    assertTrue(client.bfExists(\"b3\", \"2\"));\n  }\n\n  @Test\n  public void issue49() {\n    BFInsertParams insertOptions = new BFInsertParams();\n    List<Boolean> insert = client.bfInsert(\"mykey\", insertOptions, \"a\", \"b\", \"c\");\n    assertEquals(3, insert.size());\n  }\n\n  @Test\n  public void card() {\n    client.bfInsert(\"test_card\", new BFInsertParams().capacity(1L), \"1\");\n    assertEquals(1L, client.bfCard(\"test_card\"));\n\n    // returning '0' if the filter does not already exist\n    assertEquals(0L, client.bfCard(\"not_exist\"));\n\n    // returning an error if the filter is not a bloom filter\n    client.set(\"foo\", \"bar\");\n    assertThrows(JedisDataException.class, () -> client.bfCard(\"foo\"),\n        \"Should error if the filter is not a bloom filter\");\n  }\n\n  @Test\n  public void info() {\n    client.bfInsert(\"test_info\", new BFInsertParams().capacity(1L), \"1\");\n    Map<String, Object> info = client.bfInfo(\"test_info\");\n    assertEquals(1L, info.get(\"Number of items inserted\"));\n\n    // returning an error if the filter does not already exist\n    JedisDataException jde = assertThrows(JedisDataException.class,\n        () -> client.bfInfo(\"not_exist\"), \"Should error if the filter does not already exist.\");\n    assertEquals(\"ERR not found\", jde.getMessage());\n  }\n\n  @Test\n  public void insertNonScaling() {\n    List<Boolean> insert = client.bfInsert(\"nonscaling_err\",\n        BFInsertParams.insertParams().capacity(4).nonScaling(), \"a\", \"b\", \"c\");\n    assertEquals(Arrays.asList(true, true, true), insert);\n\n    insert = client.bfInsert(\"nonscaling_err\", \"d\", \"e\");\n    assertEquals(Arrays.asList(true, null), insert);\n  }\n\n  @Test\n  public void insertExpansion() {\n    // BF.INSERT bfexpansion CAPACITY 3 expansion 3 ITEMS a b c d e f g h j k l o i u y t r e w q\n    List<Boolean> insert = client.bfInsert(\"bfexpansion\",\n        BFInsertParams.insertParams().capacity(3).expansion(3),\n        \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"j\", \"k\", \"l\",\n        \"o\", \"i\", \"u\", \"y\", \"t\", \"r\", \"e\", \"w\", \"q\");\n    assertEquals(20, insert.size());\n  }\n\n  @Test\n  @Timeout(2)\n  public void testScanDumpAndLoadChunk() {\n    client.bfAdd(\"bloom-dump\", \"a\");\n\n    long iterator = 0;\n    while (true) {\n      Map.Entry<Long, byte[]> chunkData = client.bfScanDump(\"bloom-dump\", iterator);\n      iterator = chunkData.getKey();\n      if (iterator == 0L) break;\n      assertEquals(\"OK\", client.bfLoadChunk(\"bloom-load\", iterator, chunkData.getValue()));\n    }\n\n    // check for properties\n    assertEquals(client.bfInfo(\"bloom-dump\"), client.bfInfo(\"bloom-load\"));\n    // check for existing items\n    assertTrue(client.bfExists(\"bloom-load\", \"a\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/bloom/CMSTest.java",
    "content": "package redis.clients.jedis.modules.bloom;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * Tests for the Count-Min-Sketch Implementation\n */\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class CMSTest extends RedisModuleCommandsTestBase {\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public CMSTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testInitByDim() {\n    client.cmsInitByDim(\"cms1\", 16L, 4L);\n    Map<String, Object> info = client.cmsInfo(\"cms1\");\n    assertEquals(16L, info.get(\"width\"));\n    assertEquals(4L, info.get(\"depth\"));\n    assertEquals(0L, info.get(\"count\"));\n  }\n\n  @Test\n  public void testInitByProb() {\n    client.cmsInitByProb(\"cms2\", 0.01, 0.01);\n    Map<String, Object> info = client.cmsInfo(\"cms2\");\n    assertEquals(200L, info.get(\"width\"));\n    assertEquals(7L, info.get(\"depth\"));\n    assertEquals(0L, info.get(\"count\"));\n  }\n\n  @Test\n  public void testKeyAlreadyExists() {\n    client.cmsInitByDim(\"dup\", 16L, 4L);\n    JedisException thrown = assertThrows(JedisException.class, () -> {\n      client.cmsInitByDim(\"dup\", 8L, 6L);\n    });\n    assertEquals(\"CMS: key already exists\", thrown.getMessage());\n  }\n\n  @Test\n  public void testIncrBy() {\n    client.cmsInitByDim(\"cms3\", 1000L, 5L);\n    long resp = client.cmsIncrBy(\"cms3\", \"foo\", 5L);\n    assertEquals(5L, resp);\n\n    Map<String, Object> info = client.cmsInfo(\"cms3\");\n    assertEquals(1000L, info.get(\"width\"));\n    assertEquals(5L, info.get(\"depth\"));\n    assertEquals(5L, info.get(\"count\"));\n  }\n\n  @Test\n  public void testIncrByMultipleArgs() {\n    client.cmsInitByDim(\"cms4\", 1000L, 5L);\n    client.cmsIncrBy(\"cms4\", \"foo\", 5L);\n\n    Map<String, Long> itemIncrements = new LinkedHashMap<>();\n    itemIncrements.put(\"foo\", 5L);\n    itemIncrements.put(\"bar\", 15L);\n\n    List<Long> resp = client.cmsIncrBy(\"cms4\", itemIncrements);\n//    assertArrayEquals(new Long[] { 15L, 10L }, resp.toArray(new Long[0]));\n    assertEquals(Arrays.asList(10L, 15L), resp);\n\n    Map<String, Object> info = client.cmsInfo(\"cms4\");\n    assertEquals(1000L, info.get(\"width\"));\n    assertEquals(5L, info.get(\"depth\"));\n    assertEquals(25L, info.get(\"count\"));\n  }\n\n  @Test\n  public void testQuery() {\n    client.cmsInitByDim(\"cms5\", 1000L, 5L);\n\n    Map<String, Long> itemIncrements = new HashMap<>();\n    itemIncrements.put(\"foo\", 10L);\n    itemIncrements.put(\"bar\", 15L);\n\n    client.cmsIncrBy(\"cms5\", itemIncrements);\n\n    List<Long> resp = client.cmsQuery(\"cms5\", \"foo\", \"bar\");\n    assertEquals(Arrays.asList(10L, 15L), resp);\n  }\n\n  @Test\n  public void testMerge() {\n    client.cmsInitByDim(\"A\", 1000L, 5L);\n    client.cmsInitByDim(\"B\", 1000L, 5L);\n    client.cmsInitByDim(\"C\", 1000L, 5L);\n\n    Map<String, Long> aValues = new HashMap<>();\n    aValues.put(\"foo\", 5L);\n    aValues.put(\"bar\", 3L);\n    aValues.put(\"baz\", 9L);\n\n    client.cmsIncrBy(\"A\", aValues);\n\n    Map<String, Long> bValues = new HashMap<>();\n    bValues.put(\"foo\", 2L);\n    bValues.put(\"bar\", 3L);\n    bValues.put(\"baz\", 1L);\n\n    client.cmsIncrBy(\"B\", bValues);\n\n    List<Long> q1 = client.cmsQuery(\"A\", \"foo\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(5L, 3L, 9L), q1);\n\n    List<Long> q2 = client.cmsQuery(\"B\", \"foo\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(2L, 3L, 1L), q2);\n\n    client.cmsMerge(\"C\", \"A\", \"B\");\n\n    List<Long> q3 = client.cmsQuery(\"C\", \"foo\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(7L, 6L, 10L), q3);\n\n    Map<String, Long> keysAndWeights = new HashMap<>();\n    keysAndWeights.put(\"A\", 1L);\n    keysAndWeights.put(\"B\", 2L);\n\n    client.cmsMerge(\"C\", keysAndWeights);\n\n    List<Long> q4 = client.cmsQuery(\"C\", \"foo\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(9L, 9L, 11L), q4);\n\n    keysAndWeights.clear();\n    keysAndWeights.put(\"A\", 2L);\n    keysAndWeights.put(\"B\", 3L);\n\n    client.cmsMerge(\"C\", keysAndWeights);\n\n    List<Long> q5 = client.cmsQuery(\"C\", \"foo\", \"bar\", \"baz\");\n    assertEquals(Arrays.asList(16L, 15L, 21L), q5);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/bloom/CuckooTest.java",
    "content": "package redis.clients.jedis.modules.bloom;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.bloom.CFInsertParams;\nimport redis.clients.jedis.bloom.CFReserveParams;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\n/**\n * Tests for the Cuckoo Filter Implementation\n */\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class CuckooTest extends RedisModuleCommandsTestBase {\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public CuckooTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testReservationCapacityOnly() {\n    client.cfReserve(\"cuckoo1\", 10);\n\n    Map<String, Object> info = client.cfInfo(\"cuckoo1\");\n    assertEquals(8L, info.get(\"Number of buckets\"));\n    assertEquals(0L, info.get(\"Number of items inserted\"));\n    assertEquals(72L, info.get(\"Size\"));\n    assertEquals(1L, info.get(\"Expansion rate\"));\n    assertEquals(1L, info.get(\"Number of filters\"));\n    assertEquals(2L, info.get(\"Bucket size\"));\n    assertEquals(20L, info.get(\"Max iterations\"));\n    assertEquals(0L, info.get(\"Number of items deleted\"));\n  }\n\n  @Test\n  public void testReservationCapacityAndBucketSize() {\n    client.cfReserve(\"cuckoo2\", 200, CFReserveParams.reserveParams().bucketSize(10));\n\n    Map<String, Object> info = client.cfInfo(\"cuckoo2\");\n\n    assertEquals(32L, info.get(\"Number of buckets\"));\n    assertEquals(0L, info.get(\"Number of items inserted\"));\n    assertEquals(376L, info.get(\"Size\"));\n    assertEquals(1L, info.get(\"Expansion rate\"));\n    assertEquals(1L, info.get(\"Number of filters\"));\n    assertEquals(10L, info.get(\"Bucket size\"));\n    assertEquals(20L, info.get(\"Max iterations\"));\n    assertEquals(0L, info.get(\"Number of items deleted\"));\n  }\n\n  @Test\n  public void testReservationCapacityAndBucketSizeAndMaxIterations() {\n    client.cfReserve(\"cuckoo3\", 200, CFReserveParams.reserveParams()\n        .bucketSize(10).maxIterations(20));\n\n    Map<String, Object> info = client.cfInfo(\"cuckoo3\");\n\n    assertEquals(32L, info.get(\"Number of buckets\"));\n    assertEquals(0L, info.get(\"Number of items inserted\"));\n    assertEquals(376L, info.get(\"Size\"));\n    assertEquals(1L, info.get(\"Expansion rate\"));\n    assertEquals(1L, info.get(\"Number of filters\"));\n    assertEquals(10L, info.get(\"Bucket size\"));\n    assertEquals(20L, info.get(\"Max iterations\"));\n    assertEquals(0L, info.get(\"Number of items deleted\"));\n  }\n\n  @Test\n  public void testReservationAllParams() {\n    client.cfReserve(\"cuckoo4\", 200, CFReserveParams.reserveParams()\n        .bucketSize(10).expansion(4).maxIterations(20));\n\n    Map<String, Object> info = client.cfInfo(\"cuckoo4\");\n\n    assertEquals(32L, info.get(\"Number of buckets\"));\n    assertEquals(0L, info.get(\"Number of items inserted\"));\n    assertEquals(376L, info.get(\"Size\"));\n    assertEquals(4L, info.get(\"Expansion rate\"));\n    assertEquals(1L, info.get(\"Number of filters\"));\n    assertEquals(10L, info.get(\"Bucket size\"));\n    assertEquals(20L, info.get(\"Max iterations\"));\n    assertEquals(0L, info.get(\"Number of items deleted\"));\n  }\n\n  @Test\n  public void testAdd() {\n    client.cfReserve(\"cuckoo5\", 64000);\n    assertTrue(client.cfAdd(\"cuckoo5\", \"test\"));\n\n    Map<String, Object> info = client.cfInfo(\"cuckoo5\");\n    assertEquals(1L, info.get(\"Number of items inserted\"));\n  }\n\n  @Test\n  public void testAddNxItemDoesExist() {\n    client.cfReserve(\"cuckoo6\", 64000);\n    assertTrue(client.cfAddNx(\"cuckoo6\", \"filter\"));\n  }\n\n  @Test\n  public void testAddNxItemExists() {\n    client.cfReserve(\"cuckoo7\", 64000);\n    client.cfAdd(\"cuckoo7\", \"filter\");\n    assertFalse(client.cfAddNx(\"cuckoo7\", \"filter\"));\n  }\n\n  @Test\n  public void testInsert() {\n    assertEquals(Arrays.asList(true), client.cfInsert(\"cuckoo8\", \"foo\"));\n  }\n\n  @Test\n  public void testInsertWithCapacity() {\n    assertEquals(Arrays.asList(true), client.cfInsert(\"cuckoo9\",\n        CFInsertParams.insertParams().capacity(1000), \"foo\"));\n  }\n\n  @Test\n  public void testInsertNoCreateFilterDoesNotExist() {\n    try {\n      client.cfInsert(\"cuckoo10\", CFInsertParams.insertParams().noCreate(), \"foo\", \"bar\");\n      fail();\n    } catch (JedisDataException e) {\n      assertEquals(\"ERR not found\", e.getMessage());\n    }\n  }\n\n  @Test\n  public void testInsertNoCreateFilterExists() {\n    client.cfInsert(\"cuckoo11\", \"bar\");\n    assertEquals(Arrays.asList(true, true), client.cfInsert(\"cuckoo11\",\n        CFInsertParams.insertParams().noCreate(), \"foo\", \"bar\"));\n  }\n\n  @Test\n  public void testInsertNx() {\n    assertEquals(Arrays.asList(true), client.cfInsertNx(\"cuckoo12\", \"bar\"));\n  }\n\n  @Test\n  public void testInsertNxWithCapacity() {\n    client.cfInsertNx(\"cuckoo13\", \"bar\");\n    assertEquals(Arrays.asList(false), client.cfInsertNx(\"cuckoo13\",\n        CFInsertParams.insertParams().capacity(1000), \"bar\"));\n  }\n\n  @Test\n  public void testInsertNxMultiple() {\n    client.cfInsertNx(\"cuckoo14\", \"foo\");\n    client.cfInsertNx(\"cuckoo14\", \"bar\");\n    assertEquals(Arrays.asList(false, false, true),\n        client.cfInsertNx(\"cuckoo14\", \"foo\", \"bar\", \"baz\"));\n  }\n\n  @Test\n  public void testInsertNxNoCreate() {\n    try {\n      client.cfInsertNx(\"cuckoo15\", CFInsertParams.insertParams().noCreate(), \"foo\", \"bar\");\n      fail();\n    } catch (JedisDataException e) {\n      assertEquals(\"ERR not found\", e.getMessage());\n    }\n  }\n\n  @Test\n  public void testExistsItemDoesntExist() {\n    assertFalse(client.cfExists(\"cuckoo16\", \"foo\"));\n    assertEquals(Collections.singletonList(false), client.cfMExists(\"cuckoo16\", \"foo\"));\n  }\n\n  @Test\n  public void testExistsItemExists() {\n    client.cfInsert(\"cuckoo17\", \"foo\");\n    assertTrue(client.cfExists(\"cuckoo17\", \"foo\"));\n    assertEquals(Collections.singletonList(true), client.cfMExists(\"cuckoo17\", \"foo\"));\n  }\n\n  @Test\n  public void testMExistsMixedItems() {\n    client.cfInsert(\"cuckoo27\", \"foo\");\n    assertEquals(Arrays.asList(true, false), client.cfMExists(\"cuckoo27\", \"foo\", \"bar\"));\n    assertEquals(Arrays.asList(false, true), client.cfMExists(\"cuckoo27\", \"bar\", \"foo\"));\n  }\n\n  @Test\n  public void testDeleteItemDoesntExist() {\n    client.cfInsert(\"cuckoo8\", \"bar\");\n    assertFalse(client.cfDel(\"cuckoo8\", \"foo\"));\n  }\n\n  @Test\n  public void testDeleteItemExists() {\n    client.cfInsert(\"cuckoo18\", \"foo\");\n    assertTrue(client.cfDel(\"cuckoo18\", \"foo\"));\n  }\n\n  @Test\n  public void testDeleteFilterDoesNotExist() {\n    Exception ex = assertThrows(JedisDataException.class, () -> {\n      client.cfDel(\"cuckoo19\", \"foo\");\n    });\n    assertTrue(ex.getMessage().contains(\"Not found\"));\n  }\n\n  @Test\n  public void testCountFilterDoesNotExist() {\n    assertEquals(0L, client.cfCount(\"cuckoo20\", \"filter\"));\n  }\n\n  @Test\n  public void testCountFilterExist() {\n    client.cfInsert(\"cuckoo21\", \"foo\");\n    assertEquals(0L, client.cfCount(\"cuckoo21\", \"filter\"));\n  }\n\n  @Test\n  public void testCountItemExists() {\n    client.cfInsert(\"cuckoo22\", \"foo\");\n    assertEquals(1L, client.cfCount(\"cuckoo22\", \"foo\"));\n  }\n\n  @Test\n  public void testInfoFilterDoesNotExists() {\n    Exception ex = assertThrows(JedisDataException.class, () -> {\n      client.cfInfo(\"cuckoo23\");\n    });\n    assertTrue(ex.getMessage().contains(\"ERR not found\"));\n  }\n\n  @Test\n  @Timeout(2)\n  public void testScanDumpAndLoadChunk() {\n    client.cfReserve(\"cuckoo24\", 100L /*capacity*/, CFReserveParams.reserveParams().bucketSize(50));\n    client.cfAdd(\"cuckoo24-dump\", \"a\");\n\n    long iterator = 0;\n    while (true) {\n      Map.Entry<Long, byte[]> chunkData = client.cfScanDump(\"cuckoo24-dump\", iterator);\n      iterator = chunkData.getKey();\n      if (iterator == 0L) break;\n      assertEquals(\"OK\", client.cfLoadChunk(\"cuckoo24-load\", iterator, chunkData.getValue()));\n    }\n\n    // check for properties\n    assertEquals(client.cfInfo(\"cuckoo24-dump\"), client.cfInfo(\"cuckoo24-load\"));\n    // check for existing items\n    assertTrue(client.cfExists(\"cuckoo24-load\", \"a\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/bloom/TDigestTest.java",
    "content": "package redis.clients.jedis.modules.bloom;\n\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Random;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.bloom.TDigestMergeParams;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class TDigestTest extends RedisModuleCommandsTestBase {\n\n  private static final Random random = new Random();\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public TDigestTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  private void assertMergedUnmergedNodes(String key, int mergedNodes, int unmergedNodes) {\n    Map<String, Object> info = client.tdigestInfo(key);\n    assertEquals(Long.valueOf(mergedNodes), info.get(\"Merged nodes\"));\n    assertEquals(Long.valueOf(unmergedNodes), info.get(\"Unmerged nodes\"));\n  }\n\n  private void assertTotalWeight(String key, long totalWeight) {\n    Map<String, Object> info = client.tdigestInfo(key);\n    assertEquals(totalWeight, (Long) info.get(\"Merged weight\")\n        + (Long) info.get(\"Unmerged weight\"));\n  }\n\n  @Test\n  public void createSimple() {\n    assertEquals(\"OK\", client.tdigestCreate(\"td-simple\"));\n\n    Map<String, Object> info = client.tdigestInfo(\"td-simple\");\n    assertEquals(100L, info.get(\"Compression\"));\n  }\n\n  @Test\n  public void createAndInfo() {\n    for (int i = 100; i < 1000; i += 100) {\n      String key = \"td-\" + i;\n      assertEquals(\"OK\", client.tdigestCreate(key, i));\n\n      Map<String, Object> info = client.tdigestInfo(key);\n      assertEquals(Long.valueOf(i), info.get(\"Compression\"));\n    }\n  }\n\n  @Test\n  public void reset() {\n    client.tdigestCreate(\"reset\", 100);\n    assertMergedUnmergedNodes(\"reset\", 0, 0);\n\n    // on empty\n    assertEquals(\"OK\", client.tdigestReset(\"reset\"));\n    assertMergedUnmergedNodes(\"reset\", 0, 0);\n\n//    client.tdigestAdd(\"reset\", randomValueWeight(), randomValueWeight(), randomValueWeight());\n    client.tdigestAdd(\"reset\", randomValue(), randomValue(), randomValue());\n    assertMergedUnmergedNodes(\"reset\", 0, 3);\n\n    assertEquals(\"OK\", client.tdigestReset(\"reset\"));\n    assertMergedUnmergedNodes(\"reset\", 0, 0);\n  }\n\n  @Test\n  public void add() {\n    client.tdigestCreate(\"tdadd\", 100);\n\n//    assertEquals(\"OK\", client.tdigestAdd(\"tdadd\", randomValueWeight()));\n    assertEquals(\"OK\", client.tdigestAdd(\"tdadd\", randomValue()));\n    assertMergedUnmergedNodes(\"tdadd\", 0, 1);\n\n//    assertEquals(\"OK\", client.tdigestAdd(\"tdadd\", randomValueWeight(), randomValueWeight(), randomValueWeight(), randomValueWeight()));\n    assertEquals(\"OK\", client.tdigestAdd(\"tdadd\", randomValue(), randomValue(), randomValue(), randomValue()));\n    assertMergedUnmergedNodes(\"tdadd\", 0, 5);\n  }\n\n  @Test\n  public void merge() {\n    client.tdigestCreate(\"td2\", 100);\n    client.tdigestCreate(\"td4m\", 100);\n\n    assertEquals(\"OK\", client.tdigestMerge(\"td2\", \"td4m\"));\n    assertMergedUnmergedNodes(\"td2\", 0, 0);\n\n//    client.tdigestAdd(\"td2\", definedValueWeight(1, 1), definedValueWeight(1, 1), definedValueWeight(1, 1));\n//    client.tdigestAdd(\"td4m\", definedValueWeight(1, 100), definedValueWeight(1, 100));\n    client.tdigestAdd(\"td2\", 1, 1, 1);\n    client.tdigestAdd(\"td4m\", 1, 1);\n\n    assertEquals(\"OK\", client.tdigestMerge(\"td2\", \"td4m\"));\n    assertMergedUnmergedNodes(\"td2\", 3, 2);\n  }\n\n  @Test\n  public void mergeMultiAndParams() {\n    client.tdigestCreate(\"from1\", 100);\n    client.tdigestCreate(\"from2\", 200);\n\n    client.tdigestAdd(\"from1\", 1d);\n    client.tdigestAdd(\"from2\", weightedValue(1d, 10));\n\n    assertEquals(\"OK\", client.tdigestMerge(\"to\", \"from1\", \"from2\"));\n    assertTotalWeight(\"to\", 11L);\n\n    assertEquals(\"OK\", client.tdigestMerge(TDigestMergeParams.mergeParams()\n        .compression(50).override(), \"to\", \"from1\", \"from2\"));\n    assertEquals(50L, client.tdigestInfo(\"to\").get(\"Compression\"));\n  }\n\n  @Test\n  public void cdf() {\n    client.tdigestCreate(\"tdcdf\", 100);\n    assertEquals(singletonList(Double.NaN), client.tdigestCDF(\"tdcdf\", 50));\n\n    client.tdigestAdd(\"tdcdf\", 1, 1, 1);\n    client.tdigestAdd(\"tdcdf\", 100, 100);\n    assertEquals(singletonList(0.6), client.tdigestCDF(\"tdcdf\", 50));\n    client.tdigestCDF(\"tdcdf\", 25, 50, 75);\n  }\n\n  @Test\n  public void quantile() {\n    client.tdigestCreate(\"tdqnt\", 100);\n    assertEquals(singletonList(Double.NaN), client.tdigestQuantile(\"tdqnt\", 0.5));\n\n//    client.tdigestAdd(\"tdqnt\", definedValueWeight(1, 1), definedValueWeight(1, 1), definedValueWeight(1, 1));\n//    client.tdigestAdd(\"tdqnt\", definedValueWeight(100, 1), definedValueWeight(100, 1));\n    client.tdigestAdd(\"tdqnt\", 1, 1, 1);\n    client.tdigestAdd(\"tdqnt\", 100, 100);\n    assertEquals(singletonList(1.0), client.tdigestQuantile(\"tdqnt\", 0.5));\n  }\n\n  @Test\n  public void minAndMax() {\n    final String key = \"tdmnmx\";\n    client.tdigestCreate(key, 100);\n    assertEquals(Double.NaN, client.tdigestMin(key), 0d);\n    assertEquals(Double.NaN, client.tdigestMax(key), 0d);\n\n//    client.tdigestAdd(key, definedValueWeight(2, 1));\n//    client.tdigestAdd(key, definedValueWeight(5, 1));\n    client.tdigestAdd(key, 2);\n    client.tdigestAdd(key, 5);\n    assertEquals(2d, client.tdigestMin(key), 0.01);\n    assertEquals(5d, client.tdigestMax(key), 0.01);\n  }\n\n  @Test\n  public void trimmedMean() {\n    final String key = \"trimmed_mean\";\n    client.tdigestCreate(key, 500);\n\n    for (int i = 0; i < 20; i++) {\n//      client.tdigestAdd(key, KeyValue.of(Double.valueOf(i), 1l));\n      client.tdigestAdd(key, (double) i);\n    }\n\n    assertEquals(9.5, client.tdigestTrimmedMean(key, 0.1, 0.9), 0.01);\n    assertEquals(9.5, client.tdigestTrimmedMean(key, 0.0, 1.0), 0.01);\n    assertEquals(4.5, client.tdigestTrimmedMean(key, 0.0, 0.5), 0.01);\n    assertEquals(14.5, client.tdigestTrimmedMean(key, 0.5, 1.0), 0.01);\n  }\n\n  @Test\n  public void rankCommands() {\n    final String key = \"ranks\";\n    client.tdigestCreate(key);\n    client.tdigestAdd(key, 2d, 3d, 5d);\n    assertEquals(Arrays.asList(0L, 2L), client.tdigestRank(key, 2d, 4d));\n    assertEquals(Arrays.asList(0L, 1L), client.tdigestRevRank(key, 5d, 4d));\n    assertEquals(Arrays.asList(2d, 3d), client.tdigestByRank(key, 0L, 1L));\n    assertEquals(Arrays.asList(5d, 3d), client.tdigestByRevRank(key, 0L, 1L));\n  }\n\n  private static double randomValue() {\n    return random.nextDouble() * 10000;\n  }\n\n  private static double[] weightedValue(double value, int weight) {\n    double[] values = new double[weight];\n    Arrays.fill(values, value);\n    return values;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/bloom/TopKTest.java",
    "content": "package redis.clients.jedis.modules.bloom;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class TopKTest extends RedisModuleCommandsTestBase {\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public TopKTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void createTopKFilter() {\n    client.topkReserve(\"aaa\", 30, 2000, 7, 0.925);\n\n    assertEquals(Arrays.asList(null, null), client.topkAdd(\"aaa\", \"bb\", \"cc\"));\n\n    assertEquals(Arrays.asList(true, false, true), client.topkQuery(\"aaa\", \"bb\", \"gg\", \"cc\"));\n\n    assertEquals(Arrays.asList(\"bb\", \"cc\"), client.topkList(\"aaa\"));\n\n    Map<String, Long> listWithCount = client.topkListWithCount(\"aaa\");\n    assertEquals(2, listWithCount.size());\n    listWithCount.forEach((item, count) -> {\n      assertTrue(Arrays.asList(\"bb\", \"cc\").contains(item));\n      assertEquals(Long.valueOf(1), count);\n    });\n\n    assertNull(client.topkIncrBy(\"aaa\", \"ff\", 5));\n    assertEquals(Arrays.asList(\"ff\", \"bb\", \"cc\"), client.topkList(\"aaa\"));\n\n    assertEquals(Collections.<String>singletonList(null),\n        client.topkIncrBy(\"aaa\", Collections.singletonMap(\"ff\", 8L)));\n    assertEquals(Long.valueOf(13), client.topkListWithCount(\"aaa\").get(\"ff\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/json/JsonObjects.java",
    "content": "package redis.clients.jedis.modules.json;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Objects;\n\npublic class JsonObjects {\n\n  /* A simple class that represents an object in real life */\n  @SuppressWarnings(\"unused\")\n  public static class IRLObject {\n\n    public String str;\n    public boolean bool;\n\n    public IRLObject() {\n      this.str = \"string\";\n      this.bool = true;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (this == obj) return true;\n      if (obj == null) return false;\n      if (getClass() != obj.getClass()) return false;\n      final IRLObject other = (IRLObject) obj;\n      return Objects.equals(str, other.str)\n          && Objects.equals(bool, other.bool);\n    }\n  }\n\n  @SuppressWarnings(\"unused\")\n  public static class FooBarObject {\n\n    public String foo;\n    public boolean fooB;\n    public int fooI;\n    public float fooF;\n    public String[] fooArr;\n\n    public FooBarObject() {\n      this.foo = \"bar\";\n      this.fooB = true;\n      this.fooI = 6574;\n      this.fooF = 435.345f;\n      this.fooArr = new String[]{\"a\", \"b\", \"c\"};\n    }\n  }\n\n  public static class Baz {\n\n    String quuz;\n    private String grault;\n    private String waldo;\n\n    public Baz(final String quuz, final String grault, final String waldo) {\n      this.quuz = quuz;\n      this.grault = grault;\n      this.waldo = waldo;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) {\n        return true;\n      }\n      if (o == null) {\n        return false;\n      }\n      if (getClass() != o.getClass()) {\n        return false;\n      }\n      Baz other = (Baz) o;\n\n      return Objects.equals(quuz, other.quuz)\n          && Objects.equals(grault, other.grault)\n          && Objects.equals(waldo, other.waldo);\n    }\n  }\n\n  public static class Qux {\n\n    private String quux;\n    private String corge;\n    private String garply;\n    private Baz baz;\n\n    public Qux(final String quux, final String corge, final String garply, final Baz baz) {\n      this.quux = quux;\n      this.corge = corge;\n      this.garply = garply;\n      this.baz = baz;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) {\n        return true;\n      }\n      if (o == null) {\n        return false;\n      }\n      if (getClass() != o.getClass()) {\n        return false;\n      }\n      Qux other = (Qux) o;\n\n      return Objects.equals(quux, other.quux)\n          && Objects.equals(corge, other.corge)\n          && Objects.equals(garply, other.garply)\n          && Objects.equals(baz, other.baz);\n    }\n  }\n\n  public static class Person {\n    public String name;\n    public int age;\n    public String address;\n    public String phone;\n    public List<String> childrens;\n\n    public Person(String name, int age, String address, String phone, List<String> childrens) {\n      this.name = name;\n      this.age = age;\n      this.address = address;\n      this.phone = phone;\n      this.childrens = childrens;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) {\n        return true;\n      }\n      if (o == null) {\n        return false;\n      }\n      // if (getClass() != o.getClass()) {\n      //   return false;\n      // }\n      Person other = (Person) o;\n\n      return Objects.equals(name, other.name)\n            && Objects.equals(age, other.age)\n            && Objects.equals(address, other.address)\n            && Objects.equals(phone, other.phone)\n            && Objects.equals(childrens, other.childrens);\n    }\n  }\n\n  public static class Tick {\n    private final String id;\n    private final Instant created;\n\n    public Tick(String id, Instant created) {\n      this.id = id;\n      this.created = created;\n    }\n\n    public String getId() {\n      return id;\n    }\n\n    public Instant getCreated() {\n      return created;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/json/Path2Test.java",
    "content": "package redis.clients.jedis.modules.json;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.json.Path2;\n\npublic class Path2Test {\n\n  @Test\n  public void _null() {\n    assertThrows(NullPointerException.class, ()->Path2.of(null));\n  }\n\n  @Test\n  public void empty() {\n    assertThrows(IllegalArgumentException.class,()->Path2.of(\"\"));\n  }\n\n  @Test\n  public void root() {\n    assertEquals(\"$\", Path2.ROOT_PATH.toString());\n    assertEquals(Path2.ROOT_PATH, new Path2(\"$\"));\n    assertEquals(Path2.ROOT_PATH, Path2.of(\"$\"));\n  }\n\n  @Test\n  public void test() {\n    assertEquals(\"$.a.b\", Path2.of(\"$.a.b\").toString());\n    assertEquals(\"$.a.b\", new Path2(\"$.a.b\").toString());\n    assertEquals(\"$.a.b\", Path2.of(\".a.b\").toString());\n    assertEquals(\"$.a.b\", new Path2(\".a.b\").toString());\n    assertEquals(\"$.a.b\", Path2.of(\"a.b\").toString());\n    assertEquals(\"$.a.b\", new Path2(\"a.b\").toString());\n  }\n\n  @Test\n  public void equals() {\n    assertEquals(new Path2(\"a.b\"), Path2.of(\".a.b\"));\n    assertEquals(Path2.of(\"a.b\"), new Path2(\".a.b\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/json/PathTest.java",
    "content": "package redis.clients.jedis.modules.json;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.json.Path;\n\npublic class PathTest {\n\n  @Test\n  public void testRootPathConstant() {\n    assertEquals(\".\", Path.ROOT_PATH.toString());\n  }\n\n  @Test\n  public void testStaticFactoryMethod() {\n    assertEquals(\".a.b\", Path.of(\".a.b\").toString());\n  }\n\n  @Test\n  public void testPathEquals() {\n    assertEquals(Path.of(\".a.b.c\"), Path.of(\".a.b.c\"));\n    assertNotEquals(Path.of(\".a.b.c\"), Path.of(\".b.c\"));\n    assertNotEquals(Path.of(\".a.b.c\"), null);\n    assertNotEquals(Path.of(\".a.b.c\"), \".a.b.c\");\n    Path aPath = Path.of(\".a\");\n    assertEquals(aPath, aPath);\n  }\n\n  @Test\n  public void testPathHashCode() {\n    assertEquals(Path.of(\".a.b.c\").hashCode(), Path.of(\".a.b.c\").hashCode());\n    assertNotEquals(Path.of(\".a.b.c\").hashCode(), Path.of(\".b.c\").hashCode());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/json/RedisJsonV1Test.java",
    "content": "package redis.clients.jedis.modules.json;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static redis.clients.jedis.json.Path.ROOT_PATH;\nimport static redis.clients.jedis.modules.json.JsonObjects.*;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\n\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.json.commands.RedisJsonV1Commands;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.util.JsonObjectMapperTestUtil;\n\n/**\n * V1 of the RedisJSON is only supported with RESP2, hence this test is not parameterized.\n */\npublic class RedisJsonV1Test extends RedisModuleCommandsTestBase {\n\n  private final Gson gson = new Gson();\n\n  private RedisJsonV1Commands jsonV1;\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public RedisJsonV1Test() {\n    super(RedisProtocol.RESP2);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() {\n    super.setUp();\n    this.jsonV1 = super.client;\n  }\n\n  @Test\n  public void basicSetGetShouldSucceed() {\n\n    // naive set with a path\n//    jsonClient.jsonSet(\"null\", null, ROOT_PATH);\n    jsonV1.jsonSet(\"null\", ROOT_PATH, (Object) null);\n    assertNull(jsonV1.jsonGet(\"null\", String.class, ROOT_PATH));\n\n    // real scalar value and no path\n    jsonV1.jsonSet(\"str\", ROOT_PATH, \"strong\");\n    assertEquals(\"strong\", jsonV1.jsonGet(\"str\"));\n\n    // a slightly more complex object\n    IRLObject obj = new IRLObject();\n    jsonV1.jsonSet(\"obj\", ROOT_PATH, obj);\n    Object expected = gson.fromJson(gson.toJson(obj), Object.class);\n    assertTrue(expected.equals(jsonV1.jsonGet(\"obj\")));\n\n    // check an update\n    Path p = Path.of(\".str\");\n    jsonV1.jsonSet(\"obj\", p, \"strung\");\n    assertEquals(\"strung\", jsonV1.jsonGet(\"obj\", String.class, p));\n  }\n\n  @Test\n  public void setExistingPathOnlyIfExistsShouldSucceed() {\n    jsonV1.jsonSet(\"obj\", ROOT_PATH, new IRLObject());\n    Path p = Path.of(\".str\");\n    jsonV1.jsonSet(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().xx());\n    assertEquals(\"strangle\", jsonV1.jsonGet(\"obj\", String.class, p));\n  }\n\n  @Test\n  public void setNonExistingOnlyIfNotExistsShouldSucceed() {\n    jsonV1.jsonSet(\"obj\", ROOT_PATH, new IRLObject());\n    Path p = Path.of(\".none\");\n    jsonV1.jsonSet(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().nx());\n    assertEquals(\"strangle\", jsonV1.jsonGet(\"obj\", String.class, p));\n  }\n\n  @Test\n  public void setWithoutAPathDefaultsToRootPath() {\n    jsonV1.jsonSet(\"obj1\", ROOT_PATH, new IRLObject());\n//    jsonClient.jsonSet(\"obj1\", \"strangle\", JsonSetParams.jsonSetParams().xx());\n    jsonV1.jsonSetLegacy(\"obj1\", (Object) \"strangle\", JsonSetParams.jsonSetParams().xx());\n    assertEquals(\"strangle\", jsonV1.jsonGet(\"obj1\", String.class, ROOT_PATH));\n  }\n\n  @Test\n  public void setExistingPathOnlyIfNotExistsShouldFail() {\n    jsonV1.jsonSet(\"obj\", ROOT_PATH, new IRLObject());\n    Path p = Path.of(\".str\");\n    assertNull(jsonV1.jsonSet(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().nx()));\n  }\n\n  @Test\n  public void setNonExistingPathOnlyIfExistsShouldFail() {\n    jsonV1.jsonSet(\"obj\", ROOT_PATH, new IRLObject());\n    Path p = Path.of(\".none\");\n    assertNull(jsonV1.jsonSet(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().xx()));\n  }\n\n  @Test\n  public void setException() {\n    // should error on non root path for new key\n    assertThrows(JedisDataException.class, () -> jsonV1.jsonSet(\"test\", Path.of(\".foo\"), \"bar\"));\n  }\n\n  @Test\n  public void getMultiplePathsShouldSucceed() {\n    // check multiple paths\n    IRLObject obj = new IRLObject();\n    jsonV1.jsonSetLegacy(\"obj\", obj);\n    Object expected = gson.fromJson(gson.toJson(obj), Object.class);\n    assertTrue(expected.equals(jsonV1.jsonGet(\"obj\", Object.class, Path.of(\"bool\"), Path.of(\"str\"))));\n  }\n\n  @Test\n  public void toggle() {\n\n    IRLObject obj = new IRLObject();\n    jsonV1.jsonSetLegacy(\"obj\", obj);\n\n    Path pbool = Path.of(\".bool\");\n    // check initial value\n    assertTrue(jsonV1.jsonGet(\"obj\", Boolean.class, pbool));\n\n    // true -> false\n    jsonV1.jsonToggle(\"obj\", pbool);\n    assertFalse(jsonV1.jsonGet(\"obj\", Boolean.class, pbool));\n\n    // false -> true\n    jsonV1.jsonToggle(\"obj\", pbool);\n    assertTrue(jsonV1.jsonGet(\"obj\", Boolean.class, pbool));\n\n    // ignore non-boolean field\n    Path pstr = Path.of(\".str\");\n    try {\n      jsonV1.jsonToggle(\"obj\", pstr);\n      fail(\"String not a bool\");\n    } catch (JedisDataException jde) {\n      assertTrue(jde.getMessage().contains(\"not a bool\"));\n    }\n    assertEquals(\"string\", jsonV1.jsonGet(\"obj\", String.class, pstr));\n  }\n\n  @Test\n  public void getAbsent() {\n    jsonV1.jsonSet(\"test\", ROOT_PATH, \"foo\");\n    assertThrows(JedisDataException.class,\n        () -> jsonV1.jsonGet(\"test\", String.class, Path.of(\".bar\")));\n  }\n\n  @Test\n  public void delValidShouldSucceed() {\n    // check deletion of a single path\n    jsonV1.jsonSet(\"obj\", ROOT_PATH, new IRLObject());\n    assertEquals(1L, jsonV1.jsonDel(\"obj\", Path.of(\".str\")));\n    assertTrue(client.exists(\"obj\"));\n\n    // check deletion root using default root -> key is removed\n    assertEquals(1L, jsonV1.jsonDel(\"obj\"));\n    assertFalse(client.exists(\"obj\"));\n  }\n\n  @Test\n  public void delNonExistingPathsAreIgnored() {\n    jsonV1.jsonSet(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertEquals(0L, jsonV1.jsonDel(\"foobar\", Path.of(\".foo[1]\")));\n  }\n\n  @Test\n  public void typeChecksShouldSucceed() {\n    assertNull(jsonV1.jsonType(\"foobar\"));\n    jsonV1.jsonSet(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertSame(Object.class, jsonV1.jsonType(\"foobar\"));\n    assertSame(Object.class, jsonV1.jsonType(\"foobar\", ROOT_PATH));\n    assertSame(String.class, jsonV1.jsonType(\"foobar\", Path.of(\".foo\")));\n    assertSame(int.class, jsonV1.jsonType(\"foobar\", Path.of(\".fooI\")));\n    assertSame(float.class, jsonV1.jsonType(\"foobar\", Path.of(\".fooF\")));\n    assertSame(List.class, jsonV1.jsonType(\"foobar\", Path.of(\".fooArr\")));\n    assertSame(boolean.class, jsonV1.jsonType(\"foobar\", Path.of(\".fooB\")));\n    assertNull(jsonV1.jsonType(\"foobar\", Path.of(\".fooErr\")));\n  }\n\n  @Test\n  public void testJsonMerge() {\n    // create data\n    List<String> childrens = new ArrayList<>();\n    childrens.add(\"Child 1\");\n    Person person = new Person(\"John Doe\", 25, \"123 Main Street\", \"123-456-7890\", childrens);\n    assertEquals(\"OK\", jsonV1.jsonSet(\"test_merge\", ROOT_PATH, person));\n\n    // After 5 years:\n    person.age = 30;\n    person.childrens.add(\"Child 2\");\n    person.childrens.add(\"Child 3\");\n\n    // merge the new data\n    assertEquals(\"OK\", jsonV1.jsonMerge(\"test_merge\", Path.of((\".childrens\")), person.childrens));\n    assertEquals(\"OK\", jsonV1.jsonMerge(\"test_merge\", Path.of((\".age\")), person.age));\n    assertEquals(person, jsonV1.jsonGet(\"test_merge\", Person.class));\n  }\n\n  @Test\n  public void mgetWithPathWithAllKeysExist() {\n    Baz baz1 = new Baz(\"quuz1\", \"grault1\", \"waldo1\");\n    Baz baz2 = new Baz(\"quuz2\", \"grault2\", \"waldo2\");\n    Qux qux1 = new Qux(\"quux1\", \"corge1\", \"garply1\", baz1);\n    Qux qux2 = new Qux(\"quux2\", \"corge2\", \"garply2\", baz2);\n\n    jsonV1.jsonSetLegacy(\"qux1\", qux1);\n    jsonV1.jsonSetLegacy(\"qux2\", qux2);\n\n    List<Baz> allBaz = jsonV1.jsonMGet(Path.of(\"baz\"), Baz.class, \"qux1\", \"qux2\");\n\n    assertEquals(2, allBaz.size());\n\n    Baz testBaz1 = allBaz.stream() //\n        .filter(b -> b.quuz.equals(\"quuz1\")) //\n        .findFirst() //\n        .orElseThrow(() -> new NullPointerException(\"\"));\n    Baz testBaz2 = allBaz.stream() //\n        .filter(q -> q.quuz.equals(\"quuz2\")) //\n        .findFirst() //\n        .orElseThrow(() -> new NullPointerException(\"\"));\n\n    assertEquals(baz1, testBaz1);\n    assertEquals(baz2, testBaz2);\n  }\n\n  @Test\n  public void mgetAtRootPathWithMissingKeys() {\n    Baz baz1 = new Baz(\"quuz1\", \"grault1\", \"waldo1\");\n    Baz baz2 = new Baz(\"quuz2\", \"grault2\", \"waldo2\");\n    Qux qux1 = new Qux(\"quux1\", \"corge1\", \"garply1\", baz1);\n    Qux qux2 = new Qux(\"quux2\", \"corge2\", \"garply2\", baz2);\n\n    jsonV1.jsonSetLegacy(\"qux1\", qux1);\n    jsonV1.jsonSetLegacy(\"qux2\", qux2);\n\n    List<Qux> allQux = jsonV1.jsonMGet(Qux.class, \"qux1\", \"qux2\", \"qux3\");\n\n    assertEquals(3, allQux.size());\n    assertNull(allQux.get(2));\n    allQux.removeAll(Collections.singleton(null));\n    assertEquals(2, allQux.size());\n  }\n\n  @Test\n  public void arrLen() {\n    jsonV1.jsonSet(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertEquals(Long.valueOf(3), jsonV1.jsonArrLen(\"foobar\", Path.of(\".fooArr\")));\n  }\n\n  @Test\n  public void arrLenDefaultPath() {\n    assertNull(jsonV1.jsonArrLen(\"array\"));\n    jsonV1.jsonSetLegacy(\"array\", new int[]{1, 2, 3});\n    assertEquals(Long.valueOf(3), jsonV1.jsonArrLen(\"array\"));\n  }\n\n  @Test\n  public void clearArray() {\n    jsonV1.jsonSet(\"foobar\", ROOT_PATH, new FooBarObject());\n\n    Path arrPath = Path.of(\".fooArr\");\n    assertEquals(Long.valueOf(3), jsonV1.jsonArrLen(\"foobar\", arrPath));\n\n    assertEquals(1L, jsonV1.jsonClear(\"foobar\", arrPath));\n    assertEquals(Long.valueOf(0), jsonV1.jsonArrLen(\"foobar\", arrPath));\n\n    // ignore non-array\n    Path strPath = Path.of(\"foo\");\n    assertEquals(0L, jsonV1.jsonClear(\"foobar\", strPath));\n    assertEquals(\"bar\", jsonV1.jsonGet(\"foobar\", String.class, strPath));\n  }\n\n  @Test\n  public void clearObject() {\n    Baz baz = new Baz(\"quuz\", \"grault\", \"waldo\");\n    Qux qux = new Qux(\"quux\", \"corge\", \"garply\", baz);\n\n    jsonV1.jsonSetLegacy(\"qux\", qux);\n    Path objPath = Path.of(\"baz\");\n    assertEquals(baz, jsonV1.jsonGet(\"qux\", Baz.class, objPath));\n\n    assertEquals(1L, jsonV1.jsonClear(\"qux\", objPath));\n    assertEquals(new Baz(null, null, null), jsonV1.jsonGet(\"qux\", Baz.class, objPath));\n  }\n\n  @Test\n  public void arrAppendSameType() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    JsonObject jsonObject = gson.fromJson(json, JsonObject.class);\n\n    jsonV1.jsonSet(\"test_arrappend\", ROOT_PATH, jsonObject);\n    assertEquals(Long.valueOf(6), jsonV1.jsonArrAppend(\"test_arrappend\", Path.of(\".b\"), 4, 5, 6));\n\n    Integer[] array = jsonV1.jsonGet(\"test_arrappend\", Integer[].class, Path.of(\".b\"));\n    assertArrayEquals(new Integer[]{1, 2, 3, 4, 5, 6}, array);\n  }\n\n  @Test\n  public void arrAppendMultipleTypes() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    JsonObject jsonObject = gson.fromJson(json, JsonObject.class);\n\n    jsonV1.jsonSet(\"test_arrappend\", ROOT_PATH, jsonObject);\n    assertEquals(Long.valueOf(6), jsonV1.jsonArrAppend(\"test_arrappend\", Path.of(\".b\"), \"foo\", true, null));\n\n    Object[] array = jsonV1.jsonGet(\"test_arrappend\", Object[].class, Path.of(\".b\"));\n\n    // NOTE: GSon converts numeric types to the most accommodating type (Double)\n    // when type information is not provided (as in the Object[] below)\n    assertArrayEquals(new Object[]{1.0, 2.0, 3.0, \"foo\", true, null}, array);\n  }\n\n  @Test\n  public void arrAppendMultipleTypesWithDeepPath() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    JsonObject jsonObject = gson.fromJson(json, JsonObject.class);\n\n    jsonV1.jsonSet(\"test_arrappend\", ROOT_PATH, jsonObject);\n    assertEquals(Long.valueOf(4), jsonV1.jsonArrAppend(\"test_arrappend\", Path.of(\".c.d\"), \"foo\", true, null));\n\n    Object[] array = jsonV1.jsonGet(\"test_arrappend\", Object[].class, Path.of(\".c.d\"));\n    assertArrayEquals(new Object[]{\"ello\", \"foo\", true, null}, array);\n  }\n\n  @Test\n  public void arrAppendAgaintsEmptyArray() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: [] }}\";\n    JsonObject jsonObject = gson.fromJson(json, JsonObject.class);\n\n    jsonV1.jsonSet(\"test_arrappend\", ROOT_PATH, jsonObject);\n    assertEquals(Long.valueOf(3), jsonV1.jsonArrAppend(\"test_arrappend\", Path.of(\".c.d\"), \"a\", \"b\", \"c\"));\n\n    String[] array = jsonV1.jsonGet(\"test_arrappend\", String[].class, Path.of(\".c.d\"));\n    assertArrayEquals(new String[]{\"a\", \"b\", \"c\"}, array);\n  }\n\n  @Test\n  public void arrAppendPathIsNotArray() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    JsonObject jsonObject = gson.fromJson(json, JsonObject.class);\n\n    jsonV1.jsonSet(\"test_arrappend\", ROOT_PATH, jsonObject);\n    assertThrows(JedisDataException.class,\n        () -> jsonV1.jsonArrAppend(\"test_arrappend\", Path.of(\".a\"), 1));\n  }\n\n  @Test\n  public void arrIndexAbsentKey() {\n    assertThrows(JedisDataException.class,\n        () -> jsonV1.jsonArrIndex(\"quxquux\", ROOT_PATH, gson.toJson(new Object())));\n  }\n\n  @Test\n  public void arrIndexWithInts() {\n    jsonV1.jsonSet(\"quxquux\", ROOT_PATH, new int[]{8, 6, 7, 5, 3, 0, 9});\n    assertEquals(2L, jsonV1.jsonArrIndex(\"quxquux\", ROOT_PATH, 7));\n    assertEquals(-1L, jsonV1.jsonArrIndex(\"quxquux\", ROOT_PATH, \"7\"));\n  }\n\n  @Test\n  public void arrIndexWithStrings() {\n    jsonV1.jsonSet(\"quxquux\", ROOT_PATH, new String[]{\"8\", \"6\", \"7\", \"5\", \"3\", \"0\", \"9\"});\n    assertEquals(2L, jsonV1.jsonArrIndex(\"quxquux\", ROOT_PATH, \"7\"));\n  }\n\n  @Test\n  public void arrIndexWithStringsAndPath() {\n    jsonV1.jsonSet(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertEquals(1L, jsonV1.jsonArrIndex(\"foobar\", Path.of(\".fooArr\"), \"b\"));\n  }\n\n  @Test\n  public void arrIndexNonExistentPath() {\n    jsonV1.jsonSet(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertThrows(JedisDataException.class,\n        () -> assertEquals(1L, jsonV1.jsonArrIndex(\"foobar\", Path.of(\".barArr\"), \"x\")));\n  }\n\n  @Test\n  public void arrInsert() {\n    String json = \"['hello', 'world', true, 1, 3, null, false]\";\n    JsonArray jsonArray = gson.fromJson(json, JsonArray.class);\n\n    jsonV1.jsonSet(\"test_arrinsert\", ROOT_PATH, jsonArray);\n    assertEquals(8L, jsonV1.jsonArrInsert(\"test_arrinsert\", ROOT_PATH, 1, \"foo\"));\n\n    Object[] array = jsonV1.jsonGet(\"test_arrinsert\", Object[].class, ROOT_PATH);\n\n    // NOTE: GSon converts numeric types to the most accommodating type (Double)\n    // when type information is not provided (as in the Object[] below)\n    assertArrayEquals(new Object[]{\"hello\", \"foo\", \"world\", true, 1.0, 3.0, null, false}, array);\n  }\n\n  @Test\n  public void arrInsertWithNegativeIndex() {\n    String json = \"['hello', 'world', true, 1, 3, null, false]\";\n    JsonArray jsonArray = gson.fromJson(json, JsonArray.class);\n\n    jsonV1.jsonSet(\"test_arrinsert\", ROOT_PATH, jsonArray);\n    assertEquals(8L, jsonV1.jsonArrInsert(\"test_arrinsert\", ROOT_PATH, -1, \"foo\"));\n\n    Object[] array = jsonV1.jsonGet(\"test_arrinsert\", Object[].class, ROOT_PATH);\n    assertArrayEquals(new Object[]{\"hello\", \"world\", true, 1.0, 3.0, null, \"foo\", false}, array);\n  }\n\n  @Test\n  public void testArrayPop() {\n    jsonV1.jsonSet(\"arr\", ROOT_PATH, new int[]{0, 1, 2, 3, 4});\n    assertEquals(Long.valueOf(4), jsonV1.jsonArrPop(\"arr\", Long.class, ROOT_PATH));\n    assertEquals(Long.valueOf(3), jsonV1.jsonArrPop(\"arr\", Long.class, ROOT_PATH, -1));\n    assertEquals(Long.valueOf(2), jsonV1.jsonArrPop(\"arr\", Long.class));\n    assertEquals(Long.valueOf(0), jsonV1.jsonArrPop(\"arr\", Long.class, ROOT_PATH, 0));\n    assertEquals(Double.valueOf(1), jsonV1.jsonArrPop(\"arr\"));\n  }\n\n  @Test\n  public void arrTrim() {\n    jsonV1.jsonSet(\"arr\", ROOT_PATH, new int[]{0, 1, 2, 3, 4});\n    assertEquals(Long.valueOf(3), jsonV1.jsonArrTrim(\"arr\", ROOT_PATH, 1, 3));\n    assertArrayEquals(new Integer[]{1, 2, 3}, jsonV1.jsonGet(\"arr\", Integer[].class, ROOT_PATH));\n  }\n\n  @Test\n  public void strAppend() {\n    jsonV1.jsonSet(\"str\", ROOT_PATH, \"foo\");\n    assertEquals(6L, jsonV1.jsonStrAppend(\"str\", ROOT_PATH, \"bar\"));\n    assertEquals(\"foobar\", jsonV1.jsonGet(\"str\", String.class, ROOT_PATH));\n    assertEquals(8L, jsonV1.jsonStrAppend(\"str\", \"ed\"));\n//    assertEquals(\"foobared\", jsonClient.jsonGet(\"str\", String.class));\n    assertEquals(\"foobared\", jsonV1.jsonGet(\"str\"));\n  }\n\n  @Test\n  public void strLen() {\n    assertNull(jsonV1.jsonStrLen(\"str\"));\n    jsonV1.jsonSet(\"str\", ROOT_PATH, \"foo\");\n    assertEquals(Long.valueOf(3), jsonV1.jsonStrLen(\"str\"));\n    assertEquals(Long.valueOf(3), jsonV1.jsonStrLen(\"str\", ROOT_PATH));\n  }\n\n  @Test\n  public void numIncrBy() {\n    jsonV1.jsonSetLegacy(\"doc\", gson.fromJson(\"{a:3}\", JsonObject.class));\n    assertEquals(5d, jsonV1.jsonNumIncrBy(\"doc\", Path.of(\".a\"), 2), 0d);\n  }\n\n  @Test\n  public void obj() {\n    assertNull(jsonV1.jsonObjLen(\"doc\"));\n    assertNull(jsonV1.jsonObjKeys(\"doc\"));\n    assertNull(jsonV1.jsonObjLen(\"doc\", ROOT_PATH));\n    assertNull(jsonV1.jsonObjKeys(\"doc\", ROOT_PATH));\n\n    String json = \"{\\\"a\\\":[3], \\\"nested\\\": {\\\"a\\\": {\\\"b\\\":2, \\\"c\\\": 1}}}\";\n    jsonV1.jsonSetWithPlainString(\"doc\", ROOT_PATH, json);\n    assertEquals(Long.valueOf(2), jsonV1.jsonObjLen(\"doc\"));\n    assertEquals(Arrays.asList(\"a\", \"nested\"), jsonV1.jsonObjKeys(\"doc\"));\n    assertEquals(Long.valueOf(2), jsonV1.jsonObjLen(\"doc\", Path.of(\".nested.a\")));\n    assertEquals(Arrays.asList(\"b\", \"c\"), jsonV1.jsonObjKeys(\"doc\", Path.of(\".nested.a\")));\n  }\n\n  @Test\n  public void debugMemory() {\n    assertEquals(0L, jsonV1.jsonDebugMemory(\"json\"));\n    assertEquals(0L, jsonV1.jsonDebugMemory(\"json\", ROOT_PATH));\n\n    String json = \"{ foo: 'bar', bar: { foo: 10 }}\";\n    JsonObject jsonObject = gson.fromJson(json, JsonObject.class);\n    jsonV1.jsonSet(\"json\", ROOT_PATH, jsonObject);\n    // it is okay as long as any 'long' is returned\n    jsonV1.jsonDebugMemory(\"json\");\n    jsonV1.jsonDebugMemory(\"json\", ROOT_PATH);\n    jsonV1.jsonDebugMemory(\"json\", Path.of(\".bar\"));\n  }\n\n  @Test\n  public void plainString() {\n    String json = \"{\\\"foo\\\":\\\"bar\\\",\\\"bar\\\":{\\\"foo\\\":10}}\";\n    assertEquals(\"OK\", jsonV1.jsonSetWithPlainString(\"plain\", ROOT_PATH, json));\n    assertEquals(json, jsonV1.jsonGetAsPlainString(\"plain\", ROOT_PATH));\n  }\n\n  @Test\n  public void testJsonGsonParser() {\n    Tick person = new Tick(\"foo\", Instant.now());\n\n    // setting the custom json gson parser\n    client.setJsonObjectMapper(JsonObjectMapperTestUtil.getCustomGsonObjectMapper());\n\n    jsonV1.jsonSet(person.getId(), ROOT_PATH, person);\n\n    String valueExpected = jsonV1.jsonGet(person.getId(), String.class, Path.of(\".created\"));\n    assertEquals(valueExpected, person.getCreated().toString());\n  }\n\n  @Test\n  public void testDefaultJsonGsonParserStringsMustBeDifferent() {\n    Tick tick = new Tick(\"foo\", Instant.now());\n\n    // using the default json gson parser which is automatically configured\n\n    jsonV1.jsonSet(tick.getId(), ROOT_PATH, tick);\n\n    Object valueExpected = jsonV1.jsonGet(tick.getId(), Path.of(\".created\"));\n    assertNotEquals(valueExpected, tick.getCreated().toString());\n  }\n\n  @Test\n  public void testJsonJacksonParser() {\n    Tick person = new Tick(\"foo\", Instant.now());\n\n    // setting the custom json jackson parser\n    client.setJsonObjectMapper(JsonObjectMapperTestUtil.getCustomJacksonObjectMapper());\n\n    jsonV1.jsonSet(person.getId(), ROOT_PATH, person);\n\n    String valueExpected = jsonV1.jsonGet(person.getId(), String.class, Path.of(\".created\"));\n    assertEquals(valueExpected, person.getCreated().toString());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java",
    "content": "package redis.clients.jedis.modules.json;\n\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static redis.clients.jedis.json.Path2.ROOT_PATH;\nimport static redis.clients.jedis.modules.json.JsonObjects.*;\n\nimport com.google.gson.Gson;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.JsonSetParams;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.json.commands.RedisJsonV2Commands;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class RedisJsonV2Test extends RedisModuleCommandsTestBase {\n\n  private static final Gson gson = new Gson();\n\n  private RedisJsonV2Commands jsonV2;\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public RedisJsonV2Test(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @BeforeEach\n  @Override\n  public void setUp() {\n    super.setUp();\n    this.jsonV2 = super.client;\n  }\n\n  @Test\n  public void basicSetGetShouldSucceed() {\n    // naive set with a path\n    jsonV2.jsonSetWithEscape(\"null\", ROOT_PATH, (Object) null);\n    assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonGet(\"null\", ROOT_PATH));\n\n    // real scalar value and no path\n    jsonV2.jsonSetWithEscape(\"str\", \"strong\");\n    assertEquals(\"strong\", jsonV2.jsonGet(\"str\"));\n\n    // a slightly more complex object\n    IRLObject obj = new IRLObject();\n    jsonV2.jsonSetWithEscape(\"obj\", obj);\n    Object expected = gson.fromJson(gson.toJson(obj), Object.class);\n    assertEquals(expected, jsonV2.jsonGet(\"obj\"));\n\n    // check an update\n    Path2 p = Path2.of(\".str\");\n    jsonV2.jsonSet(\"obj\", p, gson.toJson(\"strung\"));\n    assertJsonArrayEquals(jsonArray(\"strung\"), jsonV2.jsonGet(\"obj\", p));\n  }\n\n  @Test\n  public void setExistingPathOnlyIfExistsShouldSucceed() {\n    jsonV2.jsonSetWithEscape(\"obj\", new IRLObject());\n    Path2 p = Path2.of(\".str\");\n    jsonV2.jsonSetWithEscape(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().xx());\n    assertJsonArrayEquals(jsonArray(\"strangle\"), jsonV2.jsonGet(\"obj\", p));\n  }\n\n  @Test\n  public void setNonExistingOnlyIfNotExistsShouldSucceed() {\n    jsonV2.jsonSet(\"obj\", gson.toJson(new IRLObject()));\n    Path2 p = Path2.of(\".none\");\n    jsonV2.jsonSet(\"obj\", p, gson.toJson(\"strangle\"), JsonSetParams.jsonSetParams().nx());\n    assertJsonArrayEquals(jsonArray(\"strangle\"), jsonV2.jsonGet(\"obj\", p));\n  }\n\n  @Test\n  public void setWithoutAPathDefaultsToRootPath() {\n    String objStr = gson.toJson(new IRLObject());\n    jsonV2.jsonSet(\"obj1\", new JSONObject(objStr));\n//    jsonClient.jsonSet(\"obj1\", \"strangle\", JsonSetParams.jsonSetParams().xx());\n    jsonV2.jsonSetWithEscape(\"obj1\", (Object) \"strangle\", JsonSetParams.jsonSetParams().xx());\n    assertJsonArrayEquals(jsonArray(\"strangle\"), jsonV2.jsonGet(\"obj1\", ROOT_PATH));\n  }\n\n  @Test\n  public void setExistingPathOnlyIfNotExistsShouldFail() {\n    jsonV2.jsonSetWithEscape(\"obj\", new IRLObject());\n    Path2 p = Path2.of(\".str\");\n    assertNull(jsonV2.jsonSetWithEscape(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().nx()));\n  }\n\n  @Test\n  public void setNonExistingPathOnlyIfExistsShouldFail() {\n    jsonV2.jsonSetWithEscape(\"obj\", new IRLObject());\n    Path2 p = Path2.of(\".none\");\n    assertNull(jsonV2.jsonSetWithEscape(\"obj\", p, \"strangle\", JsonSetParams.jsonSetParams().xx()));\n  }\n\n  @Test\n  public void setException() {\n    // should error on non root path for new key\n    assertThrows(JedisDataException.class, () -> jsonV2.jsonSet(\"test\", Path2.of(\".foo\"), \"bar\"));\n  }\n\n  @Test\n  public void getMultiplePathsShouldSucceed() {\n    // check multiple paths\n    IRLObject obj = new IRLObject();\n    jsonV2.jsonSetWithEscape(\"obj\", obj);\n    JSONObject result = (JSONObject) jsonV2.jsonGet(\"obj\", Path2.of(\"bool\"), Path2.of(\"str\"));\n    assertJsonArrayEquals(jsonArray(true), result.get(\"$.bool\"));\n    assertJsonArrayEquals(jsonArray(\"string\"), result.get(\"$.str\"));\n  }\n\n  @Test\n  public void getMultiLevels() {\n    JSONObject obj = new JSONObject();\n    obj.put(\"foo\", \"John\");\n    JSONObject inner = new JSONObject();\n    inner.put(\"foo\", \"Jane\");\n    obj.put(\"bar\", inner);\n    jsonV2.jsonSet(\"multi\", obj);\n    assertJsonArrayEquals(jsonArray(\"John\", \"Jane\"), jsonV2.jsonGet(\"multi\", new Path2(\"..foo\")));\n  }\n\n  @Test\n  public void toggle() {\n\n    IRLObject obj = new IRLObject();\n    jsonV2.jsonSetWithEscape(\"obj\", obj);\n\n    Path2 pbool = Path2.of(\".bool\");\n    // check initial value\n    assertJsonArrayEquals(jsonArray(true), jsonV2.jsonGet(\"obj\", pbool));\n\n    // true -> false\n    jsonV2.jsonToggle(\"obj\", pbool);\n    assertJsonArrayEquals(jsonArray(false), jsonV2.jsonGet(\"obj\", pbool));\n\n    // false -> true\n    jsonV2.jsonToggle(\"obj\", pbool);\n    assertJsonArrayEquals(jsonArray(true), jsonV2.jsonGet(\"obj\", pbool));\n\n    // ignore non-boolean field\n    Path2 pstr = Path2.of(\".str\");\n    assertEquals(singletonList(null), jsonV2.jsonToggle(\"obj\", pstr));\n    assertJsonArrayEquals(jsonArray(\"string\"), jsonV2.jsonGet(\"obj\", pstr));\n  }\n\n  @Test\n  public void getAbsent() {\n    jsonV2.jsonSetWithEscape(\"test\", ROOT_PATH, \"foo\");\n    assertJsonArrayEquals(jsonArray(), jsonV2.jsonGet(\"test\", Path2.of(\".bar\")));\n  }\n\n  @Test\n  public void delValidShouldSucceed() {\n    // check deletion of a single path\n    jsonV2.jsonSetWithEscape(\"obj\", ROOT_PATH, new IRLObject());\n    assertEquals(1L, jsonV2.jsonDel(\"obj\", Path2.of(\".str\")));\n    assertTrue(client.exists(\"obj\"));\n\n    // check deletion root using default root -> key is removed\n    assertEquals(1L, jsonV2.jsonDel(\"obj\"));\n    assertFalse(client.exists(\"obj\"));\n  }\n\n  @Test\n  public void delNonExistingPathsAreIgnored() {\n    jsonV2.jsonSetWithEscape(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertEquals(0L, jsonV2.jsonDel(\"foobar\", Path2.of(\".foo[1]\")));\n  }\n\n  @Test\n  public void typeChecksShouldSucceed() {\n    jsonV2.jsonSet(\"foobar\", ROOT_PATH, new JSONObject(gson.toJson(new FooBarObject())));\n    assertEquals(singletonList(Object.class), jsonV2.jsonType(\"foobar\", ROOT_PATH));\n    assertEquals(singletonList(String.class), jsonV2.jsonType(\"foobar\", Path2.of(\".foo\")));\n    assertEquals(singletonList(int.class), jsonV2.jsonType(\"foobar\", Path2.of(\".fooI\")));\n    assertEquals(singletonList(float.class), jsonV2.jsonType(\"foobar\", Path2.of(\".fooF\")));\n    assertEquals(singletonList(List.class), jsonV2.jsonType(\"foobar\", Path2.of(\".fooArr\")));\n    assertEquals(singletonList(boolean.class), jsonV2.jsonType(\"foobar\", Path2.of(\".fooB\")));\n    assertEquals(Collections.emptyList(), jsonV2.jsonType(\"foobar\", Path2.of(\".fooErr\")));\n  }\n\n  @Test\n  public void testJsonMerge() {\n    // Test with root path\n    JSONObject json = new JSONObject(\"{\\\"person\\\":{\\\"name\\\":\\\"John Doe\\\",\\\"age\\\":25,\\\"address\\\":{\\\"home\\\":\\\"123 Main Street\\\"},\\\"phone\\\":\\\"123-456-7890\\\"}}\");\n    assertEquals(\"OK\", jsonV2.jsonSet(\"test_merge\", json));\n\n    json = new JSONObject(\"{\\\"person\\\":{\\\"name\\\":\\\"John Doe\\\",\\\"age\\\":30,\\\"address\\\":{\\\"home\\\":\\\"123 Main Street\\\"},\\\"phone\\\":\\\"123-456-7890\\\"}}\");\n    assertEquals(\"OK\", jsonV2.jsonMerge(\"test_merge\", Path2.of(\"$\"), \"{\\\"person\\\":{\\\"age\\\":30}}\"));\n\n    assertJsonArrayEquals(jsonArray(json), jsonV2.jsonGet(\"test_merge\", Path2.of(\"$\")));\n\n    // Test with root path path $.a.b\n    assertEquals(\"OK\", jsonV2.jsonMerge(\"test_merge\", Path2.of(\"$.person.address\"), \"{\\\"work\\\":\\\"Redis office\\\"}\"));\n    json = new JSONObject(\"{\\\"person\\\":{\\\"name\\\":\\\"John Doe\\\",\\\"age\\\":30,\\\"address\\\":{\\\"home\\\":\\\"123 Main Street\\\",\\\"work\\\":\\\"Redis office\\\"},\\\"phone\\\":\\\"123-456-7890\\\"}}\");\n    assertJsonArrayEquals(jsonArray(json), jsonV2.jsonGet(\"test_merge\", Path2.of(\"$\")));\n\n    // Test with null value to delete a value\n    assertEquals(\"OK\", jsonV2.jsonMerge(\"test_merge\", Path2.of(\"$.person\"), \"{\\\"age\\\":null}\"));\n    json = new JSONObject(\"{\\\"person\\\":{\\\"name\\\":\\\"John Doe\\\",\\\"address\\\":{\\\"home\\\":\\\"123 Main Street\\\",\\\"work\\\":\\\"Redis office\\\"},\\\"phone\\\":\\\"123-456-7890\\\"}}\");\n    assertJsonArrayEquals(jsonArray(json), jsonV2.jsonGet(\"test_merge\", Path2.of(\"$\")));\n\n    // cleanup\n    assertEquals(1L, client.del(\"test_merge\"));\n  }\n\n  @Test\n  public void testJsonMergeArray()\n  {\n    // Test merge on an array\n    JSONObject json = new JSONObject(\"{\\\"a\\\":{\\\"b\\\":{\\\"c\\\":[\\\"d\\\",\\\"e\\\"]}}}\");\n    assertEquals(\"OK\", jsonV2.jsonSet(\"test_merge_array\", Path2.of(\"$\"), json));\n    assertEquals(\"OK\", jsonV2.jsonMerge(\"test_merge_array\", Path2.of(\"$.a.b.c\"), \"[\\\"f\\\"]\"));\n\n    json = new JSONObject(\"{\\\"a\\\":{\\\"b\\\":{\\\"c\\\":[\\\"f\\\"]}}}\");\n    assertJsonArrayEquals(jsonArray(json), jsonV2.jsonGet(\"test_merge_array\", Path2.of(\"$\")));\n\n    // assertEquals(\"{{a={b={c=[f]}}}\", jsonClient.jsonGet(\"test_merge_array\", Path2.of(\"$\")));\n\n    // Test merge an array on a value\n    assertEquals(\"OK\", jsonV2.jsonSet(\"test_merge_array\", Path2.of(\"$\"), \"{\\\"a\\\":{\\\"b\\\":{\\\"c\\\":\\\"d\\\"}}}\"));\n    assertEquals(\"OK\", jsonV2.jsonMerge(\"test_merge_array\", Path2.of(\"$.a.b.c\"), \"[\\\"f\\\"]\"));\n    json = new JSONObject(\"{\\\"a\\\":{\\\"b\\\":{\\\"c\\\":[\\\"f\\\"]}}}\");\n    assertJsonArrayEquals(jsonArray(json), jsonV2.jsonGet(\"test_merge_array\", Path2.of(\"$\")));\n\n    // Test with null value to delete an array value\n    assertEquals(\"OK\", jsonV2.jsonSet(\"test_merge_array\", Path2.of(\"$\"), \"{\\\"a\\\":{\\\"b\\\":{\\\"c\\\":[\\\"d\\\",\\\"e\\\"]}}}\"));\n    assertEquals(\"OK\", jsonV2.jsonMerge(\"test_merge_array\", Path2.of(\"$.a.b\"), \"{\\\"c\\\":null}\"));\n    json = new JSONObject(\"{\\\"a\\\":{\\\"b\\\":{}}}\");\n    assertJsonArrayEquals(jsonArray(json), jsonV2.jsonGet(\"test_merge_array\", Path2.of(\"$\")));\n  }\n\n  @Test\n  public void mgetWithPathWithAllKeysExist() {\n    Baz baz1 = new Baz(\"quuz1\", \"grault1\", \"waldo1\");\n    Baz baz2 = new Baz(\"quuz2\", \"grault2\", \"waldo2\");\n    Qux qux1 = new Qux(\"quux1\", \"corge1\", \"garply1\", baz1);\n    Qux qux2 = new Qux(\"quux2\", \"corge2\", \"garply2\", baz2);\n\n    jsonV2.jsonSet(\"qux1\", new JSONObject(gson.toJson(qux1)));\n    jsonV2.jsonSet(\"qux2\", new JSONObject(gson.toJson(qux2)));\n\n    List<JSONArray> list = jsonV2.jsonMGet(Path2.of(\"baz\"), \"qux1\", \"qux2\");\n    assertEquals(2, list.size());\n    assertJsonArrayEquals(jsonArray(new JSONObject(gson.toJson(baz1))), list.get(0));\n    assertJsonArrayEquals(jsonArray(new JSONObject(gson.toJson(baz2))), list.get(1));\n  }\n\n  @Test\n  public void mgetAtRootPathWithMissingKeys() {\n    Baz baz1 = new Baz(\"quuz1\", \"grault1\", \"waldo1\");\n    Baz baz2 = new Baz(\"quuz2\", \"grault2\", \"waldo2\");\n    Qux qux1 = new Qux(\"quux1\", \"corge1\", \"garply1\", baz1);\n    Qux qux2 = new Qux(\"quux2\", \"corge2\", \"garply2\", baz2);\n\n    jsonV2.jsonSetWithEscape(\"qux1\", qux1);\n    jsonV2.jsonSetWithEscape(\"qux2\", qux2);\n\n    List<JSONArray> list = jsonV2.jsonMGet(\"qux1\", \"qux2\", \"qux3\");\n\n    assertEquals(3, list.size());\n    assertNull(list.get(2));\n    list.removeAll(singletonList(null));\n    assertEquals(2, list.size());\n  }\n\n  @Test\n  public void arrLen() {\n    jsonV2.jsonSet(\"arr\", ROOT_PATH, new JSONArray(new int[]{0, 1, 2, 3, 4}));\n    assertEquals(singletonList(5L), jsonV2.jsonArrLen(\"arr\", ROOT_PATH));\n  }\n\n  @Test\n  public void clearArray() {\n    jsonV2.jsonSet(\"foobar\", ROOT_PATH, gson.toJson(new FooBarObject()));\n\n    Path2 arrPath = Path2.of(\".fooArr\");\n    assertEquals(singletonList(3L), jsonV2.jsonArrLen(\"foobar\", arrPath));\n\n    assertEquals(1L, jsonV2.jsonClear(\"foobar\", arrPath));\n    assertEquals(singletonList(0L), jsonV2.jsonArrLen(\"foobar\", arrPath));\n\n    // ignore non-array\n    Path2 strPath = Path2.of(\".foo\");\n    assertEquals(0L, jsonV2.jsonClear(\"foobar\", strPath));\n    assertJsonArrayEquals(jsonArray(\"bar\"), jsonV2.jsonGet(\"foobar\", strPath));\n  }\n\n  @Test\n  public void clearObject() {\n    Baz baz = new Baz(\"quuz\", \"grault\", \"waldo\");\n    Qux qux = new Qux(\"quux\", \"corge\", \"garply\", baz);\n\n    jsonV2.jsonSet(\"qux\", gson.toJson(qux));\n    Path2 objPath = Path2.of(\".baz\");\n//    assertEquals(baz, jsonClient.jsonGet(\"qux\", objPath));\n\n    assertEquals(1L, jsonV2.jsonClear(\"qux\", objPath));\n//    assertEquals(new Baz(null, null, null), jsonClient.jsonGet(\"qux\", objPath));\n    assertJsonArrayEquals(jsonArray(new JSONObject()), jsonV2.jsonGet(\"qux\", objPath));\n  }\n\n  @Test\n  public void arrAppendSameType() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    jsonV2.jsonSet(\"test_arrappend\", ROOT_PATH, new JSONObject(json));\n    assertEquals(singletonList(6L), jsonV2.jsonArrAppend(\"test_arrappend\", Path2.of(\".b\"), 4, 5, 6));\n\n    assertJsonArrayEquals(jsonArray(jsonArray(1, 2, 3, 4, 5, 6)), jsonV2.jsonGet(\"test_arrappend\", Path2.of(\".b\")));\n  }\n\n  @Test\n  public void arrAppendMultipleTypes() {\n    Object fooObject = gson.toJson(\"foo\");\n    Object trueObject = gson.toJson(true);\n    Object nullObject = gson.toJson(null);\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    jsonV2.jsonSet(\"test_arrappend\", ROOT_PATH, new JSONObject(json));\n    assertEquals(singletonList(6L), jsonV2.jsonArrAppend(\"test_arrappend\", Path2.of(\".b\"), fooObject, trueObject, nullObject));\n\n    assertJsonArrayEquals(jsonArray(jsonArray(1, 2, 3, \"foo\", true, null)), jsonV2.jsonGet(\"test_arrappend\", Path2.of(\".b\")));\n  }\n\n  @Test\n  public void arrAppendMultipleTypesWithDeepPath() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    jsonV2.jsonSet(\"test_arrappend\", ROOT_PATH, new JSONObject(json));\n    assertEquals(singletonList(4L), jsonV2.jsonArrAppendWithEscape(\"test_arrappend\", Path2.of(\".c.d\"), \"foo\", true, null));\n\n    assertJsonArrayEquals(jsonArray(jsonArray(\"ello\", \"foo\", true, null)), jsonV2.jsonGet(\"test_arrappend\", Path2.of(\".c.d\")));\n  }\n\n  @Test\n  public void arrAppendAgaintsEmptyArray() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: [] }}\";\n    jsonV2.jsonSet(\"test_arrappend\", ROOT_PATH, new JSONObject(json));\n    assertEquals(singletonList(3L), jsonV2.jsonArrAppendWithEscape(\"test_arrappend\", Path2.of(\".c.d\"), \"a\", \"b\", \"c\"));\n\n    assertJsonArrayEquals(jsonArray(jsonArray(\"a\", \"b\", \"c\")), jsonV2.jsonGet(\"test_arrappend\", Path2.of(\".c.d\")));\n  }\n\n  @Test\n  public void arrAppendPathIsNotArray() {\n    String json = \"{ a: 'hello', b: [1, 2, 3], c: { d: ['ello'] }}\";\n    jsonV2.jsonSet(\"test_arrappend\", ROOT_PATH, new JSONObject(json));\n    assertEquals(singletonList(null), jsonV2.jsonArrAppend(\"test_arrappend\", Path2.of(\".a\"), 1));\n    assertEquals(singletonList(null), jsonV2.jsonArrAppend(\"test_arrappend\", Path2.of(\".a\"), gson.toJson(1)));\n    assertEquals(singletonList(null), jsonV2.jsonArrAppendWithEscape(\"test_arrappend\", Path2.of(\".a\"), 1));\n  }\n\n  @Test\n  public void arrIndexAbsentKey() {\n    assertThrows(JedisDataException.class,\n        () -> jsonV2.jsonArrIndexWithEscape(\"quxquux\", ROOT_PATH, new JSONObject()));\n  }\n\n  @Test\n  public void arrIndexWithInts() {\n    jsonV2.jsonSetWithEscape(\"quxquux\", ROOT_PATH, new int[]{8, 6, 7, 5, 3, 0, 9});\n    assertEquals(singletonList(2L), jsonV2.jsonArrIndexWithEscape(\"quxquux\", ROOT_PATH, 7));\n    assertEquals(singletonList(-1L), jsonV2.jsonArrIndexWithEscape(\"quxquux\", ROOT_PATH, \"7\"));\n  }\n\n  @Test\n  public void arrIndexWithStrings() {\n    jsonV2.jsonSetWithEscape(\"quxquux\", ROOT_PATH, new String[]{\"8\", \"6\", \"7\", \"5\", \"3\", \"0\", \"9\"});\n    assertEquals(singletonList(2L), jsonV2.jsonArrIndexWithEscape(\"quxquux\", ROOT_PATH, \"7\"));\n  }\n\n  @Test\n  public void arrIndexWithStringsAndPath() {\n    jsonV2.jsonSetWithEscape(\"foobar\", ROOT_PATH, new FooBarObject());\n    assertEquals(singletonList(1L), jsonV2.jsonArrIndexWithEscape(\"foobar\", Path2.of(\".fooArr\"), \"b\"));\n  }\n\n  @Test\n  public void arrIndexNonExistentPath() {\n    jsonV2.jsonSet(\"foobar\", ROOT_PATH, gson.toJson(new FooBarObject()));\n    assertEquals(Collections.emptyList(), jsonV2.jsonArrIndex(\"foobar\", Path2.of(\".barArr\"), gson.toJson(\"x\")));\n  }\n\n  @Test\n  public void arrInsert() {\n    String json = \"['hello', 'world', true, 1, 3, null, false]\";\n    jsonV2.jsonSet(\"test_arrinsert\", ROOT_PATH, new JSONArray(json));\n    assertEquals(singletonList(8L), jsonV2.jsonArrInsertWithEscape(\"test_arrinsert\", ROOT_PATH, 1, \"foo\"));\n\n    assertJsonArrayEquals(jsonArray(jsonArray(\"hello\", \"foo\", \"world\", true, 1, 3, null, false)),\n        jsonV2.jsonGet(\"test_arrinsert\", ROOT_PATH));\n  }\n\n  @Test\n  public void arrInsertWithNegativeIndex() {\n    String json = \"['hello', 'world', true, 1, 3, null, false]\";\n    jsonV2.jsonSet(\"test_arrinsert\", ROOT_PATH, new JSONArray(json));\n    assertEquals(singletonList(8L), jsonV2.jsonArrInsertWithEscape(\"test_arrinsert\", ROOT_PATH, -1, \"foo\"));\n\n    assertJsonArrayEquals(jsonArray(jsonArray(\"hello\", \"world\", true, 1, 3, null, \"foo\", false)),\n        jsonV2.jsonGet(\"test_arrinsert\", ROOT_PATH));\n  }\n\n  @Test\n  public void arrPop() {\n    jsonV2.jsonSet(\"arr\", ROOT_PATH, new JSONArray(new int[]{0, 1, 2, 3, 4}));\n    assertEquals(singletonList(4d), jsonV2.jsonArrPop(\"arr\", ROOT_PATH));\n    assertEquals(singletonList(3d), jsonV2.jsonArrPop(\"arr\", ROOT_PATH, -1));\n    assertEquals(singletonList(0d), jsonV2.jsonArrPop(\"arr\", ROOT_PATH, 0));\n  }\n\n  @Test\n  public void arrTrim() {\n//    jsonClient.jsonSet(\"arr\", ROOT_PATH, new int[]{0, 1, 2, 3, 4});\n    jsonV2.jsonSet(\"arr\", ROOT_PATH, new JSONArray(new int[]{0, 1, 2, 3, 4}));\n    assertEquals(singletonList(3L), jsonV2.jsonArrTrim(\"arr\", ROOT_PATH, 1, 3));\n//    assertArrayEquals(new Integer[]{1, 2, 3}, jsonClient.jsonGet(\"arr\", Integer[].class, ROOT_PATH));\n    assertJsonArrayEquals(jsonArray(jsonArray(1, 2, 3)), jsonV2.jsonGet(\"arr\", ROOT_PATH));\n  }\n\n  @Test\n  public void strAppend() {\n//    jsonClient.jsonSet(\"str\", ROOT_PATH, \"foo\");\n    jsonV2.jsonSet(\"str\", ROOT_PATH, gson.toJson(\"foo\"));\n    assertEquals(singletonList(6L), jsonV2.jsonStrAppend(\"str\", ROOT_PATH, \"bar\"));\n    assertJsonArrayEquals(jsonArray(\"foobar\"), jsonV2.jsonGet(\"str\", ROOT_PATH));\n  }\n\n  @Test\n  public void strLen() {\n    jsonV2.jsonSetWithEscape(\"str\", \"foobar\");\n    assertEquals(singletonList(6L), jsonV2.jsonStrLen(\"str\", ROOT_PATH));\n  }\n\n  @Test\n  public void numIncrBy() {\n    assumeFalse(protocol == RedisProtocol.RESP3);\n    jsonV2.jsonSet(\"doc\", \"{\\\"a\\\":\\\"b\\\",\\\"b\\\":[{\\\"a\\\":2}, {\\\"a\\\":5}, {\\\"a\\\":\\\"c\\\"}]}\");\n    assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\".a\"), 1d));\n    assertJsonArrayEquals(jsonArray(null, 4, 7, null), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\"..a\"), 2d));\n    assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\"..b\"), 0d));\n    assertJsonArrayEquals(jsonArray(), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\"..c\"), 0d));\n  }\n\n  @Test\n  public void numIncrByResp3() {\n    assumeTrue(protocol == RedisProtocol.RESP3);\n    jsonV2.jsonSet(\"doc\", \"{\\\"a\\\":\\\"b\\\",\\\"b\\\":[{\\\"a\\\":2}, {\\\"a\\\":5}, {\\\"a\\\":\\\"c\\\"}]}\");\n    assertEquals(singletonList((Object) null), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\".a\"), 1d));\n    assertEquals(Arrays.asList(null, 4d, 7d, null), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\"..a\"), 2d));\n    assertEquals(singletonList((Object) null), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\"..b\"), 0d));\n    assertEquals(Collections.emptyList(), jsonV2.jsonNumIncrBy(\"doc\", Path2.of(\"..c\"), 0d));\n  }\n\n  @Test\n  public void obj() {\n    String json = \"{\\\"a\\\":[3], \\\"nested\\\": {\\\"a\\\": {\\\"b\\\":2, \\\"c\\\": 1}}}\";\n    jsonV2.jsonSet(\"doc\", ROOT_PATH, json);\n    assertEquals(Arrays.asList(2L), jsonV2.jsonObjLen(\"doc\", ROOT_PATH));\n    assertEquals(Arrays.asList(Arrays.asList(\"a\", \"nested\")), jsonV2.jsonObjKeys(\"doc\", ROOT_PATH));\n    assertEquals(Arrays.asList(null, 2L), jsonV2.jsonObjLen(\"doc\", Path2.of(\"..a\")));\n    assertEquals(Arrays.asList(null, Arrays.asList(\"b\", \"c\")), jsonV2.jsonObjKeys(\"doc\", Path2.of(\"..a\")));\n  }\n\n  @Test\n  public void debugMemory() {\n    assertEquals(Collections.emptyList(), jsonV2.jsonDebugMemory(\"json\", ROOT_PATH));\n\n    jsonV2.jsonSet(\"json\", new JSONObject(\"{ foo: 'bar', bar: { foo: 10 }}\"));\n    assertEquals(1, jsonV2.jsonDebugMemory(\"json\", ROOT_PATH).size());\n    assertEquals(2, jsonV2.jsonDebugMemory(\"json\", Path2.of(\"$..foo\")).size());\n    assertEquals(1, jsonV2.jsonDebugMemory(\"json\", Path2.of(\"$..bar\")).size());\n  }\n\n  private void assertJsonArrayEquals(JSONArray a, Object _b) {\n    if (!(_b instanceof JSONArray)) {\n      fail(\"Actual value is not JSONArray.\");\n    }\n    JSONArray b = (JSONArray) _b;\n    assertEquals(a.length(), b.length(), \"JSONArray length mismatch\");\n    int length = a.length();\n    for (int index = 0; index < length; index++) {\n      if (a.isNull(index)) {\n        assertTrue(b.isNull(index), index + \"'th element is not null\");\n        continue;\n      }\n      Object ia = a.get(index);\n      Object ib = b.get(index);\n      if (ia instanceof JSONArray) {\n        assertJsonArrayEquals((JSONArray) ia, ib);\n      } else if (ia instanceof JSONObject) {\n        assertJsonObjectEquals((JSONObject) ia, ib);\n      } else if (ia instanceof Number && ib instanceof Number) {\n        assertEquals(((Number) ia).doubleValue(), ((Number) ib).doubleValue(), 0d,\n            index + \"'th element mismatch\");\n      } else {\n        assertEquals(ia, ib, index + \"'th element mismatch\");\n      }\n    }\n  }\n\n  private void assertJsonObjectEquals(JSONObject a, Object _b) {\n    if (!(_b instanceof JSONObject)) {\n      fail(\"Actual value is not JSONObject.\");\n    }\n    JSONObject b = (JSONObject) _b;\n    assertEquals(a.length(), b.length(), \"JSONObject length mismatch\");\n    assertEquals(a.keySet(), b.keySet());\n    for (String key : a.keySet()) {\n      if (a.isNull(key)) {\n        assertTrue(b.isNull(key), key + \"'s value is not null\");\n        continue;\n      }\n      Object oa = a.get(key);\n      Object ob = b.get(key);\n      if (oa instanceof JSONArray) {\n        assertJsonArrayEquals((JSONArray) oa, ob);\n      } else if (oa instanceof JSONObject) {\n        assertJsonObjectEquals((JSONObject) oa, ob);\n      } else {\n        assertEquals(oa, ob, key + \"'s value mismatch\");\n      }\n    }\n  }\n\n  private static JSONArray jsonArray(Object... objects) {\n    JSONArray arr = new JSONArray();\n    for (Object o : objects) {\n      arr.put(o);\n    }\n    return arr;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/AggregationBuilderTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.search.aggr.Reducer;\nimport redis.clients.jedis.search.aggr.Reducers;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\npublic class AggregationBuilderTest {\n\n  @Test\n  public void reducerObject() {\n    Reducer reducer = Reducers.sum(\"@count\").as(\"total\");\n    assertEquals(\"SUM\", reducer.getName());\n    assertEquals(\"@count\", reducer.getField());\n    assertEquals(\"total\", reducer.getAlias());\n  }\n\n  @Test\n  public void countObject() {\n    Reducer count = Reducers.count();\n    assertEquals(\"COUNT\", count.getName());\n    assertNull(count.getField());\n    assertNull(count.getAlias());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/AggregationTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.util.RedisConditions.ModuleVersion.SEARCH_MOD_VER_80M3;\nimport static redis.clients.jedis.util.RedisConditions.ModuleVersion.SEARCH_MOD_VER_84RC1;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.utils.RedisVersion;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.aggr.*;\nimport redis.clients.jedis.search.schemafields.NumericField;\nimport redis.clients.jedis.search.schemafields.TextField;\nimport redis.clients.jedis.util.RedisConditions;\nimport redis.clients.jedis.util.RedisVersionUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class AggregationTest extends RedisModuleCommandsTestBase {\n\n  private static final String index = \"aggbindex\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public AggregationTest(RedisProtocol redisProtocol) {\n    super(redisProtocol);\n  }\n\n  private void addDocument(Document doc) {\n    String key = doc.getId();\n    Map<String, String> map = new LinkedHashMap<>();\n    doc.getProperties().forEach(entry -> map.put(entry.getKey(), String.valueOf(entry.getValue())));\n    client.hset(key, map);\n  }\n\n  private void addDocument(String key, Map<String, Object> objMap) {\n    Map<String, String> strMap = new HashMap<>();\n    objMap.entrySet().forEach(entry -> strMap.put(entry.getKey(), String.valueOf(entry.getValue())));\n    client.hset(key, strMap);\n  }\n\n  @Test\n  public void testAggregations() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n//    client.addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n//    client.addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n//    client.addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    AggregationBuilder r = new AggregationBuilder()\n        .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n        .sortBy(10, SortedField.desc(\"@sum\"));\n\n    // actual search\n    AggregationResult res = client.ftAggregate(index, r);\n    assertEquals(2, res.getTotalResults());\n\n    Row r1 = res.getRow(0);\n    assertNotNull(r1);\n    assertEquals(\"def\", r1.getString(\"name\"));\n    assertEquals(30, r1.getLong(\"sum\"));\n    assertEquals(30., r1.getDouble(\"sum\"), 0);\n\n    assertEquals(0L, r1.getLong(\"nosuchcol\"));\n    assertEquals(0.0, r1.getDouble(\"nosuchcol\"), 0);\n    assertEquals(\"\", r1.getString(\"nosuchcol\"));\n\n    Row r2 = res.getRow(1);\n    assertNotNull(r2);\n    assertEquals(\"abc\", r2.getString(\"name\"));\n    assertEquals(10, r2.getLong(\"sum\"));\n  }\n\n  @Test\n  public void testAggregations2() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    AggregationBuilder r = new AggregationBuilder()\n        .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n        .sortBy(10, SortedField.desc(\"@sum\"));\n\n    // actual search\n    AggregationResult res = client.ftAggregate(index, r);\n    assertEquals(2, res.getTotalResults());\n\n    List<Row> rows = res.getRows();\n    assertEquals(\"def\", rows.get(0).get(\"name\"));\n    assertEquals(\"30\", rows.get(0).get(\"sum\"));\n    assertNull(rows.get(0).get(\"nosuchcol\"));\n\n    assertEquals(\"abc\", rows.get(1).get(\"name\"));\n    assertEquals(\"10\", rows.get(1).get(\"sum\"));\n  }\n\n  @Test\n  public void testAggregations2Profile() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    AggregationBuilder aggr = new AggregationBuilder()\n        .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n        .sortBy(10, SortedField.desc(\"@sum\"));\n\n    Map.Entry<AggregationResult, ProfilingInfo> reply\n        = client.ftProfileAggregate(index, FTProfileParams.profileParams(), aggr);\n\n    // actual search\n    AggregationResult result = reply.getKey();\n    assertEquals(2, result.getTotalResults());\n\n    List<Row> rows = result.getRows();\n    assertEquals(\"def\", rows.get(0).get(\"name\"));\n    assertEquals(\"30\", rows.get(0).get(\"sum\"));\n    assertNull(rows.get(0).get(\"nosuchcol\"));\n\n    assertEquals(\"abc\", rows.get(1).get(\"name\"));\n    assertEquals(\"10\", rows.get(1).get(\"sum\"));\n\n    // profile\n    Object profileObject = reply.getValue().getProfilingInfo();\n    if (protocol != RedisProtocol.RESP3) {\n      assertThat(profileObject, Matchers.isA(List.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0_PRE)) {\n        assertThat((List<Object>) profileObject, Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    } else {\n      assertThat(profileObject, Matchers.isA(Map.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0_PRE)) {\n        assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    }\n  }\n\n  @Test\n  public void testAggregationBuilderVerbatim() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n    addDocument(new Document(\"data1\").set(\"name\", \"hello kitty\"));\n\n    AggregationBuilder r = new AggregationBuilder(\"kitti\");\n\n    AggregationResult res = client.ftAggregate(index, r);\n    assertEquals(1, res.getTotalResults());\n\n    r = new AggregationBuilder(\"kitti\")\n            .verbatim();\n\n    res = client.ftAggregate(index, r);\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"ADDSCORES\")\n  public void testAggregationBuilderAddScores() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"age\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n    addDocument(new Document(\"data1\").set(\"name\", \"Adam\").set(\"age\", 33));\n    addDocument(new Document(\"data2\").set(\"name\", \"Sara\").set(\"age\", 44));\n\n    AggregationBuilder r = new AggregationBuilder(\"sara\").addScores()\n        .apply(\"@__score * 100\", \"normalized_score\").dialect(3);\n\n    AggregationResult res = client.ftAggregate(index, r);\n    if (RedisConditions.of(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3)) {\n      // Default scorer is BM25\n      assertEquals(0.6931, res.getRow(0).getDouble(\"__score\"), 0.0001);\n      assertEquals(69.31, res.getRow(0).getDouble(\"normalized_score\"), 0.01);\n    } else {\n      // Default scorer is TF-IDF\n      assertEquals(2, res.getRow(0).getLong(\"__score\"));\n      assertEquals(200, res.getRow(0).getLong(\"normalized_score\"));\n    }\n  }\n\n  @Test\n  public void testAggregationBuilderTimeout() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    AggregationBuilder r = new AggregationBuilder()\n            .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n            .timeout(5000);\n\n    AggregationResult res = client.ftAggregate(index, r);\n    assertEquals(2, res.getTotalResults());\n  }\n\n  @Test\n  public void testAggregationBuilderParamsDialect() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"name\", \"abc\");\n\n    AggregationBuilder r = new AggregationBuilder(\"$name\")\n            .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n            .params(params)\n            .dialect(2); // From documentation - To use PARAMS, DIALECT must be set to 2\n\n    AggregationResult res = client.ftAggregate(index, r);\n    assertEquals(1, res.getTotalResults());\n\n    Row r1 = res.getRow(0);\n    assertNotNull(r1);\n    assertEquals(\"abc\", r1.getString(\"name\"));\n    assertEquals(10, r1.getLong(\"sum\"));\n  }\n\n  @Test\n  public void testApplyAndFilterAggregations() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"subj1\");\n    sc.addSortableNumericField(\"subj2\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"subj1\", 20).set(\"subj2\", 70));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"subj1\", 60).set(\"subj2\", 40));\n    addDocument(new Document(\"data3\").set(\"name\", \"ghi\").set(\"subj1\", 50).set(\"subj2\", 80));\n    addDocument(new Document(\"data4\").set(\"name\", \"abc\").set(\"subj1\", 30).set(\"subj2\", 20));\n    addDocument(new Document(\"data5\").set(\"name\", \"def\").set(\"subj1\", 65).set(\"subj2\", 45));\n    addDocument(new Document(\"data6\").set(\"name\", \"ghi\").set(\"subj1\", 70).set(\"subj2\", 70));\n\n    AggregationBuilder r = new AggregationBuilder().apply(\"(@subj1+@subj2)/2\", \"attemptavg\")\n        .groupBy(\"@name\", Reducers.avg(\"@attemptavg\").as(\"avgscore\"))\n        .filter(\"@avgscore>=50\")\n        .sortBy(10, SortedField.asc(\"@name\"));\n\n    // actual search\n    AggregationResult res = client.ftAggregate(index, r);\n\n    if (RedisConditions.of(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_84RC1)) {\n      //prior to 8.4rc1, the returned total result was reported as 3 (number of results before filter),\n      // while 2 rows were actually returned\n      assertEquals(2, res.getTotalResults());\n    }\n\n    Row r1 = res.getRow(0);\n    assertNotNull(r1);\n    assertEquals(\"def\", r1.getString(\"name\"));\n    assertEquals(52.5, r1.getDouble(\"avgscore\"), 0);\n\n    Row r2 = res.getRow(1);\n    assertNotNull(r2);\n    assertEquals(\"ghi\", r2.getString(\"name\"));\n    assertEquals(67.5, r2.getDouble(\"avgscore\"), 0);\n  }\n\n  @Test\n  public void load() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"subj1\");\n    sc.addSortableNumericField(\"subj2\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n//    client.addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"subj1\", 20).set(\"subj2\", 70));\n//    client.addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"subj1\", 60).set(\"subj2\", 40));\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"subj1\", 20).set(\"subj2\", 70));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"subj1\", 60).set(\"subj2\", 40));\n\n    AggregationBuilder builder = new AggregationBuilder()\n        .load(FieldName.of(\"@subj1\").as(\"a\"), FieldName.of(\"@subj2\").as(\"b\"))\n        .apply(\"(@a+@b)/2\", \"avg\").sortByDesc(\"@avg\");\n\n    AggregationResult result = client.ftAggregate(index, builder);\n    assertEquals(50.0, result.getRow(0).getDouble(\"avg\"), 0d);\n    assertEquals(45.0, result.getRow(1).getDouble(\"avg\"), 0d);\n  }\n\n  @Test\n  public void loadAll() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"subj1\");\n    sc.addSortableNumericField(\"subj2\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"subj1\", 20).set(\"subj2\", 70));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"subj1\", 60).set(\"subj2\", 40));\n\n    AggregationBuilder builder = new AggregationBuilder()\n        .loadAll()\n        .apply(\"(@subj1+@subj2)/2\", \"avg\").sortByDesc(\"@avg\");\n\n    AggregationResult result = client.ftAggregate(index, builder);\n    assertEquals(50.0, result.getRow(0).getDouble(\"avg\"), 0d);\n    assertEquals(45.0, result.getRow(1).getDouble(\"avg\"), 0d);\n  }\n\n  @Test\n  public void cursor() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n//    client.addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n//    client.addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n//    client.addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    AggregationBuilder r = new AggregationBuilder()\n        .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n        .sortBy(10, SortedField.desc(\"@sum\"))\n        .cursor(1, 3000);\n\n    // actual search\n    AggregationResult res = client.ftAggregate(index, r);\n    assertEquals(2, res.getTotalResults());\n\n    Row row = res.getRow(0);\n    assertNotNull(row);\n    assertEquals(\"def\", row.getString(\"name\"));\n    assertEquals(30, row.getLong(\"sum\"));\n    assertEquals(30., row.getDouble(\"sum\"), 0);\n\n    assertEquals(0L, row.getLong(\"nosuchcol\"));\n    assertEquals(0.0, row.getDouble(\"nosuchcol\"), 0);\n    assertEquals(\"\", row.getString(\"nosuchcol\"));\n\n    res = client.ftCursorRead(index, res.getCursorId(), 1);\n    Row row2 = res.getRow(0);\n    assertNotNull(row2);\n    assertEquals(\"abc\", row2.getString(\"name\"));\n    assertEquals(10, row2.getLong(\"sum\"));\n\n    assertEquals(\"OK\", client.ftCursorDel(index, res.getCursorId()));\n\n    try {\n      client.ftCursorRead(index, res.getCursorId(), 1);\n      fail();\n    } catch (JedisDataException e) {\n      // ignore\n    }\n  }\n\n  @Test\n  public void aggregateIteration() {\n    client.ftCreate(index, TextField.of(\"name\").sortable(), NumericField.of(\"count\"));\n\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n    addDocument(new Document(\"data4\").set(\"name\", \"ghi\").set(\"count\", 15));\n    addDocument(new Document(\"data5\").set(\"name\", \"jkl\").set(\"count\", 20));\n\n    AggregationBuilder agg = new AggregationBuilder()\n        .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n        .sortBy(10, SortedField.desc(\"@sum\"))\n        .cursor(2, 10000);\n\n    FtAggregateIteration rr = client.ftAggregateIteration(index, agg);\n    int total = 0;\n    while (!rr.isIterationCompleted()) {\n      AggregationResult res = rr.nextBatch();\n      int count = res.getRows().size();\n      assertThat(count, Matchers.lessThanOrEqualTo(2));\n      total += count;\n    }\n    assertEquals(4, total);\n  }\n\n  @Test\n  public void aggregateIterationCollect() {\n    client.ftCreate(index, TextField.of(\"name\").sortable(), NumericField.of(\"count\"));\n\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n    addDocument(new Document(\"data4\").set(\"name\", \"ghi\").set(\"count\", 15));\n    addDocument(new Document(\"data5\").set(\"name\", \"jkl\").set(\"count\", 20));\n\n    AggregationBuilder agg = new AggregationBuilder()\n        .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n        .sortBy(10, SortedField.desc(\"@sum\"))\n        .cursor(2, 10000);\n\n    assertEquals(4, client.ftAggregateIteration(index, agg).collect(new ArrayList<>()).size());\n  }\n\n  @Test\n  public void testWrongAggregation() throws InterruptedException {\n    Schema sc = new Schema()\n        .addTextField(\"title\", 5.0)\n        .addTextField(\"body\", 1.0)\n        .addTextField(\"state\", 1.0)\n        .addNumericField(\"price\");\n\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    // insert document(s)\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"state\", \"NY\");\n    fields.put(\"body\", \"lorem ipsum\");\n    fields.put(\"price\", \"1337\");\n//    client.addDocument(\"doc1\", fields);\n    addDocument(\"doc1\", fields);\n\n    // wrong aggregation query\n    AggregationBuilder builder = new AggregationBuilder(\"hello\")\n        .apply(\"@price/1000\", \"k\")\n        .groupBy(\"@state\", Reducers.avg(\"@k\").as(\"avgprice\"))\n        .filter(\"@avgprice>=2\")\n        .sortBy(10, SortedField.asc(\"@state\"));\n\n    try {\n      client.ftAggregate(index, builder);\n      fail();\n    } catch (JedisDataException e) {\n      // should throw JedisDataException on wrong aggregation query \n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/CreateTest.java",
    "content": "//package redis.clients.jedis.modules.search;\n//\n//import static org.junit.jupiter.api.Assertions.*;\n//\n//import java.util.ArrayList;\n//import java.util.Arrays;\n//import java.util.List;\n//import org.junit.Test;\n//\n//import redis.clients.jedis.search.IndexDefinition;\n//import redis.clients.jedis.search.IndexOptions;\n//\n//public class CreateTest {\n//\n//  @Test\n//  public void defaultOptions() throws Exception {\n//    IndexOptions defaultOptions = IndexOptions.defaultOptions();\n//    List<String> arrayList = new ArrayList<>();\n//    defaultOptions.serializeRedisArgs(arrayList);\n//\n//    assertEquals(Arrays.asList(), arrayList);\n//  }\n//\n//  @Test\n//  public void allOptions() throws Exception {\n//    IndexOptions defaultOptions = new Client.IndexOptions(0);\n//\n//    defaultOptions.setStopwords(\"stop\", \"run\");\n//    defaultOptions.setTemporary(1234L);\n//    defaultOptions.setDefinition(new IndexDefinition());\n//\n//    List<String> arrayList = new ArrayList<>();\n//    defaultOptions.serializeRedisArgs(arrayList);\n//\n//    assertEquals(\n//        Arrays.asList(\"NOOFFSETS\", \"NOFIELDS\", \"NOFREQS\", \"TEMPORARY\", \"1234\", \"STOPWORDS\", \"2\", \"stop\", \"run\"),\n//        arrayList);\n//  }\n//\n//  @Test\n//  public void allIndexDefinition() throws Exception {\n//    IndexDefinition indexRule = new IndexDefinition(IndexDefinition.Type.HASH);\n//\n//    indexRule.setAsync(true);\n//    indexRule.setFilter(\"@sum<30\");\n//    indexRule.setLanguage(\"FR\");\n//    indexRule.setLanguageField(\"myLanguage\");\n//    indexRule.setPayloadField(\"myPayload\");\n//    indexRule.setPrefixes(\"person:\");\n//    indexRule.setScore(0.818656);\n//    indexRule.setScoreFiled(\"myScore\");\n//\n//    List<String> arrayList = new ArrayList<>();\n//    indexRule.serializeRedisArgs(arrayList);\n//\n//    assertEquals(Arrays.asList(\"ON\", \"HASH\", \"ASYNC\", \"PREFIX\", \"1\", \"person:\", \"FILTER\", \"@sum<30\", \"LANGUAGE_FIELD\",\n//        \"myLanguage\", \"LANGUAGE\", \"FR\", \"SCORE_FIELD\", \"myScore\", \"SCORE\", \"0.818656\", \"PAYLOAD_FIELD\", \"myPayload\"),\n//        arrayList);\n//\n//    assertEquals(true, indexRule.isAsync());\n//    assertEquals(\"@sum<30\", indexRule.getFilter());\n//    assertEquals(\"FR\", indexRule.getLanguage());\n//    assertEquals(\"myLanguage\", indexRule.getLanguageField());\n//    assertEquals(\"myPayload\", indexRule.getPayloadField());\n//    assertArrayEquals(new String[]{\"person:\"}, indexRule.getPrefixes());\n//    assertEquals(0.818656, indexRule.getScore(), 0.0);\n//    assertEquals(\"myScore\", indexRule.getScoreFiled());\n//    assertEquals(IndexDefinition.Type.HASH, indexRule.getType());\n//  }\n//}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/DocumentTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.search.Document;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class DocumentTest {\n\n  @Test\n  public void serialize() throws IOException, ClassNotFoundException {\n    String id = \"9f\";\n    double score = 10d;\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"string\", \"c\");\n    map.put(\"float\", 12d);\n    Document document = new Document(id, map, score);\n\n    ByteArrayOutputStream aos = new ByteArrayOutputStream();\n    ObjectOutputStream oos = new ObjectOutputStream(aos);\n    oos.writeObject(document);\n    oos.flush();\n    oos.close();\n\n    ByteArrayInputStream ais = new ByteArrayInputStream(aos.toByteArray());\n    ObjectInputStream ois = new ObjectInputStream(ais);\n    Document read = (Document) ois.readObject();\n    ois.close();\n\n    assertEquals(id, read.getId());\n    assertEquals(score, read.getScore(), 0d);\n    assertEquals(\"c\", read.getString(\"string\"));\n    assertEquals(12d, read.get(\"float\"));\n  }\n\n  @Test\n  public void toStringTest() {\n    String id = \"9f\";\n    double score = 10d;\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"string\", \"c\");\n    map.put(\"float\", 12d);\n    Document document = new Document(id, map, score);\n\n    // use english language to make sure the decimal separator is the same as the toString\n    String expected1 = String.format(Locale.ENGLISH, \"id:%s, score: %.1f, properties:%s\", id, score,\n        \"[string=c, float=12.0]\");\n    String expected2 = String.format(Locale.ENGLISH, \"id:%s, score: %.1f, properties:%s\", id, score,\n        \"[float=12.0, string=c]\");\n\n    // the order of the properties is not guaranteed, so we check both possible outcomes\n    String actual = document.toString();\n    assertTrue(actual.equals(expected1) || actual.equals(expected2));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/JsonSearchTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.json.JsonProtocol;\nimport redis.clients.jedis.json.Path2;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.Schema.*;\nimport redis.clients.jedis.search.SearchResult;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class JsonSearchTest extends RedisModuleCommandsTestBase {\n\n  public static final String JSON_ROOT = \"$\";\n\n  private static final String index = \"json-index\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public JsonSearchTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  private void setJson(String key, JSONObject json) {\n    CommandObject command = new CommandObject<>(\n        new CommandArguments(JsonProtocol.JsonCommand.SET).key(key).add(Path2.ROOT_PATH).add(json),\n        BuilderFactory.STRING);\n    client.executeCommand(command);\n  }\n\n  private JSONObject toJson(Object... values) {\n    JSONObject json = new JSONObject();\n    for (int i = 0; i < values.length; i += 2) {\n      json.put((String) values[i], values[i + 1]);\n    }\n    return json;\n  }\n\n  @Test\n  public void create() {\n    Schema schema = new Schema().addTextField(\"$.first\", 1.0).addTextField(\"$.last\", 1.0)\n        .addNumericField(\"$.age\");\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON)\n        .setPrefixes(new String[]{\"student:\", \"pupil:\"});\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n//    try (Jedis jedis = client.connection()) {\n//      setJson(jedis, \"profesor:5555\", toJson(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", 55));\n//      setJson(jedis, \"student:1111\", toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18));\n//      setJson(jedis, \"pupil:2222\", toJson(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", 14));\n//      setJson(jedis, \"student:3333\", toJson(\"first\", \"El\", \"last\", \"Mark\", \"age\", 17));\n//      setJson(jedis, \"pupil:4444\", toJson(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", 21));\n//      setJson(jedis, \"student:5555\", toJson(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", 20));\n//      setJson(jedis, \"teacher:6666\", toJson(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", 20));\n//    }\n    setJson(\"profesor:5555\", toJson(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", 55));\n    setJson(\"student:1111\", toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18));\n    setJson(\"pupil:2222\", toJson(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", 14));\n    setJson(\"student:3333\", toJson(\"first\", \"El\", \"last\", \"Mark\", \"age\", 17));\n    setJson(\"pupil:4444\", toJson(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", 21));\n    setJson(\"student:5555\", toJson(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", 20));\n    setJson(\"teacher:6666\", toJson(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", 20));\n\n    SearchResult noFilters = client.ftSearch(index, new Query());\n    assertEquals(5, noFilters.getTotalResults());\n\n    SearchResult res1 = client.ftSearch(index, new Query(\"@\\\\$\\\\.first:Jo*\"));\n    assertEquals(2, res1.getTotalResults());\n\n    SearchResult res2 = client.ftSearch(index, new Query(\"@\\\\$\\\\.first:Pat\"));\n    assertEquals(1, res2.getTotalResults());\n  }\n\n  @Test\n  public void createWithFieldNames() {\n    Schema schema = new Schema()\n        .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n        .addField(new TextField(FieldName.of(\"$.last\")))\n        .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON)\n        .setPrefixes(new String[]{\"student:\", \"pupil:\"});\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n//    try (Jedis jedis = client.connection()) {\n//      setJson(jedis, \"profesor:5555\", toJson(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", 55));\n//      setJson(jedis, \"student:1111\", toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18));\n//      setJson(jedis, \"pupil:2222\", toJson(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", 14));\n//      setJson(jedis, \"student:3333\", toJson(\"first\", \"El\", \"last\", \"Mark\", \"age\", 17));\n//      setJson(jedis, \"pupil:4444\", toJson(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", 21));\n//      setJson(jedis, \"student:5555\", toJson(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", 20));\n//      setJson(jedis, \"teacher:6666\", toJson(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", 20));\n//    }\n    setJson(\"profesor:5555\", toJson(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", 55));\n    setJson(\"student:1111\", toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18));\n    setJson(\"pupil:2222\", toJson(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", 14));\n    setJson(\"student:3333\", toJson(\"first\", \"El\", \"last\", \"Mark\", \"age\", 17));\n    setJson(\"pupil:4444\", toJson(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", 21));\n    setJson(\"student:5555\", toJson(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", 20));\n    setJson(\"teacher:6666\", toJson(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", 20));\n\n    SearchResult noFilters = client.ftSearch(index, new Query());\n    assertEquals(5, noFilters.getTotalResults());\n\n    SearchResult asAttribute = client.ftSearch(index, new Query(\"@first:Jo*\"));\n    assertEquals(2, asAttribute.getTotalResults());\n\n    SearchResult nonAttribute = client.ftSearch(index, new Query(\"@\\\\$\\\\.last:Rod\"));\n    assertEquals(1, nonAttribute.getTotalResults());\n  }\n\n  @Test\n  public void parseJson() {\n    Schema schema = new Schema()\n        .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n        .addField(new TextField(FieldName.of(\"$.last\")))\n        .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1111\";\n    JSONObject json = toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18);\n//    try (Jedis jedis = client.connection()) {\n//      setJson(jedis, id, json);\n//    }\n    setJson(id, json);\n\n    // query\n    SearchResult sr = client.ftSearch(index, new Query().setWithScores());\n    assertEquals(1, sr.getTotalResults());\n\n    Document doc = sr.getDocuments().get(0);\n    assertEquals(1.0, doc.getScore(), 0);\n    assertEquals(json.toString(), doc.get(JSON_ROOT));\n\n    // query repeat\n    sr = client.ftSearch(index, new Query().setWithScores());\n\n    doc = sr.getDocuments().get(0);\n    JSONObject jsonRead = new JSONObject((String) doc.get(JSON_ROOT));\n    assertEquals(json.toString(), jsonRead.toString());\n\n    // query repeat\n    sr = client.ftSearch(index, new Query().setWithScores());\n\n    doc = sr.getDocuments().get(0);\n    jsonRead = new JSONObject(doc.getString(JSON_ROOT));\n    assertEquals(json.toString(), jsonRead.toString());\n  }\n\n  @Test\n  public void parseJsonPartial() {\n    Schema schema = new Schema()\n                .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n                .addField(new TextField(FieldName.of(\"$.last\")))\n                .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1111\";\n    JSONObject json = toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18);\n//    try (Jedis jedis = client.connection()) {\n//      setJson(jedis, id, json);\n//    }\n    setJson(id, json);\n\n    // query\n    SearchResult sr = client.ftSearch(index, new Query().returnFields(\"$.first\", \"$.last\", \"$.age\"));\n    assertEquals(1, sr.getTotalResults());\n\n    Document doc = sr.getDocuments().get(0);\n    assertEquals(\"Joe\", doc.get(\"$.first\"));\n    assertEquals(\"Dod\", doc.get(\"$.last\"));\n    assertEquals(Integer.toString(18), doc.get(\"$.age\"));\n\n    // query repeat\n    sr = client.ftSearch(index, new Query().returnFields(\"$.first\", \"$.last\", \"$.age\"));\n\n    doc = sr.getDocuments().get(0);\n    assertEquals(\"Joe\", doc.getString(\"$.first\"));\n    assertEquals(\"Dod\", doc.getString(\"$.last\"));\n    assertEquals(18, Integer.parseInt((String) doc.get(\"$.age\")));\n  }\n\n  @Test\n  public void parseJsonPartialWithFieldNames() {\n    Schema schema = new Schema()\n                .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n                .addField(new TextField(FieldName.of(\"$.last\")))\n                .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1111\";\n    JSONObject json = toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18);\n//    try (Jedis jedis = client.connection()) {\n//      setJson(jedis, id, json);\n//    }\n    setJson(id, json);\n\n    // query\n    SearchResult sr = client.ftSearch(index, new Query().returnFields(FieldName.of(\"$.first\").as(\"first\"),\n        FieldName.of(\"$.last\").as(\"last\"), FieldName.of(\"$.age\")));\n    assertEquals(1, sr.getTotalResults());\n\n    Document doc = sr.getDocuments().get(0);\n    assertNull(doc.get(\"$.first\"));\n    assertNull(doc.get(\"$.last\"));\n    assertEquals(Integer.toString(18), doc.get(\"$.age\"));\n    assertEquals(\"Joe\", doc.get(\"first\"));\n    assertEquals(\"Dod\", doc.get(\"last\"));\n    assertNull(doc.get(\"age\"));\n  }\n\n  @Test\n  public void dialect() {\n    Schema schema = new Schema()\n            .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n            .addField(new TextField(FieldName.of(\"$.last\")))\n            .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1111\";\n    JSONObject json = toJson(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18);\n    setJson(id, json);\n\n    SearchResult sr = client.ftSearch(index, new Query().returnFields(FieldName.of(\"$.first\").as(\"first\"),\n            FieldName.of(\"$.last\").as(\"last\"), FieldName.of(\"$.age\")).dialect(1));\n    assertEquals(1, sr.getTotalResults());\n    assertEquals(\"Joe\", sr.getDocuments().get(0).get(\"first\"));\n    assertEquals(\"Dod\", sr.getDocuments().get(0).get(\"last\"));\n  }\n\n  @Test\n  public void slop() {\n    Schema schema = new Schema()\n            .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n            .addField(new TextField(FieldName.of(\"$.last\")))\n            .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1111\";\n    JSONObject json = toJson(\"first\", \"Joe is first ok\", \"last\", \"Dod will be first next\", \"age\", 18);\n    setJson(id, json);\n\n    SearchResult sr = client.ftSearch(index, new Query(\"Dod next\").returnFields(FieldName.of(\"$.first\").as(\"first\"),\n            FieldName.of(\"$.last\").as(\"last\"), FieldName.of(\"$.age\")).slop(0));\n    assertEquals(0, sr.getTotalResults());\n\n    sr = client.ftSearch(index, new Query(\"Dod next\").returnFields(FieldName.of(\"$.first\").as(\"first\"),\n            FieldName.of(\"$.last\").as(\"last\"), FieldName.of(\"$.age\")).slop(1));\n    assertEquals(1, sr.getTotalResults());\n  }\n\n  @Test\n  public void timeout() {\n    Schema schema = new Schema()\n            .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n            .addField(new TextField(FieldName.of(\"$.last\")))\n            .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1111\";\n    JSONObject json = toJson(\"first\", \"Joe is first ok\", \"last\", \"Dod will be first next\", \"age\", 18);\n    setJson(id, json);\n\n    SearchResult sr = client.ftSearch(index, new Query(\"Dod next\").returnFields(FieldName.of(\"$.first\").as(\"first\"),\n            FieldName.of(\"$.last\").as(\"last\"), FieldName.of(\"$.age\")).timeout(2000));\n    assertEquals(1, sr.getTotalResults());\n  }\n\n  @Test\n  public void inOrder() {\n    Schema schema = new Schema()\n            .addField(new TextField(FieldName.of(\"$.first\").as(\"first\")))\n            .addField(new TextField(FieldName.of(\"$.last\")))\n            .addField(new Field(FieldName.of(\"$.age\").as(\"age\"), FieldType.NUMERIC));\n    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema));\n\n    String id = \"student:1112\";\n    JSONObject json = toJson(\"first\", \"Joe is first ok\", \"last\", \"Dod will be first next\", \"age\", 18);\n    setJson(id, json);\n    id = \"student:1113\";\n    json = toJson(\"first\", \"Joe is first ok\", \"last\", \"Dod will be first next\", \"age\", 18);\n    setJson(id, json);\n    id = \"student:1111\";\n    json = toJson(\"first\", \"Joe is first ok\", \"last\", \"Dod will be first next\", \"age\", 18);\n    setJson(id, json);\n\n    SearchResult sr = client.ftSearch(index, new Query().setInOrder());\n    assertEquals(3, sr.getTotalResults());\n    assertEquals(\"student:1112\", sr.getDocuments().get(0).getId());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/JsonSearchWithGsonTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static redis.clients.jedis.util.AssertUtil.assertOK;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class JsonSearchWithGsonTest extends RedisModuleCommandsTestBase {\n\n  private static final String index = \"gson-index\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public JsonSearchWithGsonTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  class Account {\n\n    String name;\n    String phone;\n    Integer age;\n\n    public Account(String name, String phone, Integer age) {\n      this.name = name;\n      this.phone = phone;\n      this.age = age;\n    }\n  }\n\n  @Test\n  public void returnNullField() {\n    Gson nullGson = new GsonBuilder().serializeNulls().create();\n\n    assertOK(client.ftCreate(index, FTCreateParams.createParams().on(IndexDataType.JSON),\n        redis.clients.jedis.search.schemafields.TextField.of(FieldName.of(\"$.name\").as(\"name\")),\n        redis.clients.jedis.search.schemafields.TextField.of(FieldName.of(\"$.phone\").as(\"phone\")),\n        redis.clients.jedis.search.schemafields.NumericField.of(FieldName.of(\"$.age\").as(\"age\"))));\n\n    Account object = new Account(\"Jane\", null, null);\n    String jsonString = nullGson.toJson(object);\n    client.jsonSet(\"account:2\", jsonString);\n\n    SearchResult sr = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams().returnFields(\"name\", \"phone\", \"age\"));\n    assertEquals(1, sr.getTotalResults());\n    Document doc = sr.getDocuments().get(0);\n    assertEquals(\"Jane\", doc.get(\"name\"));\n    assertNull(doc.get(\"phone\"));\n    assertNull(doc.get(\"age\"));\n\n    sr = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams().returnFields(\"name\"));\n    assertEquals(1, sr.getTotalResults());\n    doc = sr.getDocuments().get(0);\n    assertEquals(\"Jane\", doc.get(\"name\"));\n\n    sr = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams().returnFields(\"phone\"));\n    assertEquals(1, sr.getTotalResults());\n    doc = sr.getDocuments().get(0);\n    assertNull(doc.get(\"phone\"));\n\n    sr = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams().returnFields(\"age\"));\n    assertEquals(1, sr.getTotalResults());\n    doc = sr.getDocuments().get(0);\n    assertNull(doc.get(\"age\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static redis.clients.jedis.search.querybuilder.QueryBuilders.*;\nimport static redis.clients.jedis.search.querybuilder.Values.*;\n\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.search.querybuilder.Node;\nimport redis.clients.jedis.search.querybuilder.Value;\nimport redis.clients.jedis.search.querybuilder.Values;\n\n/**\n * Created by mnunberg on 2/23/18.\n */\npublic class QueryBuilderTest {\n\n  @Test\n  public void testTag() {\n    Value v = tags(\"foo\");\n    assertEquals(\"{foo}\", v.toString());\n    v = tags(\"foo\", \"bar\");\n    assertEquals(\"{foo | bar}\", v.toString());\n  }\n\n  @Test\n  public void testEmptyTag() {\n    assertThrows(IllegalArgumentException.class, () -> tags());\n  }\n\n  @Test\n  public void testRange() {\n    Value v = between(1, 10);\n    assertEquals(\"[1 10]\", v.toString());\n    v = between(1, 10).inclusiveMax(false);\n    assertEquals(\"[1 (10]\", v.toString());\n    v = between(1, 10).inclusiveMin(false);\n    assertEquals(\"[(1 10]\", v.toString());\n\n    v = between(1.0, 10.1);\n    assertEquals(\"[1.0 10.1]\", v.toString());\n    v = between(-1.0, 10.1).inclusiveMax(false);\n    assertEquals(\"[-1.0 (10.1]\", v.toString());\n    v = between(-1.1, 150.61).inclusiveMin(false);\n    assertEquals(\"[(-1.1 150.61]\", v.toString());\n\n    // le, gt, etc.\n    // le, gt, etc.\n    assertEquals(\"[42 42]\", eq(42).toString());\n    assertEquals(\"[-inf (42]\", lt(42).toString());\n    assertEquals(\"[-inf 42]\", le(42).toString());\n    assertEquals(\"[(-42 inf]\", gt(-42).toString());\n    assertEquals(\"[42 inf]\", ge(42).toString());\n\n    assertEquals(\"[42.0 42.0]\", eq(42.0).toString());\n    assertEquals(\"[-inf (42.0]\", lt(42.0).toString());\n    assertEquals(\"[-inf 42.0]\", le(42.0).toString());\n    assertEquals(\"[(42.0 inf]\", gt(42.0).toString());\n    assertEquals(\"[42.0 inf]\", ge(42.0).toString());\n\n    assertEquals(\"[(1587058030 inf]\", gt(1587058030).toString());\n\n    // string value\n    assertEquals(\"s\", value(\"s\").toString());\n\n    // Geo value\n    assertEquals(\"[1.0 2.0 3.0 km]\",\n        geo(new GeoCoordinate(1.0, 2.0), 3.0, GeoUnit.KM).toString());\n  }\n\n  @Test\n  public void testIntersectionBasic() {\n    Node n = intersect().add(\"name\", \"mark\");\n    assertEquals(\"@name:mark\", n.toString());\n\n    n = intersect().add(\"name\", \"mark\", \"dvir\");\n    assertEquals(\"@name:(mark dvir)\", n.toString());\n\n    n = intersect().add(\"name\", Arrays.asList(Values.value(\"mark\"), Values.value(\"shay\")));\n    assertEquals(\"@name:(mark shay)\", n.toString());\n\n    n = intersect(\"name\", \"meir\");\n    assertEquals(\"@name:meir\", n.toString());\n\n    n = intersect(\"name\", Values.value(\"meir\"), Values.value(\"rafi\"));\n    assertEquals(\"@name:(meir rafi)\", n.toString());\n  }\n\n  @Test\n  public void testIntersectionNested() {\n    Node n = intersect()\n        .add(union(\"name\", value(\"mark\"), value(\"dvir\")))\n        .add(\"time\", between(100, 200))\n        .add(disjunct(\"created\", lt(1000)));\n    assertEquals(\"(@name:(mark|dvir) @time:[100 200] -@created:[-inf (1000])\", n.toString());\n  }\n\n  @Test\n  public void testOptional() {\n    Node n = optional(\"name\", tags(\"foo\", \"bar\"));\n    assertEquals(\"~@name:{foo | bar}\", n.toString());\n\n    n = optional(n, n);\n    assertEquals(\"~(~@name:{foo | bar} ~@name:{foo | bar})\", n.toString());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/QueryTest.java",
    "content": "//package redis.clients.jedis.modules.search;\n//\n//import static org.junit.jupiter.api.Assertions.*;\n//\n//import java.util.ArrayList;\n//import org.junit.Before;\n//import org.junit.Test;\n//import redis.clients.jedis.search.Query;\n//\n//public class QueryTest {\n//\n//  Query query;\n//\n//  @Before\n//  public void setUp() throws Exception {\n//    query = new Query(\"hello world\");\n//  }\n//\n//  @Test\n//  public void getNoContent() throws Exception {\n//    assertFalse(query.getNoContent());\n//    assertEquals(query, query.setNoContent());\n//    assertTrue(query.getNoContent());\n//  }\n//\n//  @Test\n//  public void getWithScores() throws Exception {\n//    assertFalse(query.getWithScores());\n//    assertEquals(query, query.setWithScores());\n//    assertTrue(query.getWithScores());\n//  }\n//\n//  @Test\n//  public void serializeRedisArgs() throws Exception {\n//    query.setNoContent().setLanguage(\"xx\").setNoStopwords().setVerbatim().setWithPayload().setWithScores().setScorer(\"My.Scorer\");\n//\n//    ArrayList<byte[]> args = new ArrayList<>(1);\n//    query.serializeRedisArgs(args);\n//\n//    assertEquals(10, args.size());\n//    assertEquals(query._queryString, new String(args.get(0)));\n////    assertTrue(args.contains(\"xx\".getBytes()));\n////    assertTrue(args.contains(\"NOSTOPWORDS\".getBytes()));\n////    assertTrue(args.contains(\"VERBATIM\".getBytes()));\n////    assertTrue(args.contains(\"PAYLOADS\".getBytes()));\n////    assertTrue(args.contains(\"WITHSCORES\".getBytes()));\n//  }\n//\n//  @Test\n//  public void limit() throws Exception {\n//    assertEquals(0, query._paging.offset);\n//    assertEquals(10, query._paging.num);\n//    assertEquals(query, query.limit(1, 30));\n//    assertEquals(1, query._paging.offset);\n//    assertEquals(30, query._paging.num);\n//\n//  }\n//\n//  @Test\n//  public void addFilter() throws Exception {\n//    assertEquals(0, query._filters.size());\n//    Query.NumericFilter f = new Query.NumericFilter(\"foo\", 0, 100);\n//    assertEquals(query, query.addFilter(f));\n//    assertEquals(f, query._filters.get(0));\n//  }\n//\n//  @Test\n//  public void setVerbatim() throws Exception {\n//    assertFalse(query._verbatim);\n//    assertEquals(query, query.setVerbatim());\n//    assertTrue(query._verbatim);\n//  }\n//\n//  @Test\n//  public void setNoStopwords() throws Exception {\n//    assertFalse(query._noStopwords);\n//    assertEquals(query, query.setNoStopwords());\n//    assertTrue(query._noStopwords);\n//\n//  }\n//\n//  @Test\n//  public void setLanguage() throws Exception {\n//    assertEquals(null, query._language);\n//    assertEquals(query, query.setLanguage(\"chinese\"));\n//    assertEquals(\"chinese\", query._language);\n//  }\n//\n//  @Test\n//  public void setScorer() throws Exception {\n//    assertEquals(null, query._scorer);\n//    assertEquals(query, query.setScorer(\"the.scroer\"));\n//    assertEquals(\"the.scroer\", query._scorer);\n//  }\n//\n//  @Test\n//  public void limitFields() throws Exception {\n//    assertNull(query._fields);\n//    assertEquals(query, query.limitFields(\"foo\", \"bar\"));\n//    assertEquals(2, query._fields.length);\n//  }\n//\n//  @Test\n//  public void returnFields() throws Exception {\n//    assertNull(query._returnFields);\n//    assertEquals(query, query.returnFields(\"foo\", \"bar\"));\n//    assertEquals(2, query._returnFields.length);\n//  }\n//\n//  @Test\n//  public void highlightFields() throws Exception {\n//    assertEquals(false, query.wantsHighlight);\n//    assertNull(query.highlightFields);\n//\n//    query = new Query(\"Hello\");\n//    assertEquals(query, query.highlightFields(\"foo\", \"bar\"));\n//    assertEquals(2, query.highlightFields.length);\n//    assertNull(query.highlightTags);\n//    assertEquals(true, query.wantsHighlight);\n//\n//    query = new Query(\"Hello\").highlightFields();\n//    assertNull(query.highlightFields);\n//    assertNull(query.highlightTags);\n//    assertEquals(true, query.wantsHighlight);\n//\n//    assertEquals(query, query.highlightFields(new Query.HighlightTags(\"<b>\", \"</b>\")));\n//    assertNull(query.highlightFields);\n//    assertEquals(2, query.highlightTags.length);\n//    assertEquals(\"<b>\", query.highlightTags[0]);\n//    assertEquals(\"</b>\", query.highlightTags[1]);\n//  }\n//\n//  @Test\n//  public void summarizeFields() throws Exception {\n//    assertEquals(false, query.wantsSummarize);\n//    assertNull(query.summarizeFields);\n//\n//    query = new Query(\"Hello\");\n//    assertEquals(query, query.summarizeFields());\n//    assertEquals(true, query.wantsSummarize);\n//    assertNull(query.summarizeFields);\n//    assertEquals(-1, query.summarizeFragmentLen);\n//    assertEquals(-1, query.summarizeNumFragments);\n//\n//    query = new Query(\"Hello\");\n//    assertEquals(query, query.summarizeFields(\"someField\"));\n//    assertEquals(true, query.wantsSummarize);\n//    assertEquals(1, query.summarizeFields.length);\n//    assertEquals(-1, query.summarizeFragmentLen);\n//    assertEquals(-1, query.summarizeNumFragments);\n//  }\n//\n//}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/SchemaTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.search.FieldName;\nimport redis.clients.jedis.search.Schema;\n\npublic class SchemaTest {\n\n  private final static String TITLE = \"title\";\n  private final static String GENRE = \"genre\";\n  private final static String VOTES = \"votes\";\n  private final static String RATING = \"rating\";\n  private final static String RELEASE_YEAR = \"release_year\";\n  private final static String PLOT = \"plot\";\n  private final static String VECTOR = \"vector\";\n\n  @Test\n  public void printSchemaTest() throws Exception {\n    Schema sc = new Schema()\n        .addTextField(TITLE, 5.0)\n        .addSortableTextField(PLOT, 1.0)\n        .addSortableTagField(GENRE, \",\")\n        .addSortableNumericField(RELEASE_YEAR)\n        .addSortableNumericField(RATING)\n        .addSortableNumericField(VOTES)\n        .addVectorField(VECTOR, Schema.VectorField.VectorAlgo.HNSW, Collections.emptyMap());\n\n    String schemaPrint = sc.toString();\n    assertThat(schemaPrint, Matchers.startsWith(\"Schema{fields=[TextField{name='title'\"));\n    assertThat(schemaPrint, Matchers.containsString(\"{name='release_year', type=NUMERIC, sortable=true, noindex=false}\"));\n    assertThat(schemaPrint, Matchers.containsString(\"VectorField{name='vector', type=VECTOR, algorithm=HNSW\"));\n  }\n\n  @Test\n  public void printSvsVamanaSchemaTest() throws Exception {\n    Map<String, Object> vamanaAttributes = new HashMap<>();\n    vamanaAttributes.put(\"TYPE\", \"FLOAT32\");\n    vamanaAttributes.put(\"DIM\", 128);\n    vamanaAttributes.put(\"DISTANCE_METRIC\", \"COSINE\");\n    vamanaAttributes.put(\"COMPRESSION\", \"LVQ8\");\n\n    Schema sc = new Schema()\n        .addTextField(TITLE, 5.0)\n        .addVectorField(\"embedding\", Schema.VectorField.VectorAlgo.SVS_VAMANA, vamanaAttributes);\n\n    String schemaPrint = sc.toString();\n    assertThat(schemaPrint, Matchers.containsString(\"VectorField{name='embedding', type=VECTOR, algorithm=SVS_VAMANA\"));\n    assertThat(schemaPrint, Matchers.containsString(\"TYPE=FLOAT32\"));\n    assertThat(schemaPrint, Matchers.containsString(\"COMPRESSION=LVQ8\"));\n  }\n\n  @Test\n  public void fieldAttributeNull() {\n    assertThrows(IllegalArgumentException.class, () -> FieldName.of(\"identifier\").as(null));\n  }\n\n  @Test\n  public void fieldAttributeMultiple() {\n    assertThrows(IllegalStateException.class, () -> FieldName.of(\"identifier\").as(\"attribute\").as(\"attribute\"));\n    assertThrows(IllegalStateException.class, () -> new FieldName(\"identifier\", \"attribute\").as(\"attribute\"));\n  }\n\n  @Test\n  public void addSvsVamanaVectorFieldBasicTest() {\n    Map<String, Object> attributes = new HashMap<>();\n    attributes.put(\"TYPE\", \"FLOAT32\");\n    attributes.put(\"DIM\", 256);\n    attributes.put(\"DISTANCE_METRIC\", \"L2\");\n\n    Schema schema = new Schema()\n        .addTextField(TITLE, 1.0)\n        .addSvsVamanaVectorField(\"embedding\", attributes);\n\n    // Verify the schema contains the correct number of fields\n    assertThat(schema.fields.size(), Matchers.equalTo(2));\n\n    // Verify the vector field is correctly configured\n    Schema.VectorField vectorField = (Schema.VectorField) schema.fields.get(1);\n    assertThat(vectorField.toString(), Matchers.containsString(\"VectorField{name='embedding', type=VECTOR, algorithm=SVS_VAMANA\"));\n\n    // Verify the schema string representation\n    String schemaString = schema.toString();\n    assertThat(schemaString, Matchers.containsString(\"algorithm=SVS_VAMANA\"));\n    assertThat(schemaString, Matchers.containsString(\"TYPE=FLOAT32\"));\n    assertThat(schemaString, Matchers.containsString(\"DIM=256\"));\n    assertThat(schemaString, Matchers.containsString(\"DISTANCE_METRIC=L2\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/SearchDefaultDialectTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.emptyOrNullString;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static redis.clients.jedis.util.AssertUtil.assertEqualsByProtocol;\nimport static redis.clients.jedis.util.AssertUtil.assertOK;\n\nimport java.util.*;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.schemafields.NumericField;\nimport redis.clients.jedis.search.schemafields.TagField;\nimport redis.clients.jedis.search.schemafields.TextField;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.search.aggr.AggregationBuilder;\nimport redis.clients.jedis.search.aggr.AggregationResult;\nimport redis.clients.jedis.search.aggr.Reducers;\nimport redis.clients.jedis.search.aggr.Row;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SearchDefaultDialectTest extends RedisModuleCommandsTestBase {\n\n  private static final String INDEX = \"dialect-INDEX\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public SearchDefaultDialectTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Override\n  @BeforeEach\n  public void setUp() {\n    super.setUp();\n    client.setDefaultSearchDialect(SearchProtocol.DEFAULT_DIALECT);\n  }\n\n  private void addDocument(Document doc) {\n    String key = doc.getId();\n    Map<String, String> map = new LinkedHashMap<>();\n    doc.getProperties().forEach(entry -> map.put(entry.getKey(), String.valueOf(entry.getValue())));\n    client.hset(key, map);\n  }\n\n  private static Map<String, String> toMap(String... values) {\n    Map<String, String> map = new HashMap<>();\n    for (int i = 0; i < values.length; i += 2) {\n      map.put(values[i], values[i + 1]);\n    }\n    return map;\n  }\n\n  @Test\n  public void testQueryParams() {\n    Schema sc = new Schema().addNumericField(\"numval\");\n    assertEquals(\"OK\", client.ftCreate(INDEX, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"1\", \"numval\", \"1\");\n    client.hset(\"2\", \"numval\", \"2\");\n    client.hset(\"3\", \"numval\", \"3\");\n\n    Query query =  new Query(\"@numval:[$min $max]\").addParam(\"min\", 1).addParam(\"max\", 2);\n    assertEquals(2, client.ftSearch(INDEX, query).getTotalResults());\n  }\n\n  @Test\n  public void testQueryParamsWithParams() {\n    assertOK(client.ftCreate(INDEX, NumericField.of(\"numval\")));\n\n    client.hset(\"1\", \"numval\", \"1\");\n    client.hset(\"2\", \"numval\", \"2\");\n    client.hset(\"3\", \"numval\", \"3\");\n\n    assertEquals(2, client.ftSearch(INDEX, \"@numval:[$min $max]\",\n        FTSearchParams.searchParams().addParam(\"min\", 1).addParam(\"max\", 2)).getTotalResults());\n\n    Map<String, Object> paramValues = new HashMap<>();\n    paramValues.put(\"min\", 1);\n    paramValues.put(\"max\", 2);\n    assertEquals(2, client.ftSearch(INDEX, \"@numval:[$min $max]\",\n        FTSearchParams.searchParams().params(paramValues)).getTotalResults());\n  }\n\n  @Test\n  public void testDialectsWithFTExplain() throws Exception {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    Schema sc = new Schema()\n        .addFlatVectorField(\"v\", attr)\n        .addTagField(\"title\")\n        .addTextField(\"t1\", 1.0)\n        .addTextField(\"t2\", 1.0)\n        .addNumericField(\"num\");\n    assertEquals(\"OK\", client.ftCreate(INDEX, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"1\", \"t1\", \"hello\");\n\n    String q = \"(*)\";\n    Query query = new Query(q).dialect(1);\n    assertSyntaxError(query, client); // dialect=1 throws syntax error\n    query = new Query(q); // dialect=default=2 should return execution plan\n    assertThat(client.ftExplain(INDEX, query), containsString(\"WILDCARD\"));\n\n    q = \"$hello\";\n    query = new Query(q).dialect(1);\n    assertSyntaxError(query, client); // dialect=1 throws syntax error\n    query = new Query(q).addParam(\"hello\", \"hello\"); // dialect=default=2 should return execution plan\n    assertThat(client.ftExplain(INDEX, query), not(emptyOrNullString()));\n\n\n    q = \"@title:(@num:[0 10])\";\n    query = new Query(q).dialect(1); // dialect=1 should return execution plan\n    assertThat(client.ftExplain(INDEX, query), not(emptyOrNullString()));\n    query = new Query(q); // dialect=default=2\n    assertSyntaxError(query, client); // dialect=2 throws syntax error\n\n    q = \"@t1:@t2:@t3:hello\";\n    query = new Query(q).dialect(1); // dialect=1 should return execution plan\n    assertThat(client.ftExplain(INDEX, query), not(emptyOrNullString()));\n    query = new Query(q); // dialect=default=2\n    assertSyntaxError(query, client); // dialect=2 throws syntax error\n\n    q = \"@title:{foo}}}}}\";\n    query = new Query(q).dialect(1); // dialect=1 should return execution plan\n    assertThat(client.ftExplain(INDEX, query), not(emptyOrNullString()));\n    query = new Query(q); // dialect=default=2\n    assertSyntaxError(query, client); // dialect=2 throws syntax error\n  }\n\n  @Test\n  public void testAggregationBuilderParamsDialect() {\n    Schema sc = new Schema();\n    sc.addSortableTextField(\"name\", 1.0);\n    sc.addSortableNumericField(\"count\");\n    client.ftCreate(INDEX, IndexOptions.defaultOptions(), sc);\n    addDocument(new Document(\"data1\").set(\"name\", \"abc\").set(\"count\", 10));\n    addDocument(new Document(\"data2\").set(\"name\", \"def\").set(\"count\", 5));\n    addDocument(new Document(\"data3\").set(\"name\", \"def\").set(\"count\", 25));\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"name\", \"abc\");\n\n    AggregationBuilder r = new AggregationBuilder(\"$name\")\n            .groupBy(\"@name\", Reducers.sum(\"@count\").as(\"sum\"))\n            .params(params);\n\n    AggregationResult res = client.ftAggregate(INDEX, r);\n    assertEquals(1, res.getTotalResults());\n\n    Row r1 = res.getRow(0);\n    assertNotNull(r1);\n    assertEquals(\"abc\", r1.getString(\"name\"));\n    assertEquals(10, r1.getLong(\"sum\"));\n  }\n\n  @Test\n  public void dialectBoundSpellCheck() {\n    client.ftCreate(INDEX, TextField.of(\"t\"));\n    JedisDataException error = assertThrows(JedisDataException.class,\n        () -> client.ftSpellCheck(INDEX, \"Tooni toque kerfuffle\",\n            FTSpellCheckParams.spellCheckParams().dialect(0)));\n    assertThat(error.getMessage(), containsString(\"DIALECT requires a non negative integer\"));\n  }\n\n  private void assertSyntaxError(Query query, UnifiedJedis client) {\n    JedisDataException error = assertThrows(JedisDataException.class,\n        () -> client.ftExplain(INDEX, query));\n    assertThat(error.getMessage(), containsString(\"Syntax error\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.9.0\")\n  public void warningMaxPrefixExpansions() {\n    final String configParam = \"search-max-prefix-expansions\";\n    String defaultConfigValue = jedis.configGet(configParam).get(configParam);\n    try {\n      assertOK(client.ftCreate(INDEX, FTCreateParams.createParams().on(IndexDataType.HASH),\n          TextField.of(\"t\"), TagField.of(\"t2\")));\n\n      client.hset(\"doc13\", toMap(\"t\", \"foo\", \"t2\", \"foo\"));\n\n      jedis.configSet(configParam, \"1\");\n\n      SearchResult srcResult = client.ftSearch(INDEX, \"fo*\");\n      assertEqualsByProtocol(protocol, null, Arrays.asList(), srcResult.getWarnings());\n\n      client.hset(\"doc23\", toMap(\"t\", \"fooo\", \"t2\", \"fooo\"));\n\n      AggregationResult aggResult = client.ftAggregate(INDEX, new AggregationBuilder(\"fo*\").loadAll());\n      assertEqualsByProtocol(protocol,\n          /* resp2 */ null,\n          Arrays.asList(\"Max prefix expansions limit was reached\"),\n          aggResult.getWarnings());\n    } finally {\n      jedis.configSet(configParam, defaultConfigValue);\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/SearchTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.emptyOrNullString;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.utils.RedisVersion;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.Schema.*;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.SafeEncoder;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SearchTest extends RedisModuleCommandsTestBase {\n\n  private static final String index = \"testindex\";\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public SearchTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  private void addDocument(String key, Map<String, Object> map) {\n    client.hset(key, RediSearchUtil.toStringMap(map));\n  }\n\n  private static Map<String, Object> toMap(Object... values) {\n    Map<String, Object> map = new HashMap<>();\n    for (int i = 0; i < values.length; i += 2) {\n      map.put((String) values[i], values[i + 1]);\n    }\n    return map;\n  }\n\n  private static Map<String, String> toMap(String... values) {\n    Map<String, String> map = new HashMap<>();\n    for (int i = 0; i < values.length; i += 2) {\n      map.put(values[i], values[i + 1]);\n    }\n    return map;\n  }\n\n  @Test\n  public void create() throws Exception {\n    Schema sc = new Schema().addTextField(\"first\", 1.0).addTextField(\"last\", 1.0).addNumericField(\"age\");\n    IndexDefinition rule = new IndexDefinition()\n        .setFilter(\"@age>16\")\n        .setPrefixes(new String[]{\"student:\", \"pupil:\"});\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    SearchResult noFilters = client.ftSearch(index, new Query());\n    assertEquals(4, noFilters.getTotalResults());\n\n    SearchResult res1 = client.ftSearch(index, new Query(\"@first:Jo*\"));\n    assertEquals(2, res1.getTotalResults());\n\n    SearchResult res2 = client.ftSearch(index, new Query(\"@first:Pat\"));\n    assertEquals(1, res2.getTotalResults());\n\n    SearchResult res3 = client.ftSearch(index, new Query(\"@last:Rod\"));\n    assertEquals(0, res3.getTotalResults());\n  }\n\n  @Test\n  public void createNoParams() {\n    Schema sc = new Schema().addTextField(\"first\", 1.0).addTextField(\"last\", 1.0).addNumericField(\"age\");\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    addDocument(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18));\n    addDocument(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", 17));\n    addDocument(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", 21));\n    addDocument(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", 20));\n\n    SearchResult noFilters = client.ftSearch(index, new Query());\n    assertEquals(4, noFilters.getTotalResults());\n\n    SearchResult res1 = client.ftSearch(index, new Query(\"@first:Jo*\"));\n    assertEquals(2, res1.getTotalResults());\n\n    SearchResult res2 = client.ftSearch(index, new Query(\"@first:Pat\"));\n    assertEquals(1, res2.getTotalResults());\n\n    SearchResult res3 = client.ftSearch(index, new Query(\"@last:Rod\"));\n    assertEquals(0, res3.getTotalResults());\n  }\n\n  @Test\n  public void createWithFieldNames() {\n    Schema sc = new Schema().addField(new TextField(FieldName.of(\"first\").as(\"given\")))\n        .addField(new TextField(FieldName.of(\"last\")));\n    IndexDefinition rule = new IndexDefinition()\n        //.setFilter(\"@age>16\")\n        .setPrefixes(new String[]{\"student:\", \"pupil:\"});\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    SearchResult noFilters = client.ftSearch(index, new Query());\n    assertEquals(5, noFilters.getTotalResults());\n\n    SearchResult asAttribute = client.ftSearch(index, new Query(\"@given:Jo*\"));\n    assertEquals(2, asAttribute.getTotalResults());\n\n    SearchResult nonAttribute = client.ftSearch(index, new Query(\"@last:Rod\"));\n    assertEquals(1, nonAttribute.getTotalResults());\n  }\n\n  @Test\n  public void alterAdd() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\"));\n    assertEquals(100, res.getTotalResults());\n\n    assertEquals(\"OK\", client.ftAlter(index, new TagField(\"tags\", \",\"), new TextField(\"name\", 0.5)));\n    for (int i = 0; i < 100; i++) {\n      Map<String, Object> fields2 = new HashMap<>();\n      fields2.put(\"name\", \"name\" + i);\n      fields2.put(\"tags\", String.format(\"tagA,tagB,tag%d\", i));\n//      assertTrue(client.updateDocument(String.format(\"doc%d\", i), 1.0, fields2));\n      addDocument(String.format(\"doc%d\", i), fields2);\n    }\n    SearchResult res2 = client.ftSearch(index, new Query(\"@tags:{tagA}\"));\n    assertEquals(100, res2.getTotalResults());\n  }\n\n  @Test\n  public void search() throws Exception {\n    Schema sc = new Schema().addTextField(\"title\", 1.0).addTextField(\"body\", 1.0);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"body\", \"lorem ipsum\");\n    for (int i = 0; i < 100; i++) {\n//      assertTrue(client.addDocument(String.format(\"doc%d\", i), (double) i / 100.0, fields));\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\").limit(0, 5).setWithScores());\n    assertEquals(100, res.getTotalResults());\n    assertEquals(5, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      assertTrue(d.getId().startsWith(\"doc\"));\n      assertTrue(d.getScore() < 100);\n//      assertEquals(\n//          String.format(\n//              \"{\\\"id\\\":\\\"%s\\\",\\\"score\\\":%s,\\\"properties\\\":{\\\"title\\\":\\\"hello world\\\",\\\"body\\\":\\\"lorem ipsum\\\"}}\",\n//              d.getId(), Double.toString(d.getScore())),\n//          d.toString());\n    }\n\n//    assertTrue(client.deleteDocument(\"doc0\", true));\n//    assertFalse(client.deleteDocument(\"doc0\"));\n    client.del(\"doc0\");\n\n    res = client.ftSearch(index, new Query(\"hello world\"));\n    assertEquals(99, res.getTotalResults());\n\n    assertEquals(\"OK\", client.ftDropIndex(index));\n    try {\n      client.ftSearch(index, new Query(\"hello world\"));\n      fail();\n    } catch (JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void numericFilter() throws Exception {\n    Schema sc = new Schema().addTextField(\"title\", 1.0).addNumericField(\"price\");\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n\n    for (int i = 0; i < 100; i++) {\n      fields.put(\"price\", i);\n//      assertTrue(client.addDocument(String.format(\"doc%d\", i), fields));\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(new Query.NumericFilter(\"price\", 0, 49)));\n    assertEquals(50, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      long price = Long.valueOf((String) d.get(\"price\"));\n      assertTrue(price >= 0);\n      assertTrue(price <= 49);\n    }\n\n    res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(new Query.NumericFilter(\"price\", 0, true, 49, true)));\n    assertEquals(48, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      long price = Long.valueOf((String) d.get(\"price\"));\n      assertTrue(price > 0);\n      assertTrue(price < 49);\n    }\n    res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(new Query.NumericFilter(\"price\", 50, 100)));\n    assertEquals(50, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      long price = Long.valueOf((String) d.get(\"price\"));\n      assertTrue(price >= 50);\n      assertTrue(price <= 100);\n    }\n\n    res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(new Query.NumericFilter(\"price\", 20, Double.POSITIVE_INFINITY)));\n    assertEquals(80, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n\n    res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(new Query.NumericFilter(\"price\", Double.NEGATIVE_INFINITY, 10)));\n    assertEquals(11, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n\n  }\n\n  @Test\n  public void stopwords() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0);\n\n    assertEquals(\"OK\", client.ftCreate(index,\n        IndexOptions.defaultOptions().setStopwords(\"foo\", \"bar\", \"baz\"), sc));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world foo bar\");\n//    assertTrue(client.addDocument(\"doc1\", fields));\n    addDocument(\"doc1\", fields);\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\"));\n    assertEquals(1, res.getTotalResults());\n    res = client.ftSearch(index, new Query(\"foo bar\"));\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  public void noStopwords() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0);\n\n    assertEquals(\"OK\", client.ftCreate(index,\n        IndexOptions.defaultOptions().setNoStopwords(), sc));\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world foo bar\");\n    fields.put(\"title\", \"hello world foo bar to be or not to be\");\n    addDocument(\"doc1\", fields);\n\n    assertEquals(1, client.ftSearch(index, new Query(\"hello world\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"foo bar\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"to be or not to be\")).getTotalResults());\n  }\n\n  @Test\n  public void geoFilter() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0).addGeoField(\"loc\");\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"loc\", \"-0.441,51.458\");\n//    assertTrue(client.addDocument(\"doc1\", fields));\n    addDocument(\"doc1\", fields);\n    fields.put(\"loc\", \"-0.1,51.2\");\n//    assertTrue(client.addDocument(\"doc2\", fields));\n    addDocument(\"doc2\", fields);\n\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(\n            new Query.GeoFilter(\"loc\", -0.44, 51.45,\n                10, Query.GeoFilter.KILOMETERS)\n        ));\n\n    assertEquals(1, res.getTotalResults());\n    res = client.ftSearch(index, new Query(\"hello world\").\n        addFilter(\n            new Query.GeoFilter(\"loc\", -0.44, 51.45,\n                100, Query.GeoFilter.KILOMETERS)\n        ));\n    assertEquals(2, res.getTotalResults());\n  }\n\n  @Test\n  public void geoFilterAndGeoCoordinateObject() {\n    Schema schema = new Schema().addTextField(\"title\", 1.0).addGeoField(\"loc\");\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), schema));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"loc\", new redis.clients.jedis.GeoCoordinate(-0.441, 51.458));\n//    assertTrue(client.addDocument(\"doc1\", fields));\n    addDocument(\"doc1\", fields);\n\n    fields.put(\"loc\", new redis.clients.jedis.GeoCoordinate(-0.1, 51.2));\n//    assertTrue(client.addDocument(\"doc2\", fields));\n    addDocument(\"doc2\", fields);\n\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\").addFilter(\n        new Query.GeoFilter(\"loc\", -0.44, 51.45, 10, Query.GeoFilter.KILOMETERS)));\n    assertEquals(1, res.getTotalResults());\n\n    res = client.ftSearch(index, new Query(\"hello world\").addFilter(\n        new Query.GeoFilter(\"loc\", -0.44, 51.45, 100, Query.GeoFilter.KILOMETERS)));\n    assertEquals(2, res.getTotalResults());\n  }\n\n  @Test\n  public void testQueryFlags() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields = new HashMap<>();\n\n    for (int i = 0; i < 100; i++) {\n      fields.put(\"title\", i % 2 != 0 ? \"hello worlds\" : \"hello world\");\n//      assertTrue(client.addDocument(String.format(\"doc%d\", i), (double) i / 100.0, fields));\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    Query q = new Query(\"hello\").setWithScores();\n    SearchResult res = client.ftSearch(index, q);\n\n    assertEquals(100, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n\n    for (Document d : res.getDocuments()) {\n      assertTrue(d.getId().startsWith(\"doc\"));\n//      assertNotEquals(1.0, d.getScore());\n      assertTrue(((String) d.get(\"title\")).startsWith(\"hello world\"));\n    }\n\n    q = new Query(\"hello\").setNoContent();\n    res = client.ftSearch(index, q);\n    for (Document d : res.getDocuments()) {\n      assertTrue(d.getId().startsWith(\"doc\"));\n      if (protocol != RedisProtocol.RESP3) {\n        assertEquals(1.0, d.getScore(), 0);\n        assertNull(d.get(\"title\"));\n      } else {\n        assertNull(d.getScore());\n        assertThrows(NullPointerException.class, () -> d.get(\"title\"));\n      }\n    }\n\n    // test verbatim vs. stemming\n    res = client.ftSearch(index, new Query(\"hello worlds\"));\n    assertEquals(100, res.getTotalResults());\n    res = client.ftSearch(index, new Query(\"hello worlds\").setVerbatim());\n    assertEquals(50, res.getTotalResults());\n\n    res = client.ftSearch(index, new Query(\"hello a world\").setVerbatim());\n    assertEquals(50, res.getTotalResults());\n    res = client.ftSearch(index, new Query(\"hello a worlds\").setVerbatim());\n    assertEquals(50, res.getTotalResults());\n    res = client.ftSearch(index, new Query(\"hello a world\").setVerbatim().setNoStopwords());\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  public void testQueryParams() {\n    Schema sc = new Schema().addNumericField(\"numval\");\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"1\", \"numval\", \"1\");\n    client.hset(\"2\", \"numval\", \"2\");\n    client.hset(\"3\", \"numval\", \"3\");\n\n    Query query =  new Query(\"@numval:[$min $max]\").addParam(\"min\", 1).addParam(\"max\", 2).dialect(2);\n    assertEquals(2, client.ftSearch(index, query).getTotalResults());\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4) ) {\n      query = new Query(\"@numval:[$eq]\").addParam(\"eq\", 2).dialect(4);\n      assertEquals(1, client.ftSearch(index, query).getTotalResults());\n    }\n  }\n\n  @Test\n  public void testSortQueryFlags() {\n    Schema sc = new Schema().addSortableTextField(\"title\", 1.0);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields = new HashMap<>();\n\n    fields.put(\"title\", \"b title\");\n//    client.addDocument(\"doc1\", 1.0, fields, false, true, null);\n    addDocument(\"doc1\", fields);\n\n    fields.put(\"title\", \"a title\");\n//    client.addDocument(\"doc2\", 1.0, fields, false, true, null);\n    addDocument(\"doc2\", fields);\n\n    fields.put(\"title\", \"c title\");\n//    client.addDocument(\"doc3\", 1.0, fields, false, true, null);\n    addDocument(\"doc3\", fields);\n\n    Query q = new Query(\"title\").setSortBy(\"title\", true);\n    SearchResult res = client.ftSearch(index, q);\n\n    assertEquals(3, res.getTotalResults());\n    Document doc1 = res.getDocuments().get(0);\n    assertEquals(\"a title\", doc1.get(\"title\"));\n\n    doc1 = res.getDocuments().get(1);\n    assertEquals(\"b title\", doc1.get(\"title\"));\n\n    doc1 = res.getDocuments().get(2);\n    assertEquals(\"c title\", doc1.get(\"title\"));\n  }\n\n  @Test\n  public void testNullField() {\n    Schema sc = new Schema()\n        .addTextField(\"title\", 1.0)\n        .addTextField(\"genre\", 1.0)\n        .addTextField(\"plot\", 1.0)\n        .addSortableNumericField(\"release_year\")\n        .addTagField(\"tag\")\n        .addGeoField(\"loc\");\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    // create a document with a field set to null\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"another test with title \");\n    fields.put(\"genre\", \"Comedy\");\n    fields.put(\"plot\", \"this is the plot for the test\");\n    fields.put(\"tag\", \"fun\");\n    fields.put(\"release_year\", 2019);\n    fields.put(\"loc\", \"-0.1,51.2\");\n\n//    client.addDocument(\"doc1\", fields);\n    addDocument(\"doc1\", fields);\n    SearchResult res = client.ftSearch(index, new Query(\"title\"));\n    assertEquals(1, res.getTotalResults());\n\n    fields = new HashMap<>();\n    fields.put(\"title\", \"another title another test\");\n    fields.put(\"genre\", \"Action\");\n    fields.put(\"plot\", null);\n    fields.put(\"tag\", null);\n\n    try {\n//      client.addDocument(\"doc2\", fields);\n      addDocument(\"doc2\", fields);\n      fail(\"Should throw NullPointerException.\");\n    } catch (NullPointerException e) {\n//      assertEquals(\"Document attribute 'tag' is null. (Remove it, or set a value)\", e.getMessage());\n    }\n\n    res = client.ftSearch(index, new Query(\"title\"));\n    assertEquals(1, res.getTotalResults());\n\n    // Testing with numerical value\n    fields = new HashMap<>();\n    fields.put(\"title\", \"another title another test\");\n    fields.put(\"genre\", \"Action\");\n    fields.put(\"release_year\", null);\n    try {\n//      client.addDocument(\"doc2\", fields);\n      addDocument(\"doc2\", fields);\n      fail(\"Should throw NullPointerException.\");\n    } catch (NullPointerException e) {\n//      assertEquals(\"Document attribute 'release_year' is null. (Remove it, or set a value)\", e.getMessage());\n    }\n    res = client.ftSearch(index, new Query(\"title\"));\n    assertEquals(1, res.getTotalResults());\n  }\n\n  @Test\n  public void testJsonWithAlias() {\n    Schema sc = new Schema()\n            .addTextField(\"$.name\", 1.0).as(\"name\")\n            .addNumericField(\"$.num\").as(\"num\");\n\n    IndexDefinition definition = new IndexDefinition(IndexDefinition.Type.JSON).setPrefixes(\"king:\");\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(definition), sc));\n\n    Map<String, Object> king1 = new HashMap<>();\n    king1.put(\"name\", \"henry\");\n    king1.put(\"num\", 42);\n    client.jsonSet(\"king:1\", Path.ROOT_PATH, king1);\n\n    Map<String, Object> king2 = new HashMap<>();\n    king2.put(\"name\", \"james\");\n    king2.put(\"num\", 3.14);\n    client.jsonSet(\"king:2\", Path.ROOT_PATH, king2);\n\n    SearchResult res = client.ftSearch(index, new Query(\"@name:henry\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"king:1\", res.getDocuments().get(0).getId());\n\n    res = client.ftSearch(index, new Query(\"@num:[0 10]\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"king:2\", res.getDocuments().get(0).getId());\n\n    res = client.ftSearch(index, new Query(\"@num:[42 42]\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"king:1\", res.getDocuments().get(0).getId());\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4) ) {\n      res = client.ftSearch(index, new Query(\"@num:[42]\").dialect(4));\n      assertEquals(1, res.getTotalResults());\n      assertEquals(\"king:1\", res.getDocuments().get(0).getId());\n    }\n  }\n\n  @Test\n  public void dropIndex() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\"));\n    assertEquals(100, res.getTotalResults());\n\n    assertEquals(\"OK\", client.ftDropIndex(index));\n\n    try {\n      client.ftSearch(index, new Query(\"hello world\"));\n      fail(\"Index should not exist.\");\n    } catch (JedisDataException de) {\n      // error message updated to \"No such index\" with Redis 8.0.0\n      assertTrue(de.getMessage().toLowerCase().contains(\"no such index\"));\n    }\n    assertEquals(100, client.dbSize());\n  }\n\n  @Test\n  public void dropIndexDD() {\n    Schema sc = new Schema().addTextField(\"title\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, new Query(\"hello world\"));\n    assertEquals(100, res.getTotalResults());\n\n    assertEquals(\"OK\", client.ftDropIndexDD(index));\n\n    Set<String> keys = client.keys(\"*\");\n    assertTrue(keys.isEmpty());\n    assertEquals(0, client.dbSize());\n  }\n\n  @Test\n  public void noStem() {\n    Schema sc = new Schema().addTextField(\"stemmed\", 1.0).addField(new Schema.TextField(\"notStemmed\", 1.0, false, true));\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"stemmed\", \"located\");\n    doc.put(\"notStemmed\", \"located\");\n    // Store it\n//    assertTrue(client.addDocument(\"doc\", doc));\n    addDocument(\"doc\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, new Query(\"@stemmed:location\"));\n    assertEquals(1, res.getTotalResults());\n\n    res = client.ftSearch(index, new Query(\"@notStemmed:location\"));\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  public void phoneticMatch() {\n    Schema sc = new Schema()\n        .addTextField(\"noPhonetic\", 1.0)\n        .addField(new Schema.TextField(\"withPhonetic\", 1.0, false, false, false, \"dm:en\"));\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"noPhonetic\", \"morfix\");\n    doc.put(\"withPhonetic\", \"morfix\");\n\n    // Store it\n//    assertTrue(client.addDocument(\"doc\", doc));\n    addDocument(\"doc\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, new Query(\"@withPhonetic:morphix=>{$phonetic:true}\"));\n    assertEquals(1, res.getTotalResults());\n\n    try {\n      client.ftSearch(index, new Query(\"@noPhonetic:morphix=>{$phonetic:true}\"));\n      fail();\n    } catch (JedisDataException e) {/*field does not support phonetics*/\n    }\n\n    SearchResult res3 = client.ftSearch(index, new Query(\"@withPhonetic:morphix=>{$phonetic:false}\"));\n    assertEquals(0, res3.getTotalResults());\n  }\n\n  @Test\n  public void info() throws Exception {\n    String MOVIE_ID = \"movie_id\";\n    String TITLE = \"title\";\n    String GENRE = \"genre\";\n    String VOTES = \"votes\";\n    String RATING = \"rating\";\n    String RELEASE_YEAR = \"release_year\";\n    String PLOT = \"plot\";\n    String POSTER = \"poster\";\n\n    Schema sc = new Schema()\n        .addTextField(TITLE, 5.0)\n        .addSortableTextField(PLOT, 1.0)\n        .addSortableTagField(GENRE, \",\")\n        .addSortableNumericField(RELEASE_YEAR)\n        .addSortableNumericField(RATING)\n        .addSortableNumericField(VOTES);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> info = client.ftInfo(index);\n    assertEquals(index, info.get(\"index_name\"));\n    assertEquals(6, ((List) info.get(\"attributes\")).size());\n    if (protocol != RedisProtocol.RESP3) {\n      assertEquals(\"global_idle\", ((List) info.get(\"cursor_stats\")).get(0));\n      assertEquals(0L, ((List) info.get(\"cursor_stats\")).get(1));\n    } else {\n      assertEquals(0L, ((Map) info.get(\"cursor_stats\")).get(\"global_idle\"));\n      assertEquals(128L, ((Map) info.get(\"cursor_stats\")).get(\"index_capacity\"));\n    }\n  }\n\n  @Test\n  public void noIndex() {\n    Schema sc = new Schema()\n        .addField(new Schema.TextField(\"f1\", 1.0, true, false, true))\n        .addField(new Schema.TextField(\"f2\", 1.0));\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    Map<String, Object> mm = new HashMap<>();\n\n    mm.put(\"f1\", \"MarkZZ\");\n    mm.put(\"f2\", \"MarkZZ\");\n//    client.addDocument(\"doc1\", mm);\n    addDocument(\"doc1\", mm);\n\n    mm.clear();\n    mm.put(\"f1\", \"MarkAA\");\n    mm.put(\"f2\", \"MarkBB\");\n//    client.addDocument(\"doc2\", mm);\n    addDocument(\"doc2\", mm);\n\n    SearchResult res = client.ftSearch(index, new Query(\"@f1:Mark*\"));\n    assertEquals(0, res.getTotalResults());\n\n    res = client.ftSearch(index, new Query(\"@f2:Mark*\"));\n    assertEquals(2, res.getTotalResults());\n\n    Document[] docs = new Document[2];\n\n    res = client.ftSearch(index, new Query(\"@f2:Mark*\").setSortBy(\"f1\", false));\n    assertEquals(2, res.getTotalResults());\n\n    res.getDocuments().toArray(docs);\n    assertEquals(\"doc1\", docs[0].getId());\n\n    res = client.ftSearch(index, new Query(\"@f2:Mark*\").setSortBy(\"f1\", true));\n    res.getDocuments().toArray(docs);\n    assertEquals(\"doc2\", docs[0].getId());\n  }\n\n  @Test\n  public void testExplain() {\n    Schema sc = new Schema()\n        .addTextField(\"f1\", 1.0)\n        .addTextField(\"f2\", 1.0)\n        .addTextField(\"f3\", 1.0);\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    String res = client.ftExplain(index, new Query(\"@f3:f3_val @f2:f2_val @f1:f1_val\"));\n    assertNotNull(res);\n    assertFalse(res.isEmpty());\n  }\n\n  @Test\n  public void testHighlightSummarize() {\n    Schema sc = new Schema().addTextField(\"text\", 1.0);\n    client.ftCreate(index, IndexOptions.defaultOptions(), sc);\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"text\", \"Redis is often referred as a data structures server. What this means is that Redis provides access to mutable data structures via a set of commands, which are sent using a server-client model with TCP sockets and a simple protocol. So different processes can query and modify the same data structures in a shared way\");\n    // Add a document\n//    client.addDocument(\"foo\", 1.0, doc);\n    addDocument(\"foo\", doc);\n    Query q = new Query(\"data\").highlightFields().summarizeFields();\n    SearchResult res = client.ftSearch(index, q);\n\n    assertEquals(\"is often referred as a <b>data</b> structures server. What this means is that Redis provides... What this means is that Redis provides access to mutable <b>data</b> structures via a set of commands, which are sent using a... So different processes can query and modify the same <b>data</b> structures in a shared... \",\n        res.getDocuments().get(0).get(\"text\"));\n\n    q = new Query(\"data\").highlightFields(new Query.HighlightTags(\"<u>\", \"</u>\")).summarizeFields();\n    res = client.ftSearch(index, q);\n\n    assertEquals(\"is often referred as a <u>data</u> structures server. What this means is that Redis provides... What this means is that Redis provides access to mutable <u>data</u> structures via a set of commands, which are sent using a... So different processes can query and modify the same <u>data</u> structures in a shared... \",\n        res.getDocuments().get(0).get(\"text\"));\n  }\n\n  @Test\n  public void getTagField() {\n    Schema sc = new Schema()\n        .addTextField(\"title\", 1.0)\n        .addTagField(\"category\");\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields1 = new HashMap<>();\n    fields1.put(\"title\", \"hello world\");\n    fields1.put(\"category\", \"red\");\n//    assertTrue(client.addDocument(\"foo\", fields1));\n    addDocument(\"foo\", fields1);\n    Map<String, Object> fields2 = new HashMap<>();\n    fields2.put(\"title\", \"hello world\");\n    fields2.put(\"category\", \"blue\");\n//    assertTrue(client.addDocument(\"bar\", fields2));\n    addDocument(\"bar\", fields2);\n    Map<String, Object> fields3 = new HashMap<>();\n    fields3.put(\"title\", \"hello world\");\n    fields3.put(\"category\", \"green,yellow\");\n//    assertTrue(client.addDocument(\"baz\", fields3));\n    addDocument(\"baz\", fields3);\n    Map<String, Object> fields4 = new HashMap<>();\n    fields4.put(\"title\", \"hello world\");\n    fields4.put(\"category\", \"orange;purple\");\n//    assertTrue(client.addDocument(\"qux\", fields4));\n    addDocument(\"qux\", fields4);\n\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{red}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{blue}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"hello @category:{red}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"hello @category:{blue}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{yellow}\")).getTotalResults());\n    assertEquals(0, client.ftSearch(index, new Query(\"@category:{purple}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{orange\\\\;purple}\")).getTotalResults());\n    assertEquals(4, client.ftSearch(index, new Query(\"hello\")).getTotalResults());\n\n    assertEquals(new HashSet<>(Arrays.asList(\"red\", \"blue\", \"green\", \"yellow\", \"orange;purple\")),\n        client.ftTagVals(index, \"category\"));\n  }\n\n  @Test\n  public void testGetTagFieldWithNonDefaultSeparator() {\n    Schema sc = new Schema()\n        .addTextField(\"title\", 1.0)\n        .addTagField(\"category\", \";\");\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n    Map<String, Object> fields1 = new HashMap<>();\n    fields1.put(\"title\", \"hello world\");\n    fields1.put(\"category\", \"red\");\n//    assertTrue(client.addDocument(\"foo\", fields1));\n    addDocument(\"foo\", fields1);\n    Map<String, Object> fields2 = new HashMap<>();\n    fields2.put(\"title\", \"hello world\");\n    fields2.put(\"category\", \"blue\");\n//    assertTrue(client.addDocument(\"bar\", fields2));\n    addDocument(\"bar\", fields2);\n    Map<String, Object> fields3 = new HashMap<>();\n    fields3.put(\"title\", \"hello world\");\n    fields3.put(\"category\", \"green;yellow\");\n    addDocument(\"baz\", fields3);\n//    assertTrue(client.addDocument(\"baz\", fields3));\n    Map<String, Object> fields4 = new HashMap<>();\n    fields4.put(\"title\", \"hello world\");\n    fields4.put(\"category\", \"orange,purple\");\n//    assertTrue(client.addDocument(\"qux\", fields4));\n    addDocument(\"qux\", fields4);\n\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{red}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{blue}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"hello @category:{red}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"hello @category:{blue}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"hello @category:{yellow}\")).getTotalResults());\n    assertEquals(0, client.ftSearch(index, new Query(\"@category:{purple}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{orange\\\\,purple}\")).getTotalResults());\n    assertEquals(4, client.ftSearch(index, new Query(\"hello\")).getTotalResults());\n\n    assertEquals(new HashSet<>(Arrays.asList(\"red\", \"blue\", \"green\", \"yellow\", \"orange,purple\")),\n        client.ftTagVals(index, \"category\"));\n  }\n\n  @Test\n  public void caseSensitiveTagField() {\n    Schema sc = new Schema()\n        .addTextField(\"title\", 1.0)\n        .addTagField(\"category\", true /*casesensitive*/);\n\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"category\", \"RedX\");\n    addDocument(\"foo\", fields);\n\n    assertEquals(0, client.ftSearch(index, new Query(\"@category:{redx}\")).getTotalResults());\n    assertEquals(0, client.ftSearch(index, new Query(\"@category:{redX}\")).getTotalResults());\n    assertEquals(0, client.ftSearch(index, new Query(\"@category:{Redx}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"@category:{RedX}\")).getTotalResults());\n    assertEquals(1, client.ftSearch(index, new Query(\"hello\")).getTotalResults());\n  }\n\n  @Test\n  public void testReturnFields() {\n    Schema sc = new Schema().addTextField(\"field1\", 1.0).addTextField(\"field2\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value1\");\n    doc.put(\"field2\", \"value2\");\n    // Store it\n//    assertTrue(client.addDocument(\"doc\", doc));\n    addDocument(\"doc\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, new Query(\"*\").returnFields(\"field1\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"value1\", res.getDocuments().get(0).get(\"field1\"));\n    assertNull(res.getDocuments().get(0).get(\"field2\"));\n  }\n\n  @Test\n  public void returnWithFieldNames() {\n    Schema sc = new Schema().addTextField(\"a\", 1).addTextField(\"b\", 1).addTextField(\"c\", 1);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"a\", \"value1\");\n    map.put(\"b\", \"value2\");\n    map.put(\"c\", \"value3\");\n//    assertTrue(client.addDocument(\"doc\", map));\n    addDocument(\"doc\", map);\n\n    // Query\n    SearchResult res = client.ftSearch(index,\n        new Query().returnFields(FieldName.of(\"a\"), FieldName.of(\"b\").as(\"d\")));\n    assertEquals(1, res.getTotalResults());\n    Document doc = res.getDocuments().get(0);\n    assertEquals(\"value1\", doc.get(\"a\"));\n    assertNull(doc.get(\"b\"));\n    assertEquals(\"value2\", doc.get(\"d\"));\n    assertNull(doc.get(\"c\"));\n  }\n\n  @Test\n  public void inKeys() {\n    Schema sc = new Schema().addTextField(\"field1\", 1.0).addTextField(\"field2\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value\");\n    doc.put(\"field2\", \"not\");\n\n    // Store it\n//    assertTrue(client.addDocument(\"doc1\", doc));\n    addDocument(\"doc1\", doc);\n//    assertTrue(client.addDocument(\"doc2\", doc));\n    addDocument(\"doc2\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, new Query(\"value\").limitKeys(\"doc1\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).get(\"field1\"));\n    assertNull(res.getDocuments().get(0).get(\"value\"));\n  }\n\n  @Test\n  public void blobField() {\n    assumeFalse(protocol == RedisProtocol.RESP3); // not supporting\n\n    Schema sc = new Schema().addTextField(\"field1\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    byte[] blob = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value\");\n    doc.put(\"field2\", blob);\n\n    // Store it\n//    assertTrue(client.addDocument(\"doc1\", doc));\n    addDocument(\"doc1\", doc);\n\n    // Query\n//    SearchResult res = client.ftSearch(index, new Query(\"value\"), false);\n    SearchResult res = client.ftSearch(SafeEncoder.encode(index), new Query(\"value\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).getString(\"field1\"));\n    assertArrayEquals(blob, (byte[]) res.getDocuments().get(0).get(\"field2\"));\n  }\n\n  @Test\n  public void alias() {\n    Schema sc = new Schema().addTextField(\"field1\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value\");\n//    assertTrue(client.addDocument(\"doc1\", doc));\n    addDocument(\"doc1\", doc);\n\n    assertEquals(\"OK\", client.ftAliasAdd(\"ALIAS1\", index));\n    SearchResult res1 = client.ftSearch(\"ALIAS1\", new Query(\"*\").returnFields(\"field1\"));\n    assertEquals(1, res1.getTotalResults());\n    assertEquals(\"value\", res1.getDocuments().get(0).get(\"field1\"));\n\n    assertEquals(\"OK\", client.ftAliasUpdate(\"ALIAS2\", index));\n    SearchResult res2 = client.ftSearch(\"ALIAS2\", new Query(\"*\").returnFields(\"field1\"));\n    assertEquals(1, res2.getTotalResults());\n    assertEquals(\"value\", res2.getDocuments().get(0).get(\"field1\"));\n\n    try {\n      client.ftAliasDel(\"ALIAS3\");\n      fail(\"Should throw JedisDataException\");\n    } catch (JedisDataException e) {\n      // Alias does not exist\n    }\n    assertEquals(\"OK\", client.ftAliasDel(\"ALIAS2\"));\n    try {\n      client.ftAliasDel(\"ALIAS2\");\n      fail(\"Should throw JedisDataException\");\n    } catch (JedisDataException e) {\n      // Alias does not exist\n    }\n  }\n\n  @Test\n  public void synonym() {\n    Schema sc = new Schema().addTextField(\"name\", 1.0).addTextField(\"addr\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    long group1 = 345L;\n    long group2 = 789L;\n    String group1_str = Long.toString(group1);\n    String group2_str = Long.toString(group2);\n    assertEquals(\"OK\", client.ftSynUpdate(index, group1_str, \"girl\", \"baby\"));\n    assertEquals(\"OK\", client.ftSynUpdate(index, group1_str, \"child\"));\n    assertEquals(\"OK\", client.ftSynUpdate(index, group2_str, \"child\"));\n\n    Map<String, List<String>> dump = client.ftSynDump(index);\n\n    Map<String, List<String>> expected = new HashMap<>();\n    expected.put(\"girl\", Arrays.asList(group1_str));\n    expected.put(\"baby\", Arrays.asList(group1_str));\n    expected.put(\"child\", Arrays.asList(group1_str, group2_str));\n    assertEquals(expected, dump);\n  }\n\n  @Test\n  public void slop() {\n    Schema sc = new Schema().addTextField(\"field1\", 1.0).addTextField(\"field2\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"ok hi jedis\");\n    addDocument(\"doc1\", doc);\n\n    SearchResult res = client.ftSearch(index, new Query(\"ok jedis\").slop(0));\n    assertEquals(0, res.getTotalResults());\n\n    res = client.ftSearch(index, new Query(\"ok jedis\").slop(1));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"ok hi jedis\", res.getDocuments().get(0).get(\"field1\"));\n  }\n\n  @Test\n  public void timeout() {\n    Schema sc = new Schema().addTextField(\"field1\", 1.0).addTextField(\"field2\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"field1\", \"value\");\n    map.put(\"field2\", \"not\");\n    client.hset(\"doc1\", map);\n\n    SearchResult res = client.ftSearch(index, new Query(\"value\").timeout(1000));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).get(\"field1\"));\n    assertEquals(\"not\", res.getDocuments().get(0).get(\"field2\"));\n  }\n\n  @Test\n  public void inOrder() {\n    Schema sc = new Schema().addTextField(\"field1\", 1.0).addTextField(\"field2\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"field1\", \"value\");\n    map.put(\"field2\", \"not\");\n    client.hset(\"doc2\", map);\n    client.hset(\"doc1\", map);\n\n    SearchResult res = client.ftSearch(index, new Query(\"value\").setInOrder());\n    assertEquals(2, res.getTotalResults());\n    assertEquals(\"doc2\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).get(\"field1\"));\n    assertEquals(\"not\", res.getDocuments().get(0).get(\"field2\"));\n  }\n\n  @Test\n  public void testDialectsWithFTExplain() {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    Schema sc = new Schema()\n        .addFlatVectorField(\"v\", attr)\n        .addTagField(\"title\")\n        .addTextField(\"t1\", 1.0)\n        .addTextField(\"t2\", 1.0)\n        .addNumericField(\"num\");\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"1\", \"t1\", \"hello\");\n\n    String q = \"(*)\";\n    Query query = new Query(q).dialect(1);\n    assertSyntaxError(query, client); // dialect=1 throws syntax error\n    query = new Query(q).dialect(2);\n    assertThat(client.ftExplain(index, query), containsString(\"WILDCARD\"));\n\n    q = \"$hello\";\n    query = new Query(q).dialect(1);\n    assertSyntaxError(query, client); // dialect=1 throws syntax error\n    query = new Query(q).dialect(2).addParam(\"hello\", \"hello\");\n    assertThat(client.ftExplain(index, query), not(emptyOrNullString()));\n\n    q = \"@title:(@num:[0 10])\";\n    query = new Query(q).dialect(1);\n    assertThat(client.ftExplain(index, query), not(emptyOrNullString()));\n    query = new Query(q).dialect(2);\n    assertSyntaxError(query, client); // dialect=2 throws syntax error\n\n    q = \"@t1:@t2:@t3:hello\";\n    query = new Query(q).dialect(1);\n    assertThat(client.ftExplain(index, query), not(emptyOrNullString()));\n    query = new Query(q).dialect(2);\n    assertSyntaxError(query, client); // dialect=2 throws syntax error\n\n    q = \"@title:{foo}}}}}\";\n    query = new Query(q).dialect(1);\n    assertThat(client.ftExplain(index, query), not(emptyOrNullString()));\n    query = new Query(q).dialect(2);\n    assertSyntaxError(query, client); // dialect=2 throws syntax error\n\n    q = \"*=>[KNN 10 @v $BLOB]\";\n    query = new Query(q).addParam(\"BLOB\", \"aaaa\").dialect(1);\n    assertSyntaxError(query, client); // dialect=1 throws syntax error\n    query = new Query(q).addParam(\"BLOB\", \"aaaa\").dialect(2);\n    assertThat(client.ftExplain(index, query), not(emptyOrNullString()));\n\n    q = \"*=>[knn $K @v $BLOB as score]\";\n    query = new Query(q).addParam(\"BLOB\", \"aaaa\").addParam(\"K\", \"10\").dialect(1);\n    assertSyntaxError(query, client); // dialect=1 throws syntax error\n    query = new Query(q).addParam(\"BLOB\", \"aaaa\").addParam(\"K\", \"10\").dialect(2);\n    assertThat(client.ftExplain(index, query), not(emptyOrNullString()));\n  }\n\n  @Test\n  public void searchProfile() {\n    Schema sc = new Schema().addTextField(\"t1\", 1.0).addTextField(\"t2\", 1.0);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"t1\", \"foo\");\n    hash.put(\"t2\", \"bar\");\n    client.hset(\"doc1\", hash);\n\n    Map.Entry<SearchResult, ProfilingInfo> reply = client.ftProfileSearch(index,\n        FTProfileParams.profileParams(), new Query(\"foo\"));\n\n    SearchResult result = reply.getKey();\n    assertEquals(1, result.getTotalResults());\n    assertEquals(Collections.singletonList(\"doc1\"), result.getDocuments().stream().map(Document::getId).collect(Collectors.toList()));\n\n    Object profileObject = reply.getValue().getProfilingInfo();\n    if (protocol != RedisProtocol.RESP3) {\n      assertThat(profileObject, Matchers.isA(List.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat((List<Object>) profileObject, Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    } else {\n      assertThat(profileObject, Matchers.isA(Map.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    }\n  }\n\n  @Test\n  public void testHNSWVectorSimilarity() {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    Schema sc = new Schema().addHNSWVectorField(\"v\", attr);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"a\", \"v\", \"aaaaaaaa\");\n    client.hset(\"b\", \"v\", \"aaaabaaa\");\n    client.hset(\"c\", \"v\", \"aaaaabaa\");\n\n    Query query = new Query(\"*=>[KNN 2 @v $vec]\")\n        .addParam(\"vec\", \"aaaaaaaa\")\n        .setSortBy(\"__v_score\", true)\n        .returnFields(\"__v_score\")\n        .dialect(2);\n    Document doc1 = client.ftSearch(index, query).getDocuments().get(0);\n    assertEquals(\"a\", doc1.getId());\n    assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testSvsVamanaVectorSimilarity() {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    Schema sc = new Schema().addSvsVamanaVectorField(\"v\", attr);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"a\", \"v\", \"aaaaaaaa\");\n    client.hset(\"b\", \"v\", \"aaaabaaa\");\n    client.hset(\"c\", \"v\", \"aaaaabaa\");\n\n    Query query = new Query(\"*=>[KNN 2 @v $vec]\")\n            .addParam(\"vec\", \"aaaaaaaa\")\n            .setSortBy(\"__v_score\", true)\n            .returnFields(\"__v_score\")\n            .dialect(2);\n    Document doc1 = client.ftSearch(index, query).getDocuments().get(0);\n    assertEquals(\"a\", doc1.getId());\n    assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  public void testFlatVectorSimilarity() {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    Schema sc = new Schema().addFlatVectorField(\"v\", attr);\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions(), sc));\n\n    client.hset(\"a\", \"v\", \"aaaaaaaa\");\n    client.hset(\"b\", \"v\", \"aaaabaaa\");\n    client.hset(\"c\", \"v\", \"aaaaabaa\");\n\n    Query query = new Query(\"*=>[KNN 2 @v $vec]\")\n        .addParam(\"vec\", \"aaaaaaaa\")\n        .setSortBy(\"__v_score\", true)\n        .returnFields(\"__v_score\")\n        .dialect(2);\n    Document doc1 = client.ftSearch(index, query).getDocuments().get(0);\n    assertEquals(\"a\", doc1.getId());\n    assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  public void searchIteration() {\n    Schema sc = new Schema().addTextField(\"first\", 1.0).addTextField(\"last\", 1.0).addNumericField(\"age\");\n    IndexDefinition rule = new IndexDefinition();\n    assertEquals(\"OK\", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    FtSearchIteration search = client.ftSearchIteration(3, index, new Query());\n    int total = 0;\n    while (!search.isIterationCompleted()) {\n      SearchResult result = search.nextBatch();\n      int count = result.getDocuments().size();\n      assertThat(count, Matchers.lessThanOrEqualTo(3));\n      total += count;\n    }\n    assertEquals(7, total);\n  }\n\n  void assertSyntaxError(Query query, UnifiedJedis client) {\n    JedisDataException error = assertThrows(JedisDataException.class,\n        () -> client.ftExplain(index, query));\n    assertThat(error.getMessage(), containsString(\"Syntax error\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static redis.clients.jedis.util.AssertUtil.assertOK;\nimport static redis.clients.jedis.util.RedisConditions.ModuleVersion.SEARCH_MOD_VER_80M3;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.utils.RedisVersion;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport org.locationtech.jts.geom.Coordinate;\nimport org.locationtech.jts.geom.GeometryFactory;\nimport org.locationtech.jts.geom.Point;\nimport org.locationtech.jts.geom.Polygon;\nimport org.locationtech.jts.io.ParseException;\nimport org.locationtech.jts.io.WKTReader;\n\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.args.GeoUnit;\nimport redis.clients.jedis.args.SortingOrder;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.json.Path;\nimport redis.clients.jedis.search.*;\nimport redis.clients.jedis.search.RediSearchUtil;\nimport redis.clients.jedis.search.schemafields.*;\nimport redis.clients.jedis.search.schemafields.GeoShapeField.CoordinateSystem;\nimport redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.util.RedisConditions;\nimport redis.clients.jedis.util.RedisVersionUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\n@Tag(\"integration\")\npublic class SearchWithParamsTest extends RedisModuleCommandsTestBase {\n\n  private static final String index = \"testindex\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    if (client.ftList().contains(index)) {\n      client.ftDropIndex(index);\n    }\n  }\n\n  public SearchWithParamsTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  private void addDocument(String key, Map<String, Object> map) {\n    client.hset(key, RediSearchUtil.toStringMap(map));\n  }\n\n  private static Map<String, Object> toMap(Object... values) {\n    Map<String, Object> map = new HashMap<>();\n    for (int i = 0; i < values.length; i += 2) {\n      map.put((String) values[i], values[i + 1]);\n    }\n    return map;\n  }\n\n  private static Map<String, String> toMap(String... values) {\n    Map<String, String> map = new HashMap<>();\n    for (int i = 0; i < values.length; i += 2) {\n      map.put(values[i], values[i + 1]);\n    }\n    return map;\n  }\n\n  @Test\n  public void create() {\n    assertOK(client.ftCreate(index,\n        FTCreateParams.createParams()\n            .filter(\"@age>16\")\n            .prefix(\"student:\", \"pupil:\"),\n        TextField.of(\"first\"), TextField.of(\"last\"), NumericField.of(\"age\")));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    SearchResult noFilters = client.ftSearch(index);\n    assertEquals(4, noFilters.getTotalResults());\n\n    SearchResult res1 = client.ftSearch(index, \"@first:Jo*\");\n    assertEquals(2, res1.getTotalResults());\n\n    SearchResult res2 = client.ftSearch(index, \"@first:Pat\");\n    assertEquals(1, res2.getTotalResults());\n\n    SearchResult res3 = client.ftSearch(index, \"@last:Rod\");\n    assertEquals(0, res3.getTotalResults());\n  }\n\n  @Test\n  public void createNoParams() {\n    assertOK(client.ftCreate(index,\n        TextField.of(\"first\").weight(1),\n        TextField.of(\"last\").weight(1),\n        NumericField.of(\"age\")));\n\n    addDocument(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", 18));\n    addDocument(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", 17));\n    addDocument(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", 21));\n    addDocument(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", 20));\n\n    SearchResult noFilters = client.ftSearch(index);\n    assertEquals(4, noFilters.getTotalResults());\n\n    SearchResult res1 = client.ftSearch(index, \"@first:Jo*\");\n    assertEquals(2, res1.getTotalResults());\n\n    SearchResult res2 = client.ftSearch(index, \"@first:Pat\");\n    assertEquals(1, res2.getTotalResults());\n\n    SearchResult res3 = client.ftSearch(index, \"@last:Rod\");\n    assertEquals(0, res3.getTotalResults());\n  }\n\n  @Test\n  public void createWithFieldNames() {\n    assertOK(client.ftCreate(index,\n        FTCreateParams.createParams()\n            .addPrefix(\"student:\").addPrefix(\"pupil:\"),\n        TextField.of(\"first\").as(\"given\"),\n        TextField.of(FieldName.of(\"last\").as(\"family\"))));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    SearchResult noFilters = client.ftSearch(index);\n    assertEquals(5, noFilters.getTotalResults());\n\n    SearchResult asGiven = client.ftSearch(index, \"@given:Jo*\");\n    assertEquals(2, asGiven.getTotalResults());\n\n    SearchResult nonLast = client.ftSearch(index, \"@last:Rod\");\n    assertEquals(0, nonLast.getTotalResults());\n\n    SearchResult asFamily = client.ftSearch(index, \"@family:Rod\");\n    assertEquals(1, asFamily.getTotalResults());\n  }\n\n  @Test\n  public void alterAdd() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n    SearchResult res = client.ftSearch(index, \"hello world\");\n    assertEquals(100, res.getTotalResults());\n\n    assertOK(client.ftAlter(index,\n        TagField.of(\"tags\"),\n        TextField.of(\"name\").weight(0.5)));\n\n    for (int i = 0; i < 100; i++) {\n      Map<String, Object> fields2 = new HashMap<>();\n      fields2.put(\"name\", \"name\" + i);\n      fields2.put(\"tags\", String.format(\"tagA,tagB,tag%d\", i));\n      addDocument(String.format(\"doc%d\", i), fields2);\n    }\n    SearchResult res2 = client.ftSearch(index, \"@tags:{tagA}\");\n    assertEquals(100, res2.getTotalResults());\n  }\n\n  @Test\n  public void search() {\n    assertOK(client.ftCreate(index, FTCreateParams.createParams(),\n        TextField.of(\"title\"), TextField.of(\"body\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"body\", \"lorem ipsum\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult result = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams().limit(0, 5).withScores());\n    assertEquals(100, result.getTotalResults());\n    assertEquals(5, result.getDocuments().size());\n    for (Document d : result.getDocuments()) {\n      assertTrue(d.getId().startsWith(\"doc\"));\n      assertTrue(d.getScore() < 100);\n    }\n\n    client.del(\"doc0\");\n\n    result = client.ftSearch(index, \"hello world\");\n    assertEquals(99, result.getTotalResults());\n\n    assertEquals(\"OK\", client.ftDropIndex(index));\n    try {\n      client.ftSearch(index, \"hello world\");\n      fail();\n    } catch (JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void textFieldParams() {\n    assertOK(client.ftCreate(\"testindex\", TextField.of(\"title\")\n        .weight(2.5).noStem().phonetic(\"dm:en\").withSuffixTrie().sortable()));\n\n    assertOK(client.ftCreate(\"testunfindex\", TextField.of(\"title\")\n        .weight(2.5).noStem().phonetic(\"dm:en\").withSuffixTrie().sortableUNF()));\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      assertOK(client.ftCreate(\"testindex-missing\",\n          TextField.of(\"title\").indexMissing().indexEmpty().weight(2.5).noStem().phonetic(\"dm:en\")\n              .withSuffixTrie().sortable()));\n\n      assertOK(client.ftCreate(\"testunfindex-missing\",\n          TextField.of(\"title\").indexMissing().indexEmpty().weight(2.5).noStem().phonetic(\"dm:en\")\n              .withSuffixTrie().sortableUNF()));\n    }\n\n    assertOK(client.ftCreate(\"testnoindex\", TextField.of(\"title\").sortable().noIndex()));\n\n    assertOK(client.ftCreate(\"testunfnoindex\", TextField.of(\"title\").sortableUNF().noIndex()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"optional params since 7.4.0 are being tested\")\n  public void searchTextFieldsCondition() {\n    assertOK(client.ftCreate(index, FTCreateParams.createParams(), TextField.of(\"title\"),\n        TextField.of(\"body\").indexMissing().indexEmpty()));\n\n    Map<String, String> regular = new HashMap<>();\n    regular.put(\"title\", \"hello world\");\n    regular.put(\"body\", \"lorem ipsum\");\n    client.hset(\"regular-doc\", regular);\n\n    Map<String, String> empty = new HashMap<>();\n    empty.put(\"title\", \"hello world\");\n    empty.put(\"body\", \"\");\n    client.hset(\"empty-doc\", empty);\n\n    Map<String, String> missing = new HashMap<>();\n    missing.put(\"title\", \"hello world\");\n    client.hset(\"missing-doc\", missing);\n\n    SearchResult result = client.ftSearch(index, \"\", FTSearchParams.searchParams().dialect(2));\n    assertEquals(0, result.getTotalResults());\n    assertEquals(0, result.getDocuments().size());\n\n    result = client.ftSearch(index, \"*\", FTSearchParams.searchParams().dialect(2));\n    assertEquals(3, result.getTotalResults());\n    assertEquals(3, result.getDocuments().size());\n\n    result = client.ftSearch(index, \"@body:''\", FTSearchParams.searchParams().dialect(2));\n    assertEquals(1, result.getTotalResults());\n    assertEquals(1, result.getDocuments().size());\n    assertEquals(\"empty-doc\", result.getDocuments().get(0).getId());\n\n    result = client.ftSearch(index, \"ismissing(@body)\", FTSearchParams.searchParams().dialect(2));\n    assertEquals(1, result.getTotalResults());\n    assertEquals(1, result.getDocuments().size());\n    assertEquals(\"missing-doc\", result.getDocuments().get(0).getId());\n  }\n\n  @Test\n  public void numericFilter() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\"), NumericField.of(\"price\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n\n    for (int i = 0; i < 100; i++) {\n      fields.put(\"price\", i);\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams().filter(\"price\", 0, 49));\n    assertEquals(50, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      long price = Long.valueOf((String) d.get(\"price\"));\n      assertTrue(price >= 0);\n      assertTrue(price <= 49);\n    }\n\n    res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams().filter(\"price\", 0, true, 49, true));\n    assertEquals(48, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      long price = Long.valueOf((String) d.get(\"price\"));\n      assertTrue(price > 0);\n      assertTrue(price < 49);\n    }\n    res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams().filter(\"price\", 50, 100));\n    assertEquals(50, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n    for (Document d : res.getDocuments()) {\n      long price = Long.valueOf((String) d.get(\"price\"));\n      assertTrue(price >= 50);\n      assertTrue(price <= 100);\n    }\n\n    res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams()\n            .filter(\"price\", 20, Double.POSITIVE_INFINITY));\n    assertEquals(80, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n\n    res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams()\n            .filter(\"price\", Double.NEGATIVE_INFINITY, 10));\n    assertEquals(11, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n  }\n\n  @Test\n  public void numericFieldParams() {\n    assertOK(client.ftCreate(\"testindex\", TextField.of(\"title\"),\n        NumericField.of(\"price\").as(\"px\").sortable()));\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      assertOK(client.ftCreate(\"testindex-missing\", TextField.of(\"title\"),\n          NumericField.of(\"price\").as(\"px\").indexMissing().sortable()));\n    }\n\n    assertOK(client.ftCreate(\"testnoindex\", TextField.of(\"title\"),\n        NumericField.of(\"price\").as(\"px\").sortable().noIndex()));\n  }\n\n  @Test\n  public void stopwords() {\n    assertOK(client.ftCreate(index,\n        FTCreateParams.createParams()\n            .stopwords(\"foo\", \"bar\", \"baz\"),\n        TextField.of(\"title\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world foo bar\");\n    addDocument(\"doc1\", fields);\n    SearchResult res = client.ftSearch(index, \"hello world\");\n    assertEquals(1, res.getTotalResults());\n    res = client.ftSearch(index, \"foo bar\");\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  public void noStopwords() {\n    assertOK(client.ftCreate(index,\n        FTCreateParams.createParams().noStopwords(),\n        TextField.of(\"title\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world foo bar\");\n    fields.put(\"title\", \"hello world foo bar to be or not to be\");\n    addDocument(\"doc1\", fields);\n\n    assertEquals(1, client.ftSearch(index, \"hello world\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"foo bar\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"to be or not to be\").getTotalResults());\n  }\n\n  @Test\n  public void geoFilter() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\"), GeoField.of(\"loc\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"loc\", \"-0.441,51.458\");\n    addDocument(\"doc1\", fields);\n\n    fields.put(\"loc\", \"-0.1,51.2\");\n    addDocument(\"doc2\", fields);\n\n    SearchResult res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams().\n            geoFilter(\"loc\", -0.44, 51.45, 10, GeoUnit.KM));\n    assertEquals(1, res.getTotalResults());\n\n    res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams().\n            geoFilter(\"loc\", -0.44, 51.45, 100, GeoUnit.KM));\n    assertEquals(2, res.getTotalResults());\n  }\n\n  @Test\n  public void geoFilterAndGeoCoordinateObject() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\"), GeoField.of(\"loc\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"loc\", new GeoCoordinate(-0.441, 51.458));\n    addDocument(\"doc1\", fields);\n\n    fields.put(\"loc\", new GeoCoordinate(-0.1, 51.2));\n    addDocument(\"doc2\", fields);\n\n    SearchResult res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams()\n            .geoFilter(new FTSearchParams.GeoFilter(\"loc\", -0.44, 51.45, 10, GeoUnit.KM)));\n    assertEquals(1, res.getTotalResults());\n\n    res = client.ftSearch(index, \"hello world\",\n        FTSearchParams.searchParams()\n            .geoFilter(new FTSearchParams.GeoFilter(\"loc\", -0.44, 51.45, 100, GeoUnit.KM)));\n    assertEquals(2, res.getTotalResults());\n  }\n\n  @Test\n  public void geoFieldParams() {\n    assertOK(client.ftCreate(\"testindex\", TextField.of(\"title\"),\n        GeoField.of(\"location\").as(\"loc\").sortable()));\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      assertOK(client.ftCreate(\"testindex-missing\", TextField.of(\"title\"),\n          GeoField.of(\"location\").as(\"loc\").indexMissing().sortable()));\n    }\n\n    assertOK(client.ftCreate(\"testnoindex\", TextField.of(\"title\"),\n        GeoField.of(\"location\").as(\"loc\").sortable().noIndex()));\n  }\n\n  @Test\n  public void geoShapeFilterSpherical() throws ParseException {\n    final WKTReader reader = new WKTReader();\n    final GeometryFactory factory = new GeometryFactory();\n\n    assertOK(client.ftCreate(index, GeoShapeField.of(\"geom\", CoordinateSystem.SPHERICAL)));\n\n    // polygon type\n    final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(34.9001, 29.7001),\n        new Coordinate(34.9001, 29.7100), new Coordinate(34.9100, 29.7100),\n        new Coordinate(34.9100, 29.7001), new Coordinate(34.9001, 29.7001)});\n    client.hsetObject(\"small\", \"geom\", small);\n\n    final Polygon large = factory.createPolygon(new Coordinate[]{new Coordinate(34.9001, 29.7001),\n        new Coordinate(34.9001, 29.7200), new Coordinate(34.9200, 29.7200),\n        new Coordinate(34.9200, 29.7001), new Coordinate(34.9001, 29.7001)});\n    client.hsetObject(\"large\", \"geom\", large);\n\n    // within condition\n    final Polygon within = factory.createPolygon(new Coordinate[]{new Coordinate(34.9000, 29.7000),\n        new Coordinate(34.9000, 29.7150), new Coordinate(34.9150, 29.7150),\n        new Coordinate(34.9150, 29.7000), new Coordinate(34.9000, 29.7000)});\n\n    SearchResult result = client.ftSearch(index, \"@geom:[within $poly]\",\n        FTSearchParams.searchParams().addParam(\"poly\", within).dialect(3));\n    assertEquals(1, result.getTotalResults());\n    assertEquals(1, result.getDocuments().size());\n    assertEquals(small, reader.read(result.getDocuments().get(0).getString(\"geom\")));\n\n    // contains condition\n    final Polygon contains = factory.createPolygon(new Coordinate[]{new Coordinate(34.9002, 29.7002),\n        new Coordinate(34.9002, 29.7050), new Coordinate(34.9050, 29.7050),\n        new Coordinate(34.9050, 29.7002), new Coordinate(34.9002, 29.7002)});\n\n    result = client.ftSearch(index, \"@geom:[contains $poly]\",\n        FTSearchParams.searchParams().addParam(\"poly\", contains).dialect(3));\n    assertEquals(2, result.getTotalResults());\n    assertEquals(2, result.getDocuments().size());\n\n    // point type\n    final Point point = factory.createPoint(new Coordinate(34.9010, 29.7010));\n    client.hset(\"point\", \"geom\", point.toString());\n\n    result = client.ftSearch(index, \"@geom:[within $poly]\",\n        FTSearchParams.searchParams().addParam(\"poly\", within).dialect(3));\n    assertEquals(2, result.getTotalResults());\n    assertEquals(2, result.getDocuments().size());\n  }\n\n  @Test\n  public void geoShapeFilterFlat() throws ParseException {\n    final WKTReader reader = new WKTReader();\n    final GeometryFactory factory = new GeometryFactory();\n\n    assertOK(client.ftCreate(index, GeoShapeField.of(\"geom\", CoordinateSystem.FLAT)));\n\n    // polygon type\n    final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(20, 20),\n        new Coordinate(20, 100), new Coordinate(100, 100), new Coordinate(100, 20), new Coordinate(20, 20)});\n    client.hsetObject(\"small\", \"geom\", small);\n\n    final Polygon large = factory.createPolygon(new Coordinate[]{new Coordinate(10, 10),\n        new Coordinate(10, 200), new Coordinate(200, 200), new Coordinate(200, 10), new Coordinate(10, 10)});\n    client.hsetObject(\"large\", \"geom\", large);\n\n    // within condition\n    final Polygon within = factory.createPolygon(new Coordinate[]{new Coordinate(0, 0),\n        new Coordinate(0, 150), new Coordinate(150, 150), new Coordinate(150, 0), new Coordinate(0, 0)});\n\n    SearchResult result = client.ftSearch(index, \"@geom:[within $poly]\",\n        FTSearchParams.searchParams().addParam(\"poly\", within).dialect(3));\n    assertEquals(1, result.getTotalResults());\n    assertEquals(1, result.getDocuments().size());\n    assertEquals(small, reader.read(result.getDocuments().get(0).getString(\"geom\")));\n\n    // contains condition\n    final Polygon contains = factory.createPolygon(new Coordinate[]{new Coordinate(25, 25),\n        new Coordinate(25, 50), new Coordinate(50, 50), new Coordinate(50, 25), new Coordinate(25, 25)});\n\n    result = client.ftSearch(index, \"@geom:[contains $poly]\",\n        FTSearchParams.searchParams().addParam(\"poly\", contains).dialect(3));\n    assertEquals(2, result.getTotalResults());\n    assertEquals(2, result.getDocuments().size());\n\n    // intersects and disjoint\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      final Polygon disjointersect = factory.createPolygon(new Coordinate[]{new Coordinate(150, 150),\n        new Coordinate(150, 250), new Coordinate(250, 250), new Coordinate(250, 150), new Coordinate(150, 150)});\n\n      result = client.ftSearch(index, \"@geom:[intersects $poly]\",\n          FTSearchParams.searchParams().addParam(\"poly\", disjointersect).dialect(3));\n      assertEquals(1, result.getTotalResults());\n      assertEquals(1, result.getDocuments().size());\n      assertEquals(large, reader.read(result.getDocuments().get(0).getString(\"geom\")));\n\n      result = client.ftSearch(index, \"@geom:[disjoint $poly]\",\n          FTSearchParams.searchParams().addParam(\"poly\", disjointersect).dialect(3));\n      assertEquals(1, result.getTotalResults());\n      assertEquals(1, result.getDocuments().size());\n      assertEquals(small, reader.read(result.getDocuments().get(0).getString(\"geom\")));\n    }\n\n    // point type\n    final Point point = factory.createPoint(new Coordinate(30, 30));\n    client.hsetObject(\"point\", \"geom\", point);\n\n    result = client.ftSearch(index, \"@geom:[within $poly]\",\n        FTSearchParams.searchParams().addParam(\"poly\", within).dialect(3));\n    assertEquals(2, result.getTotalResults());\n    assertEquals(2, result.getDocuments().size());\n  }\n\n  @Test\n  public void geoShapeFieldParams() {\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      assertOK(client.ftCreate(\"testindex-missing\",\n          GeoShapeField.of(\"geometry\", CoordinateSystem.SPHERICAL).as(\"geom\").indexMissing()));\n    }\n\n    assertOK(client.ftCreate(\"testnoindex\",\n        GeoShapeField.of(\"geometry\", CoordinateSystem.SPHERICAL).as(\"geom\").noIndex()));\n  }\n\n  @Test\n  public void testQueryFlags() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    for (int i = 0; i < 100; i++) {\n      fields.put(\"title\", i % 2 != 0 ? \"hello worlds\" : \"hello world\");\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, \"hello\",\n        FTSearchParams.searchParams().withScores());\n    assertEquals(100, res.getTotalResults());\n    assertEquals(10, res.getDocuments().size());\n\n    for (Document d : res.getDocuments()) {\n      assertTrue(d.getId().startsWith(\"doc\"));\n      assertTrue(((String) d.get(\"title\")).startsWith(\"hello world\"));\n    }\n//\n//    res = client.ftSearch(index, \"hello\",\n//        FTSearchParams.searchParams().withScores().explainScore());\n//    assertEquals(100, res.getTotalResults());\n//    assertEquals(10, res.getDocuments().size());\n//\n//    for (Document d : res.getDocuments()) {\n//      assertTrue(d.getId().startsWith(\"doc\"));\n//      assertTrue(((String) d.get(\"title\")).startsWith(\"hello world\"));\n//    }\n\n    res = client.ftSearch(index, \"hello\",\n        FTSearchParams.searchParams().noContent());\n    for (Document d : res.getDocuments()) {\n      assertTrue(d.getId().startsWith(\"doc\"));\n      if (protocol != RedisProtocol.RESP3) {\n        assertEquals(1.0, d.getScore(), 0);\n        assertNull(d.get(\"title\"));\n      } else {\n        assertNull(d.getScore());\n        assertThrows(NullPointerException.class, () -> d.get(\"title\"));\n      }\n    }\n\n    // test verbatim vs. stemming\n    res = client.ftSearch(index, \"hello worlds\");\n    assertEquals(100, res.getTotalResults());\n    res = client.ftSearch(index, \"hello worlds\", FTSearchParams.searchParams().verbatim());\n    assertEquals(50, res.getTotalResults());\n    res = client.ftSearch(index, \"hello a world\", FTSearchParams.searchParams().verbatim());\n    assertEquals(50, res.getTotalResults());\n    res = client.ftSearch(index, \"hello a worlds\", FTSearchParams.searchParams().verbatim());\n    assertEquals(50, res.getTotalResults());\n    res = client.ftSearch(index, \"hello a world\", FTSearchParams.searchParams().verbatim().noStopwords());\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  public void testQueryParams() {\n    assertOK(client.ftCreate(index, NumericField.of(\"numval\")));\n\n    client.hset(\"1\", \"numval\", \"1\");\n    client.hset(\"2\", \"numval\", \"2\");\n    client.hset(\"3\", \"numval\", \"3\");\n\n    assertEquals(2, client.ftSearch(index, \"@numval:[$min $max]\",\n        FTSearchParams.searchParams().addParam(\"min\", 1).addParam(\"max\", 2)\n            .dialect(2)).getTotalResults());\n\n    Map<String, Object> paramValues = new HashMap<>();\n    paramValues.put(\"min\", 1);\n    paramValues.put(\"max\", 2);\n    assertEquals(2, client.ftSearch(index, \"@numval:[$min $max]\",\n        FTSearchParams.searchParams().params(paramValues)\n            .dialect(2)).getTotalResults());\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4) ) {\n      assertEquals(1, client.ftSearch(index, \"@numval:[$eq]\",\n          FTSearchParams.searchParams().addParam(\"eq\", 2).dialect(4)).getTotalResults());\n    }\n  }\n\n  @Test\n  public void testSortQueryFlags() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\").sortable()));\n\n    Map<String, Object> fields = new HashMap<>();\n\n    fields.put(\"title\", \"b title\");\n    addDocument(\"doc1\", fields);\n\n    fields.put(\"title\", \"a title\");\n    addDocument(\"doc2\", fields);\n\n    fields.put(\"title\", \"c title\");\n    addDocument(\"doc3\", fields);\n\n    SearchResult res = client.ftSearch(index, \"title\",\n        FTSearchParams.searchParams().sortBy(\"title\", SortingOrder.ASC));\n\n    assertEquals(3, res.getTotalResults());\n    Document doc1 = res.getDocuments().get(0);\n    assertEquals(\"a title\", doc1.get(\"title\"));\n\n    doc1 = res.getDocuments().get(1);\n    assertEquals(\"b title\", doc1.get(\"title\"));\n\n    doc1 = res.getDocuments().get(2);\n    assertEquals(\"c title\", doc1.get(\"title\"));\n  }\n\n  @Test\n  public void testJsonWithAlias() {\n    assertOK(client.ftCreate(index,\n        FTCreateParams.createParams()\n            .on(IndexDataType.JSON)\n            .prefix(\"king:\"),\n        TextField.of(\"$.name\").as(\"name\"),\n        NumericField.of(\"$.num\").as(\"num\")));\n\n    Map<String, Object> king1 = new HashMap<>();\n    king1.put(\"name\", \"henry\");\n    king1.put(\"num\", 42);\n    client.jsonSet(\"king:1\", Path.ROOT_PATH, king1);\n\n    Map<String, Object> king2 = new HashMap<>();\n    king2.put(\"name\", \"james\");\n    king2.put(\"num\", 3.14);\n    client.jsonSet(\"king:2\", Path.ROOT_PATH, king2);\n\n    SearchResult res = client.ftSearch(index, \"@name:henry\");\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"king:1\", res.getDocuments().get(0).getId());\n\n    res = client.ftSearch(index, \"@num:[0 10]\");\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"king:2\", res.getDocuments().get(0).getId());\n\n    res = client.ftSearch(index, \"@num:[42 42]\", FTSearchParams.searchParams());\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"king:1\", res.getDocuments().get(0).getId());\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      res = client.ftSearch(index, \"@num:[42]\", FTSearchParams.searchParams().dialect(4));\n      assertEquals(1, res.getTotalResults());\n      assertEquals(\"king:1\", res.getDocuments().get(0).getId());\n    }\n  }\n\n  @Test\n  public void dropIndex() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, \"hello world\");\n    assertEquals(100, res.getTotalResults());\n\n    assertEquals(\"OK\", client.ftDropIndex(index));\n\n    try {\n      client.ftSearch(index, \"hello world\");\n      fail(\"Index should not exist.\");\n    } catch (JedisDataException de) {\n      // toLowerCase - Error message updated to \"No such index\" with Redis 8.0.0\n      assertTrue(de.getMessage().toLowerCase().contains(\"no such index\"));\n    }\n    assertEquals(100, client.dbSize());\n  }\n\n  @Test\n  public void dropIndexDD() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\")));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    for (int i = 0; i < 100; i++) {\n      addDocument(String.format(\"doc%d\", i), fields);\n    }\n\n    SearchResult res = client.ftSearch(index, \"hello world\");\n    assertEquals(100, res.getTotalResults());\n\n    assertEquals(\"OK\", client.ftDropIndexDD(index));\n\n    Set<String> keys = client.keys(\"*\");\n    assertTrue(keys.isEmpty());\n    assertEquals(0, client.dbSize());\n  }\n\n  @Test\n  public void noStem() {\n    assertOK(client.ftCreate(index, new TextField(\"stemmed\").weight(1.0),\n        new TextField(\"notStemmed\").weight(1.0).noStem()));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"stemmed\", \"located\");\n    doc.put(\"notStemmed\", \"located\");\n    addDocument(\"doc\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, \"@stemmed:location\");\n    assertEquals(1, res.getTotalResults());\n\n    res = client.ftSearch(index, \"@notStemmed:location\");\n    assertEquals(0, res.getTotalResults());\n  }\n\n  @Test\n  public void phoneticMatch() {\n    assertOK(client.ftCreate(index, new TextField(\"noPhonetic\").weight(1.0),\n        new TextField(\"withPhonetic\").weight(1.0).phonetic(\"dm:en\")));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"noPhonetic\", \"morfix\");\n    doc.put(\"withPhonetic\", \"morfix\");\n    addDocument(\"doc\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, \"@withPhonetic:morphix=>{$phonetic:true}\");\n    assertEquals(1, res.getTotalResults());\n\n    try {\n      client.ftSearch(index, \"@noPhonetic:morphix=>{$phonetic:true}\");\n      fail();\n    } catch (JedisDataException e) {/*field does not support phonetics*/\n    }\n\n    SearchResult res3 = client.ftSearch(index, \"@withPhonetic:morphix=>{$phonetic:false}\");\n    assertEquals(0, res3.getTotalResults());\n  }\n\n  @Test\n  public void info() {\n    Collection<SchemaField> sc = new ArrayList<>();\n    sc.add(TextField.of(\"title\").weight(5));\n    sc.add(TextField.of(\"plot\").sortable());\n    sc.add(TagField.of(\"genre\").separator(',').sortable());\n    sc.add(NumericField.of(\"release_year\").sortable());\n    sc.add(NumericField.of(\"rating\").sortable());\n    sc.add(NumericField.of(\"votes\").sortable());\n\n    assertOK(client.ftCreate(index, sc));\n\n    Map<String, Object> info = client.ftInfo(index);\n    assertEquals(index, info.get(\"index_name\"));\n    assertEquals(6, ((List) info.get(\"attributes\")).size());\n    if (protocol != RedisProtocol.RESP3) {\n      assertEquals(\"global_idle\", ((List) info.get(\"cursor_stats\")).get(0));\n      assertEquals(0L, ((List) info.get(\"cursor_stats\")).get(1));\n    } else {\n      assertEquals(0L, ((Map) info.get(\"cursor_stats\")).get(\"global_idle\"));\n    }\n  }\n\n  @Test\n  public void noIndexAndSortBy() {\n    assertOK(client.ftCreate(index, TextField.of(\"f1\").sortable().noIndex(), TextField.of(\"f2\")));\n\n    Map<String, Object> mm = new HashMap<>();\n\n    mm.put(\"f1\", \"MarkZZ\");\n    mm.put(\"f2\", \"MarkZZ\");\n    addDocument(\"doc1\", mm);\n\n    mm.clear();\n    mm.put(\"f1\", \"MarkAA\");\n    mm.put(\"f2\", \"MarkBB\");\n    addDocument(\"doc2\", mm);\n\n    SearchResult res = client.ftSearch(index, \"@f1:Mark*\");\n    assertEquals(0, res.getTotalResults());\n\n    res = client.ftSearch(index, \"@f2:Mark*\");\n    assertEquals(2, res.getTotalResults());\n\n    res = client.ftSearch(index, \"@f2:Mark*\",\n        FTSearchParams.searchParams().sortBy(\"f1\", SortingOrder.DESC));\n    assertEquals(2, res.getTotalResults());\n\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n\n    res = client.ftSearch(index, \"@f2:Mark*\",\n        FTSearchParams.searchParams().sortBy(\"f1\", SortingOrder.ASC));\n    assertEquals(\"doc2\", res.getDocuments().get(0).getId());\n  }\n\n  @Test\n  public void testHighlightSummarize() {\n    assertOK(client.ftCreate(index, TextField.of(\"text\").weight(1)));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"text\", \"Redis is often referred as a data structures server. What this means is that \"\n        + \"Redis provides access to mutable data structures via a set of commands, which are sent \"\n        + \"using a server-client model with TCP sockets and a simple protocol. So different \"\n        + \"processes can query and modify the same data structures in a shared way\");\n    // Add a document\n    addDocument(\"foo\", doc);\n\n    SearchResult res = client.ftSearch(index, \"data\", FTSearchParams.searchParams().highlight().summarize());\n    assertEquals(\"is often referred as a <b>data</b> structures server. What this means is that \"\n        + \"Redis provides... What this means is that Redis provides access to mutable <b>data</b> \"\n        + \"structures via a set of commands, which are sent using a... So different processes can \"\n        + \"query and modify the same <b>data</b> structures in a shared... \",\n        res.getDocuments().get(0).get(\"text\"));\n\n    res = client.ftSearch(index, \"data\", FTSearchParams.searchParams()\n        .highlight(FTSearchParams.highlightParams().tags(\"<u>\", \"</u>\"))\n        .summarize());\n    assertEquals(\"is often referred as a <u>data</u> structures server. What this means is that \"\n        + \"Redis provides... What this means is that Redis provides access to mutable <u>data</u> \"\n        + \"structures via a set of commands, which are sent using a... So different processes can \"\n        + \"query and modify the same <u>data</u> structures in a shared... \",\n        res.getDocuments().get(0).get(\"text\"));\n  }\n\n  @Test\n  public void getTagField() {\n    assertOK(client.ftCreate(index, TextField.of(\"title\"), TagField.of(\"category\")));\n\n    Map<String, Object> fields1 = new HashMap<>();\n    fields1.put(\"title\", \"hello world\");\n    fields1.put(\"category\", \"red\");\n    addDocument(\"foo\", fields1);\n\n    Map<String, Object> fields2 = new HashMap<>();\n    fields2.put(\"title\", \"hello world\");\n    fields2.put(\"category\", \"blue\");\n    addDocument(\"bar\", fields2);\n\n    Map<String, Object> fields3 = new HashMap<>();\n    fields3.put(\"title\", \"hello world\");\n    fields3.put(\"category\", \"green,yellow\");\n    addDocument(\"baz\", fields3);\n\n    Map<String, Object> fields4 = new HashMap<>();\n    fields4.put(\"title\", \"hello world\");\n    fields4.put(\"category\", \"orange;purple\");\n    addDocument(\"qux\", fields4);\n\n    assertEquals(1, client.ftSearch(index, \"@category:{red}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"@category:{blue}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"hello @category:{red}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"hello @category:{blue}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"@category:{yellow}\").getTotalResults());\n    assertEquals(0, client.ftSearch(index, \"@category:{purple}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"@category:{orange\\\\;purple}\").getTotalResults());\n    assertEquals(4, client.ftSearch(index, \"hello\").getTotalResults());\n\n    assertEquals(new HashSet<>(Arrays.asList(\"red\", \"blue\", \"green\", \"yellow\", \"orange;purple\")),\n        client.ftTagVals(index, \"category\"));\n  }\n\n  @Test\n  public void testGetTagFieldWithNonDefaultSeparator() {\n    assertOK(client.ftCreate(index,\n        TextField.of(\"title\"),\n        TagField.of(\"category\").separator(';')));\n\n    Map<String, Object> fields1 = new HashMap<>();\n    fields1.put(\"title\", \"hello world\");\n    fields1.put(\"category\", \"red\");\n    addDocument(\"foo\", fields1);\n\n    Map<String, Object> fields2 = new HashMap<>();\n    fields2.put(\"title\", \"hello world\");\n    fields2.put(\"category\", \"blue\");\n    addDocument(\"bar\", fields2);\n\n    Map<String, Object> fields3 = new HashMap<>();\n    fields3.put(\"title\", \"hello world\");\n    fields3.put(\"category\", \"green;yellow\");\n    addDocument(\"baz\", fields3);\n\n    Map<String, Object> fields4 = new HashMap<>();\n    fields4.put(\"title\", \"hello world\");\n    fields4.put(\"category\", \"orange,purple\");\n    addDocument(\"qux\", fields4);\n\n    assertEquals(1, client.ftSearch(index, \"@category:{red}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"@category:{blue}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"hello @category:{red}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"hello @category:{blue}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"hello @category:{yellow}\").getTotalResults());\n    assertEquals(0, client.ftSearch(index, \"@category:{purple}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"@category:{orange\\\\,purple}\").getTotalResults());\n    assertEquals(4, client.ftSearch(index, \"hello\").getTotalResults());\n\n    assertEquals(new HashSet<>(Arrays.asList(\"red\", \"blue\", \"green\", \"yellow\", \"orange,purple\")),\n        client.ftTagVals(index, \"category\"));\n  }\n\n  @Test\n  public void caseSensitiveTagField() {\n    assertOK(client.ftCreate(index,\n        TextField.of(\"title\"),\n        TagField.of(\"category\").caseSensitive()));\n\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"category\", \"RedX\");\n    addDocument(\"foo\", fields);\n\n    assertEquals(0, client.ftSearch(index, \"@category:{redx}\").getTotalResults());\n    assertEquals(0, client.ftSearch(index, \"@category:{redX}\").getTotalResults());\n    assertEquals(0, client.ftSearch(index, \"@category:{Redx}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"@category:{RedX}\").getTotalResults());\n    assertEquals(1, client.ftSearch(index, \"hello\").getTotalResults());\n  }\n\n  @Test\n  public void tagFieldParams() {\n    assertOK(client.ftCreate(\"testindex\", TextField.of(\"title\"),\n        TagField.of(\"category\").as(\"cat\").separator(',')\n            .caseSensitive().withSuffixTrie().sortable()));\n\n    assertOK(client.ftCreate(\"testunfindex\", TextField.of(\"title\"),\n        TagField.of(\"category\").as(\"cat\").separator(',')\n            .caseSensitive().withSuffixTrie().sortableUNF()));\n\n    if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {\n      assertOK(client.ftCreate(\"testindex-missing\", TextField.of(\"title\"),\n          TagField.of(\"category\").as(\"cat\").indexMissing().indexEmpty().separator(',')\n              .caseSensitive().withSuffixTrie().sortable()));\n\n      assertOK(client.ftCreate(\"testunfindex-missing\", TextField.of(\"title\"),\n          TagField.of(\"category\").as(\"cat\").indexMissing().indexEmpty().separator(',')\n              .caseSensitive().withSuffixTrie().sortableUNF()));\n    }\n\n    assertOK(client.ftCreate(\"testnoindex\", TextField.of(\"title\"),\n        TagField.of(\"category\").as(\"cat\").sortable().noIndex()));\n\n    assertOK(client.ftCreate(\"testunfnoindex\", TextField.of(\"title\"),\n        TagField.of(\"category\").as(\"cat\").sortableUNF().noIndex()));\n  }\n\n  @Test\n  public void returnFields() {\n    assertOK(client.ftCreate(index, TextField.of(\"field1\"), TextField.of(\"field2\")));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value1\");\n    doc.put(\"field2\", \"value2\");\n    addDocument(\"doc\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams().returnFields(\"field1\"));\n    assertEquals(1, res.getTotalResults());\n    Document ret = res.getDocuments().get(0);\n    assertEquals(\"value1\", ret.get(\"field1\"));\n    assertNull(ret.get(\"field2\"));\n\n    res = client.ftSearch(index, \"*\", FTSearchParams.searchParams().returnField(\"field1\", true));\n    assertEquals(\"value1\", res.getDocuments().get(0).get(\"field1\"));\n\n    res = client.ftSearch(index, \"*\", FTSearchParams.searchParams().returnField(\"field1\", false));\n    assertArrayEquals(\"value1\".getBytes(), (byte[]) res.getDocuments().get(0).get(\"field1\"));\n  }\n\n  @Test\n  public void returnFieldsNames() {\n    assertOK(client.ftCreate(index, TextField.of(\"a\"), TextField.of(\"b\"), TextField.of(\"c\")));\n\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"a\", \"value1\");\n    map.put(\"b\", \"value2\");\n    map.put(\"c\", \"value3\");\n    addDocument(\"doc\", map);\n\n    // Query\n    SearchResult res = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams()\n            .returnFields(FieldName.of(\"a\"),\n                FieldName.of(\"b\").as(\"d\")));\n    assertEquals(1, res.getTotalResults());\n    Document doc = res.getDocuments().get(0);\n    assertEquals(\"value1\", doc.get(\"a\"));\n    assertNull(doc.get(\"b\"));\n    assertEquals(\"value2\", doc.get(\"d\"));\n    assertNull(doc.get(\"c\"));\n\n    res = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams()\n            .returnField(FieldName.of(\"a\"))\n            .returnField(FieldName.of(\"b\").as(\"d\")));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"value1\", res.getDocuments().get(0).get(\"a\"));\n    assertEquals(\"value2\", res.getDocuments().get(0).get(\"d\"));\n\n    res = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams()\n            .returnField(FieldName.of(\"a\"), true)\n            .returnField(FieldName.of(\"b\").as(\"d\"), true));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"value1\", res.getDocuments().get(0).get(\"a\"));\n    assertEquals(\"value2\", res.getDocuments().get(0).get(\"d\"));\n\n    res = client.ftSearch(index, \"*\",\n        FTSearchParams.searchParams()\n            .returnField(FieldName.of(\"a\"), false)\n            .returnField(FieldName.of(\"b\").as(\"d\"), false));\n    assertEquals(1, res.getTotalResults());\n    assertArrayEquals(\"value1\".getBytes(), (byte[]) res.getDocuments().get(0).get(\"a\"));\n    assertArrayEquals(\"value2\".getBytes(), (byte[]) res.getDocuments().get(0).get(\"d\"));\n  }\n\n  @Test\n  public void inKeys() {\n    assertOK(client.ftCreate(index, TextField.of(\"field1\"), TextField.of(\"field2\")));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value\");\n    doc.put(\"field2\", \"not\");\n    // Store it\n    addDocument(\"doc1\", doc);\n    addDocument(\"doc2\", doc);\n\n    // Query\n    SearchResult res = client.ftSearch(index, \"value\",\n        FTSearchParams.searchParams().inKeys(\"doc1\"));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).get(\"field1\"));\n    assertNull(res.getDocuments().get(0).get(\"value\"));\n  }\n\n  @Test\n  public void alias() {\n    assertOK(client.ftCreate(index, TextField.of(\"field1\")));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"value\");\n    addDocument(\"doc1\", doc);\n\n    assertEquals(\"OK\", client.ftAliasAdd(\"ALIAS1\", index));\n    SearchResult res1 = client.ftSearch(\"ALIAS1\", \"*\",\n        FTSearchParams.searchParams().returnFields(\"field1\"));\n    assertEquals(1, res1.getTotalResults());\n    assertEquals(\"value\", res1.getDocuments().get(0).get(\"field1\"));\n\n    assertEquals(\"OK\", client.ftAliasUpdate(\"ALIAS2\", index));\n    SearchResult res2 = client.ftSearch(\"ALIAS2\", \"*\",\n        FTSearchParams.searchParams().returnFields(\"field1\"));\n    assertEquals(1, res2.getTotalResults());\n    assertEquals(\"value\", res2.getDocuments().get(0).get(\"field1\"));\n\n    try {\n      client.ftAliasDel(\"ALIAS3\");\n      fail(\"Should throw JedisDataException\");\n    } catch (JedisDataException e) {\n      // Alias does not exist\n    }\n    assertEquals(\"OK\", client.ftAliasDel(\"ALIAS2\"));\n    try {\n      client.ftAliasDel(\"ALIAS2\");\n      fail(\"Should throw JedisDataException\");\n    } catch (JedisDataException e) {\n      // Alias does not exist\n    }\n  }\n\n  @Test\n  public void synonym() {\n    assertOK(client.ftCreate(index, TextField.of(\"name\").weight(1), TextField.of(\"addr\").weight(1)));\n\n    long group1 = 345L;\n    long group2 = 789L;\n    String group1_str = Long.toString(group1);\n    String group2_str = Long.toString(group2);\n    assertEquals(\"OK\", client.ftSynUpdate(index, group1_str, \"girl\", \"baby\"));\n    assertEquals(\"OK\", client.ftSynUpdate(index, group1_str, \"child\"));\n    assertEquals(\"OK\", client.ftSynUpdate(index, group2_str, \"child\"));\n\n    Map<String, List<String>> dump = client.ftSynDump(index);\n\n    Map<String, List<String>> expected = new HashMap<>();\n    expected.put(\"girl\", Arrays.asList(group1_str));\n    expected.put(\"baby\", Arrays.asList(group1_str));\n    expected.put(\"child\", Arrays.asList(group1_str, group2_str));\n    assertEquals(expected, dump);\n  }\n\n  @Test\n  public void slop() {\n    assertOK(client.ftCreate(index, TextField.of(\"field1\"), TextField.of(\"field2\")));\n\n    Map<String, Object> doc = new HashMap<>();\n    doc.put(\"field1\", \"ok hi jedis\");\n    addDocument(\"doc1\", doc);\n\n    SearchResult res = client.ftSearch(index, \"ok jedis\", FTSearchParams.searchParams().slop(0));\n    assertEquals(0, res.getTotalResults());\n\n    res = client.ftSearch(index, \"ok jedis\", FTSearchParams.searchParams().slop(1));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"ok hi jedis\", res.getDocuments().get(0).get(\"field1\"));\n  }\n\n  @Test\n  public void timeout() {\n    assertOK(client.ftCreate(index, TextField.of(\"field1\"), TextField.of(\"field2\")));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"field1\", \"value\");\n    map.put(\"field2\", \"not\");\n    client.hset(\"doc1\", map);\n\n    SearchResult res = client.ftSearch(index, \"value\", FTSearchParams.searchParams().timeout(1000));\n    assertEquals(1, res.getTotalResults());\n    assertEquals(\"doc1\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).get(\"field1\"));\n    assertEquals(\"not\", res.getDocuments().get(0).get(\"field2\"));\n  }\n\n  @Test\n  public void inOrder() {\n    assertOK(client.ftCreate(index, TextField.of(\"field1\"), TextField.of(\"field2\")));\n\n    Map<String, String> map = new HashMap<>();\n    map.put(\"field1\", \"value\");\n    map.put(\"field2\", \"not\");\n    client.hset(\"doc2\", map);\n    client.hset(\"doc1\", map);\n\n    SearchResult res = client.ftSearch(index, \"value\", FTSearchParams.searchParams().inOrder());\n    assertEquals(2, res.getTotalResults());\n    assertEquals(\"doc2\", res.getDocuments().get(0).getId());\n    assertEquals(\"value\", res.getDocuments().get(0).get(\"field1\"));\n    assertEquals(\"not\", res.getDocuments().get(0).get(\"field2\"));\n  }\n\n  @Test\n  public void testHNSWVectorSimilarity() {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    assertOK(client.ftCreate(index, VectorField.builder().fieldName(\"v\")\n        .algorithm(VectorAlgorithm.HNSW).attributes(attr).build()));\n\n    client.hset(\"a\", \"v\", \"aaaaaaaa\");\n    client.hset(\"b\", \"v\", \"aaaabaaa\");\n    client.hset(\"c\", \"v\", \"aaaaabaa\");\n\n    FTSearchParams searchParams = FTSearchParams.searchParams()\n        .addParam(\"vec\", \"aaaaaaaa\")\n        .sortBy(\"__v_score\", SortingOrder.ASC)\n        .returnFields(\"__v_score\")\n        .dialect(2);\n    Document doc1 = client.ftSearch(index, \"*=>[KNN 2 @v $vec]\", searchParams).getDocuments().get(0);\n    assertEquals(\"a\", doc1.getId());\n    assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  public void testFlatVectorSimilarity() {\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\")\n            .algorithm(VectorAlgorithm.FLAT)\n            .addAttribute(\"TYPE\", \"FLOAT32\")\n            .addAttribute(\"DIM\", 2)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\")\n            .build()\n    ));\n\n    client.hset(\"a\", \"v\", \"aaaaaaaa\");\n    client.hset(\"b\", \"v\", \"aaaabaaa\");\n    client.hset(\"c\", \"v\", \"aaaaabaa\");\n\n    FTSearchParams searchParams = FTSearchParams.searchParams()\n        .addParam(\"vec\", \"aaaaaaaa\")\n        .sortBy(\"__v_score\", SortingOrder.ASC)\n        .returnFields(\"__v_score\")\n        .dialect(2);\n\n    Document doc1 = client.ftSearch(index, \"*=>[KNN 2 @v $vec]\", searchParams).getDocuments().get(0);\n    assertEquals(\"a\", doc1.getId());\n    assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  public void testFlatVectorSimilarityInt8() {\n    assumeTrue(RedisConditions.of(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),\n        \"INT8\");\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\").algorithm(VectorAlgorithm.FLAT)\n            .addAttribute(\"TYPE\", \"INT8\").addAttribute(\"DIM\", 2)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\").build()));\n\n    byte[] a = { 127, 1 };\n    byte[] b = { 127, 10 };\n    byte[] c = { 127, 100 };\n\n    client.hset(\"a\".getBytes(), \"v\".getBytes(), a);\n    client.hset(\"b\".getBytes(), \"v\".getBytes(), b);\n    client.hset(\"c\".getBytes(), \"v\".getBytes(), c);\n\n    FTSearchParams searchParams = FTSearchParams.searchParams().addParam(\"vec\", a)\n        .sortBy(\"__v_score\", SortingOrder.ASC).returnFields(\"__v_score\");\n\n    Document doc1 = client.ftSearch(index, \"*=>[KNN 2 @v $vec]\", searchParams).getDocuments()\n        .get(0);\n    assertEquals(\"a\", doc1.getId());\n    assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"no optional params before 7.4.0\")\n  public void vectorFieldParams() {\n    Map<String, Object> attr = new HashMap<>();\n    attr.put(\"TYPE\", \"FLOAT32\");\n    attr.put(\"DIM\", 2);\n    attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n    assertOK(client.ftCreate(\"testindex-missing\", new VectorField(\"vector\", VectorAlgorithm.HNSW, attr).as(\"vec\").indexMissing()));\n\n    // assertOK(client.ftCreate(\"testnoindex\", new VectorField(\"vector\", VectorAlgorithm.HNSW, attr).as(\"vec\").noIndex()));\n    // throws Field `NOINDEX` does not have a type\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"FLOAT16\")\n  public void float16StorageType() {\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\")\n            .algorithm(VectorAlgorithm.HNSW)\n            .addAttribute(\"TYPE\", \"FLOAT16\")\n            .addAttribute(\"DIM\", 4)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\")\n            .build()));\n  }\n\n  @Test\n  @SinceRedisVersion(value = \"7.4.0\", message = \"BFLOAT16\")\n  public void bfloat16StorageType() {\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\")\n            .algorithm(VectorAlgorithm.HNSW)\n            .addAttribute(\"TYPE\", \"BFLOAT16\")\n            .addAttribute(\"DIM\", 4)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\")\n            .build()));\n  }\n\n  @Test\n  public void int8StorageType() {\n    assumeTrue(RedisConditions.of(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),\n        \"INT8\");\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\").algorithm(VectorAlgorithm.HNSW)\n            .addAttribute(\"TYPE\", \"INT8\").addAttribute(\"DIM\", 4)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\").build()));\n  }\n\n  @Test\n  public void uint8StorageType() {\n    assumeTrue(RedisConditions.of(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),\n        \"UINT8\");\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\").algorithm(VectorAlgorithm.HNSW)\n            .addAttribute(\"TYPE\", \"UINT8\").addAttribute(\"DIM\", 4)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\").build()));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testSvsVamanaVectorSimilarity() {\n      Map<String, Object> attr = new HashMap<>();\n      attr.put(\"TYPE\", \"FLOAT32\");\n      attr.put(\"DIM\", 2);\n      attr.put(\"DISTANCE_METRIC\", \"L2\");\n\n      assertOK(client.ftCreate(index, VectorField.builder().fieldName(\"v\")\n          .algorithm(VectorAlgorithm.SVS_VAMANA).attributes(attr).build()));\n\n      // Create proper float vectors\n      float[] vectorA = {1.0f, 2.0f};\n      float[] vectorB = {1.1f, 2.1f};\n      float[] vectorC = {2.0f, 3.0f};\n\n      // Convert to byte arrays using RediSearchUtil\n      byte[] bytesA = RediSearchUtil.toByteArray(vectorA);\n      byte[] bytesB = RediSearchUtil.toByteArray(vectorB);\n      byte[] bytesC = RediSearchUtil.toByteArray(vectorC);\n\n      client.hset(\"a\".getBytes(), \"v\".getBytes(), bytesA);\n      client.hset(\"b\".getBytes(), \"v\".getBytes(), bytesB);\n      client.hset(\"c\".getBytes(), \"v\".getBytes(), bytesC);\n\n      FTSearchParams searchParams = FTSearchParams.searchParams()\n          .addParam(\"vec\", bytesA)\n          .sortBy(\"__v_score\", SortingOrder.ASC)\n          .returnFields(\"__v_score\")\n          .dialect(2);\n      Document doc1 = client.ftSearch(index, \"*=>[KNN 2 @v $vec]\", searchParams).getDocuments().get(0);\n      assertEquals(\"a\", doc1.getId());\n      assertEquals(\"0\", doc1.get(\"__v_score\"));\n  }\n\n  @Test\n  @SinceRedisVersion(\"8.1.240\")\n  public void testSvsVamanaVectorWithAdvancedParameters() {\n    assertOK(client.ftCreate(index,\n        VectorField.builder().fieldName(\"v\")\n            .algorithm(VectorAlgorithm.SVS_VAMANA)\n            .addAttribute(\"TYPE\", \"FLOAT32\")\n            .addAttribute(\"DIM\", 4)\n            .addAttribute(\"DISTANCE_METRIC\", \"L2\")\n            .addAttribute(\"CONSTRUCTION_WINDOW_SIZE\", 200)\n            .addAttribute(\"GRAPH_MAX_DEGREE\", 64)\n            .addAttribute(\"SEARCH_WINDOW_SIZE\", 100)\n            .addAttribute(\"EPSILON\", 0.01)\n            .build()\n    ));\n  }\n\n  @Test\n  public void searchProfile() {\n    assertOK(client.ftCreate(index, TextField.of(\"t1\"), TextField.of(\"t2\")));\n\n    Map<String, String> hash = new HashMap<>();\n    hash.put(\"t1\", \"foo\");\n    hash.put(\"t2\", \"bar\");\n    client.hset(\"doc1\", hash);\n\n    Map.Entry<SearchResult, ProfilingInfo> reply = client.ftProfileSearch(index,\n        FTProfileParams.profileParams(), \"foo\", FTSearchParams.searchParams());\n\n    SearchResult result = reply.getKey();\n    assertEquals(1, result.getTotalResults());\n    assertEquals(Collections.singletonList(\"doc1\"), result.getDocuments().stream().map(Document::getId).collect(Collectors.toList()));\n\n    // profile\n    Object profileObject = reply.getValue().getProfilingInfo();\n    if (protocol != RedisProtocol.RESP3) {\n      assertThat(profileObject, Matchers.isA(List.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat((List<Object>) profileObject, Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    } else {\n      assertThat(profileObject, Matchers.isA(Map.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    }\n  }\n\n  @Test\n  public void vectorSearchProfile() {\n    assertOK(client.ftCreate(index, VectorField.builder().fieldName(\"v\")\n        .algorithm(VectorAlgorithm.FLAT).addAttribute(\"TYPE\", \"FLOAT32\")\n        .addAttribute(\"DIM\", 2).addAttribute(\"DISTANCE_METRIC\", \"L2\").build(),\n        TextField.of(\"t\")));\n\n    client.hset(\"1\", toMap(\"v\", \"bababaca\", \"t\", \"hello\"));\n    client.hset(\"2\", toMap(\"v\", \"babababa\", \"t\", \"hello\"));\n    client.hset(\"3\", toMap(\"v\", \"aabbaabb\", \"t\", \"hello\"));\n    client.hset(\"4\", toMap(\"v\", \"bbaabbaa\", \"t\", \"hello world\"));\n    client.hset(\"5\", toMap(\"v\", \"aaaabbbb\", \"t\", \"hello world\"));\n\n    FTSearchParams searchParams = FTSearchParams.searchParams().addParam(\"vec\", \"aaaaaaaa\")\n        .sortBy(\"__v_score\", SortingOrder.ASC).noContent().dialect(2);\n    Map.Entry<SearchResult, ProfilingInfo> reply = client.ftProfileSearch(index,\n        FTProfileParams.profileParams(), \"*=>[KNN 3 @v $vec]\", searchParams);\n    assertEquals(3, reply.getKey().getTotalResults());\n\n    assertEquals(Arrays.asList(4, 2, 1).toString(), reply.getKey().getDocuments()\n        .stream().map(Document::getId).collect(Collectors.toList()).toString());\n\n    // profile\n    Object profileObject = reply.getValue().getProfilingInfo();\n    if (protocol != RedisProtocol.RESP3) {\n      assertThat(profileObject, Matchers.isA(List.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat((List<Object>) profileObject, Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    } else {\n      assertThat(profileObject, Matchers.isA(Map.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    }\n  }\n\n  @Test\n  public void limitedSearchProfile() {\n    assertOK(client.ftCreate(index, TextField.of(\"t\")));\n    client.hset(\"1\", \"t\", \"hello\");\n    client.hset(\"2\", \"t\", \"hell\");\n    client.hset(\"3\", \"t\", \"help\");\n    client.hset(\"4\", \"t\", \"helowa\");\n\n    Map.Entry<SearchResult, ProfilingInfo> reply = client.ftProfileSearch(index,\n        FTProfileParams.profileParams().limited(), \"%hell% hel*\", FTSearchParams.searchParams().noContent());\n\n    // profile\n    Object profileObject = reply.getValue().getProfilingInfo();\n    if (protocol != RedisProtocol.RESP3) {\n      assertThat(profileObject, Matchers.isA(List.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat((List<Object>) profileObject, Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    } else {\n      assertThat(profileObject, Matchers.isA(Map.class));\n      if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {\n        assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems(\"Shards\", \"Coordinator\"));\n      }\n    }\n  }\n\n  @Test\n  public void list() {\n    assertEquals(Collections.emptySet(), client.ftList());\n\n    final int count = 20;\n    Set<String> names = new HashSet<>();\n    for (int i = 0; i < count; i++) {\n      final String name = \"idx\" + i;\n      assertOK(client.ftCreate(name, TextField.of(\"t\" + i)));\n      names.add(name);\n    }\n    assertEquals(names, client.ftList());\n  }\n\n  @Test\n  public void broadcast() {\n    String reply = client.ftCreate(index, TextField.of(\"t\"));\n    assertOK(reply);\n  }\n\n  @Test\n  public void searchIteration() {\n    assertOK(client.ftCreate(index, FTCreateParams.createParams(),\n        TextField.of(\"first\"), TextField.of(\"last\"), NumericField.of(\"age\")));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    FtSearchIteration search = client.ftSearchIteration(3, index, \"*\", FTSearchParams.searchParams());\n    int total = 0;\n    while (!search.isIterationCompleted()) {\n      SearchResult result = search.nextBatch();\n      int count = result.getDocuments().size();\n      assertThat(count, Matchers.lessThanOrEqualTo(3));\n      total += count;\n    }\n    assertEquals(7, total);\n  }\n\n  @Test\n  public void searchIterationCollect() {\n    assertOK(client.ftCreate(index, FTCreateParams.createParams(),\n        TextField.of(\"first\"), TextField.of(\"last\"), NumericField.of(\"age\")));\n\n    client.hset(\"profesor:5555\", toMap(\"first\", \"Albert\", \"last\", \"Blue\", \"age\", \"55\"));\n    client.hset(\"student:1111\", toMap(\"first\", \"Joe\", \"last\", \"Dod\", \"age\", \"18\"));\n    client.hset(\"pupil:2222\", toMap(\"first\", \"Jen\", \"last\", \"Rod\", \"age\", \"14\"));\n    client.hset(\"student:3333\", toMap(\"first\", \"El\", \"last\", \"Mark\", \"age\", \"17\"));\n    client.hset(\"pupil:4444\", toMap(\"first\", \"Pat\", \"last\", \"Shu\", \"age\", \"21\"));\n    client.hset(\"student:5555\", toMap(\"first\", \"Joen\", \"last\", \"Ko\", \"age\", \"20\"));\n    client.hset(\"teacher:6666\", toMap(\"first\", \"Pat\", \"last\", \"Rod\", \"age\", \"20\"));\n\n    ArrayList<Document> collect = new ArrayList<>();\n    client.ftSearchIteration(3, index, \"*\", FTSearchParams.searchParams()).collect(collect);\n    assertEquals(7, collect.size());\n    assertEquals(Arrays.asList(\"profesor:5555\", \"student:1111\", \"pupil:2222\", \"student:3333\",\n        \"pupil:4444\", \"student:5555\", \"teacher:6666\").stream().collect(Collectors.toSet()),\n        collect.stream().map(Document::getId).collect(Collectors.toSet()));\n  }\n\n  @Test\n  public void escapeUtil() {\n    assertOK(client.ftCreate(index, TextField.of(\"txt\")));\n\n    client.hset(\"doc1\", \"txt\", RediSearchUtil.escape(\"hello-world\"));\n    assertNotEquals(\"hello-world\", client.hget(\"doc1\", \"txt\"));\n    assertEquals(\"hello-world\", RediSearchUtil.unescape(client.hget(\"doc1\", \"txt\")));\n\n    SearchResult resultNoEscape = client.ftSearch(index, \"hello-world\");\n    assertEquals(0, resultNoEscape.getTotalResults());\n\n    SearchResult resultEscaped = client.ftSearch(index, RediSearchUtil.escapeQuery(\"hello-world\"));\n    assertEquals(1, resultEscaped.getTotalResults());\n  }\n\n  @Test\n  public void escapeMapUtil() {\n    client.hset(\"doc2\", RediSearchUtil.toStringMap(Collections.singletonMap(\"txt\", \"hello-world\"), true));\n    assertNotEquals(\"hello-world\", client.hget(\"doc2\", \"txt\"));\n    assertEquals(\"hello-world\", RediSearchUtil.unescape(client.hget(\"doc2\", \"txt\")));\n  }\n\n  @Test\n  public void hsetObject() {\n    float[] floats = new float[]{0.2f};\n    assertEquals(1L, client.hsetObject(\"obj\", \"floats\", floats));\n    assertArrayEquals(RediSearchUtil.toByteArray(floats),\n        client.hget(\"obj\".getBytes(), \"floats\".getBytes()));\n\n    GeoCoordinate geo = new GeoCoordinate(-0.441, 51.458);\n    Map<String, Object> fields = new HashMap<>();\n    fields.put(\"title\", \"hello world\");\n    fields.put(\"loc\", geo);\n    assertEquals(2L, client.hsetObject(\"obj\", fields));\n    Map<String, String> stringMap = client.hgetAll(\"obj\");\n    assertEquals(3, stringMap.size());\n    assertEquals(\"hello world\", stringMap.get(\"title\"));\n    assertEquals(geo.getLongitude() + \",\" + geo.getLatitude(), stringMap.get(\"loc\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/SpellCheckTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\n\nimport org.hamcrest.MatcherAssert;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.search.FTSpellCheckParams;\nimport redis.clients.jedis.search.schemafields.TextField;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class SpellCheckTest extends RedisModuleCommandsTestBase {\n\n  private static final String index = \"spellcheck\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public SpellCheckTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  private static Map<String, String> toMap(String... values) {\n    Map<String, String> map = new HashMap<>();\n    for (int i = 0; i < values.length; i += 2) {\n      map.put(values[i], values[i + 1]);\n    }\n    return map;\n  }\n\n  @Test\n  public void dictionary() {\n    assertEquals(3L, client.ftDictAdd(\"dict\", \"foo\", \"bar\", \"hello world\"));\n    assertEquals(new HashSet<>(Arrays.asList(\"foo\", \"bar\", \"hello world\")), client.ftDictDump(\"dict\"));\n    assertEquals(3L, client.ftDictDel(\"dict\", \"foo\", \"bar\", \"hello world\"));\n    assertEquals(Collections.emptySet(), client.ftDictDump(\"dict\"));\n  }\n\n  @Test\n  public void dictionaryBySampleKey() {\n    assertEquals(3L, client.ftDictAddBySampleKey(index, \"dict\", \"foo\", \"bar\", \"hello world\"));\n    assertEquals(new HashSet<>(Arrays.asList(\"foo\", \"bar\", \"hello world\")),\n        client.ftDictDumpBySampleKey(index, \"dict\"));\n    assertEquals(3L, client.ftDictDelBySampleKey(index, \"dict\", \"foo\", \"bar\", \"hello world\"));\n    assertEquals(Collections.emptySet(), client.ftDictDumpBySampleKey(index, \"dict\"));\n  }\n\n  @Test\n  public void basicSpellCheck() {\n    client.ftCreate(index, TextField.of(\"name\"), TextField.of(\"body\"));\n    client.hset(\"doc1\", toMap(\"name\", \"name1\", \"body\", \"body1\"));\n    client.hset(\"doc2\", toMap(\"name\", \"name2\", \"body\", \"body2\"));\n    client.hset(\"doc3\", toMap(\"name\", \"name2\", \"body\", \"name2\"));\n\n    Map<String, Map<String, Double>> reply = client.ftSpellCheck(index, \"name\");\n    assertEquals(Collections.singleton(\"name\"), reply.keySet());\n    assertEquals(new HashSet<>(Arrays.asList(\"name1\", \"name2\")), reply.get(\"name\").keySet());\n  }\n\n  @Test\n  public void crossTermDictionary() {\n    client.ftCreate(index, TextField.of(\"report\"));\n    client.ftDictAdd(\"slang\", \"timmies\", \"toque\", \"toonie\", \"serviette\", \"kerfuffle\", \"chesterfield\");\n\n    Map<String, Map<String, Double>> expected = Collections.singletonMap(\"tooni\",\n        Collections.singletonMap(\"toonie\", 0d));\n    assertEquals(expected, client.ftSpellCheck(index, \"Tooni toque kerfuffle\",\n        FTSpellCheckParams.spellCheckParams().includeTerm(\"slang\").excludeTerm(\"slang\")));\n  }\n\n  @Test\n  public void distanceBound() {\n    client.ftCreate(index, TextField.of(\"name\"), TextField.of(\"body\"));\n    assertThrows(JedisDataException.class, () -> client.ftSpellCheck(index, \"name\",\n        FTSpellCheckParams.spellCheckParams().distance(0)));\n  }\n\n  @Test\n  public void dialectBound() {\n    client.ftCreate(index, TextField.of(\"t\"));\n    JedisDataException error = assertThrows(JedisDataException.class,\n        () -> client.ftSpellCheck(index, \"Tooni toque kerfuffle\",\n            FTSpellCheckParams.spellCheckParams().dialect(0)));\n    MatcherAssert.assertThat(error.getMessage(),\n        Matchers.containsString(\"DIALECT requires a non negative integer\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/SuggestionTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static java.util.Collections.emptyList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.resps.Tuple;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class SuggestionTest extends RedisModuleCommandsTestBase {\n\n  private static final String key = \"suggest\";\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public SuggestionTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void addSuggestionAndGetSuggestion() {\n    String suggestion = \"ANOTHER_WORD\";\n    String noMatch = \"_WORD MISSED\";\n\n    assertTrue(client.ftSugAdd(key, suggestion, 1d) > 0,\n        suggestion + \" should of inserted at least 1\");\n    assertTrue(client.ftSugAdd(key, noMatch, 1d) > 0, noMatch + \" should of inserted at least 1\");\n\n    // test that with a partial part of that string will have the entire word returned\n    assertEquals(1, client.ftSugGet(key, suggestion.substring(0, 3), true, 5).size(),\n        suggestion + \" did not get a match with 3 characters\");\n\n    // turn off fuzzy start at second word no hit\n    assertEquals(0, client.ftSugGet(key, noMatch.substring(1, 6), false, 5).size(),\n        noMatch + \" no fuzzy and starting at 1, should not match\");\n\n    // my attempt to trigger the fuzzy by 1 character\n    assertEquals(1, client.ftSugGet(key, noMatch.substring(1, 6), true, 5).size(),\n        noMatch + \" fuzzy is on starting at 1 position should match\");\n  }\n\n  @Test\n  public void addSuggestionIncrAndGetSuggestionFuzzy() {\n    String suggestion = \"TOPIC OF WORDS\";\n\n    // test can add a suggestion string\n    assertTrue(client.ftSugAddIncr(key, suggestion, 1d) > 0,\n        suggestion + \" insert should of returned at least 1\");\n\n    // test that the partial part of that string will be returned using fuzzy\n    assertEquals(suggestion, client.ftSugGet(key, suggestion.substring(0, 3)).get(0));\n  }\n\n  @Test\n  public void getSuggestionScores() {\n    client.ftSugAdd(key, \"COUNT_ME TOO\", 1);\n    client.ftSugAdd(key, \"COUNT\", 1);\n    client.ftSugAdd(key, \"COUNT_ANOTHER\", 1);\n\n    String noScoreOrPayload = \"COUNT NO PAYLOAD OR COUNT\";\n    assertTrue(client.ftSugAddIncr(key, noScoreOrPayload, 1) > 1,\n        \"Count single added should return more than 1\");\n\n    List<Tuple> result = client.ftSugGetWithScores(key, \"COU\");\n    assertEquals(4, result.size());\n    result.forEach(tuple -> assertTrue(tuple.getScore() < .999,\n        \"Assert that a suggestion has a score not default 1 \"));\n  }\n\n  @Test\n  public void getSuggestionMax() {\n    client.ftSugAdd(key, \"COUNT_ME TOO\", 1);\n    client.ftSugAdd(key, \"COUNT\", 1);\n    client.ftSugAdd(key, \"COUNTNO PAYLOAD OR COUNT\", 1);\n\n    // test that with a partial part of that string will have the entire word returned\n    assertEquals( 3, client.ftSugGetWithScores(key, \"COU\", true, 10).size(),\"3 suggestions\");\n    assertEquals(2, client.ftSugGetWithScores(key, \"COU\", true, 2).size());\n  }\n\n  @Test\n  public void getSuggestionNoHit() {\n    client.ftSugAdd(key, \"NO WORD\", 0.4);\n\n    assertEquals(emptyList(), client.ftSugGetWithScores(key, \"DIF\"));\n    assertEquals(emptyList(), client.ftSugGet(key, \"DIF\"));\n  }\n\n  @Test\n  public void getSuggestionLengthAndDeleteSuggestion() {\n    client.ftSugAddIncr(key, \"TOPIC OF WORDS\", 1);\n    client.ftSugAddIncr(key, \"ANOTHER ENTRY\", 1);\n    assertEquals(2L, client.ftSugLen(key));\n\n    assertTrue(client.ftSugDel(key, \"ANOTHER ENTRY\"), \"Delete suggestion should succeed.\");\n    assertEquals(1L, client.ftSugLen(key));\n\n    assertFalse(client.ftSugDel(key, \"ANOTHER ENTRY\"), \"Delete suggestion should succeed.\");\n    assertEquals(1L, client.ftSugLen(key));\n\n    assertFalse(client.ftSugDel(key, \"ANOTHER ENTRY THAT IS NOT PRESENT\"),\n        \"Delete suggestion should succeed.\");\n    assertEquals(1L, client.ftSugLen(key));\n\n    client.ftSugAdd(key, \"LAST ENTRY\", 1);\n    assertEquals(2L, client.ftSugLen(key));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/search/UtilTest.java",
    "content": "package redis.clients.jedis.modules.search;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.search.RediSearchUtil;\n\nimport redis.clients.jedis.search.schemafields.NumericField;\nimport redis.clients.jedis.search.schemafields.SchemaField;\n\npublic class UtilTest {\n\n  @Test\n  public void floatArrayToByteArray() {\n    float[] floats = new float[] { 0.2f };\n    byte[] bytes = RediSearchUtil.toByteArray(floats);\n    byte[] expected = new byte[] { -51, -52, 76, 62 };\n    assertArrayEquals(expected, bytes);\n  }\n\n  @Test\n  public void getSchemaFieldName() {\n    SchemaField field = NumericField.of(\"$.num\").as(\"num\");\n\n    assertEquals(\"$.num\", field.getFieldName().getName());\n    assertEquals(\"num\", field.getFieldName().getAttribute());\n\n    assertEquals(\"$.num\", field.getName());\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java",
    "content": "package redis.clients.jedis.modules.timeseries;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static redis.clients.jedis.util.AssertUtil.assertEqualsByProtocol;\n\nimport java.util.*;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.annotations.SinceRedisVersion;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisProtocol;\nimport redis.clients.jedis.exceptions.JedisDataException;\nimport redis.clients.jedis.modules.RedisModuleCommandsTestBase;\nimport redis.clients.jedis.timeseries.*;\nimport redis.clients.jedis.util.KeyValue;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n@ParameterizedClass\n@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")\npublic class TimeSeriesTest extends RedisModuleCommandsTestBase {\n\n  @BeforeAll\n  public static void prepare() {\n    RedisModuleCommandsTestBase.prepare();\n  }\n\n  public TimeSeriesTest(RedisProtocol protocol) {\n    super(protocol);\n  }\n\n  @Test\n  public void testCreate() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n\n    assertEquals(\"OK\", client.tsCreate(\"series1\", TSCreateParams.createParams().retention(10).labels(labels)));\n    assertEquals(\"TSDB-TYPE\", client.type(\"series1\"));\n\n    assertEquals(\"OK\", client.tsCreate(\"series2\", TSCreateParams.createParams().labels(labels)));\n    assertEquals(\"TSDB-TYPE\", client.type(\"series2\"));\n\n    assertEquals(\"OK\", client.tsCreate(\"series3\", TSCreateParams.createParams().retention(10)));\n    assertEquals(\"TSDB-TYPE\", client.type(\"series3\"));\n\n    assertEquals(\"OK\", client.tsCreate(\"series4\"));\n    assertEquals(\"TSDB-TYPE\", client.type(\"series4\"));\n\n    assertEquals(\"OK\", client.tsCreate(\"series5\", TSCreateParams.createParams().retention(0).uncompressed().labels(labels)));\n    assertEquals(\"TSDB-TYPE\", client.type(\"series5\"));\n    assertEquals(\"OK\", client.tsCreate(\"series6\", TSCreateParams.createParams().retention(7898)\n        .uncompressed().duplicatePolicy(DuplicatePolicy.MAX).labels(labels)));\n    assertEquals(\"TSDB-TYPE\", client.type(\"series6\"));\n\n    try {\n      assertEquals(\"OK\", client.tsCreate(\"series1\", TSCreateParams.createParams().retention(10).labels(labels)));\n      fail();\n    } catch (JedisDataException e) {\n    }\n\n    try {\n      assertEquals(\"OK\", client.tsCreate(\"series1\", TSCreateParams.createParams().labels(labels)));\n      fail();\n    } catch (JedisDataException e) {\n    }\n\n    try {\n      assertEquals(\"OK\", client.tsCreate(\"series1\", TSCreateParams.createParams().retention(10)));\n      fail();\n    } catch (JedisDataException e) {\n    }\n\n    try {\n      assertEquals(\"OK\", client.tsCreate(\"series1\"));\n      fail();\n    } catch (JedisDataException e) {\n    }\n\n    try {\n      assertEquals(\"OK\", client.tsCreate(\"series1\"));\n      fail();\n    } catch (JedisDataException e) {\n    }\n\n    try {\n      assertEquals(\"OK\", client.tsCreate(\"series7\", TSCreateParams.createParams().retention(7898)\n          .uncompressed().chunkSize(-10).duplicatePolicy(DuplicatePolicy.MAX).labels(labels)));\n      fail();\n    } catch (JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void testAlter() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesAlter\", TSCreateParams.createParams().retention(60000).labels(labels)));\n    assertEquals(Collections.emptyList(), client.tsQueryIndex(\"l2=v22\"));\n\n    labels.put(\"l1\", \"v11\");\n    labels.remove(\"l2\");\n    labels.put(\"l3\", \"v33\");\n    assertEquals(\"OK\", client.tsAlter(\"seriesAlter\", TSAlterParams.alterParams().retention(15000).chunkSize(8192)\n        .duplicatePolicy(DuplicatePolicy.SUM).labels(labels)));\n\n    TSInfo info = client.tsInfo(\"seriesAlter\");\n    assertEquals(Long.valueOf(15000), info.getProperty(\"retentionTime\"));\n    assertEquals(Long.valueOf(8192), info.getProperty(\"chunkSize\"));\n    assertEquals(DuplicatePolicy.SUM, info.getProperty(\"duplicatePolicy\"));\n    assertEquals(\"v11\", info.getLabel(\"l1\"));\n    assertNull(info.getLabel(\"l2\"));\n    assertEquals(\"v33\", info.getLabel(\"l3\"));\n  }\n\n  @Test\n  public void createAndAlterParams() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n\n    assertEquals(\"OK\", client.tsCreate(\"ts-params\",\n        TSCreateParams.createParams().retention(60000).encoding(EncodingFormat.UNCOMPRESSED).chunkSize(4096)\n            .duplicatePolicy(DuplicatePolicy.BLOCK).ignore(50, 12.5).labels(labels)));\n\n    labels.put(\"l1\", \"v11\");\n    labels.remove(\"l2\");\n    labels.put(\"l3\", \"v33\");\n    assertEquals(\"OK\", client.tsAlter(\"ts-params\", TSAlterParams.alterParams().retention(15000).chunkSize(8192)\n        .duplicatePolicy(DuplicatePolicy.SUM).ignore(50, 12.5).labels(labels)));\n  }\n\n  @Test\n  public void testRule() {\n    assertEquals(\"OK\", client.tsCreate(\"source\"));\n    assertEquals(\"OK\", client.tsCreate(\"dest\", TSCreateParams.createParams().retention(10)));\n\n    assertEquals(\"OK\", client.tsCreateRule(\"source\", \"dest\", AggregationType.AVG, 100));\n\n    try {\n      client.tsCreateRule(\"source\", \"dest\", AggregationType.COUNT, 100);\n      fail();\n    } catch (JedisDataException e) {\n      // Error on creating same rule twice\n    }\n\n    assertEquals(\"OK\", client.tsDeleteRule(\"source\", \"dest\"));\n    assertEquals(\"OK\", client.tsCreateRule(\"source\", \"dest\", AggregationType.COUNT, 100));\n\n    try {\n      assertEquals(\"OK\", client.tsDeleteRule(\"source\", \"dest1\"));\n      fail();\n    } catch (JedisDataException e) {\n      // Error on creating same rule twice\n    }\n  }\n\n  @Test\n  public void addParams() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n\n    assertEquals(1000L, client.tsAdd(\"add1\", 1000L, 1.1,\n        TSAddParams.addParams().retention(10000).encoding(EncodingFormat.UNCOMPRESSED).chunkSize(1000)\n            .duplicatePolicy(DuplicatePolicy.FIRST).onDuplicate(DuplicatePolicy.LAST).ignore(50, 12.5).labels(labels)));\n\n    assertEquals(1000L, client.tsAdd(\"add2\", 1000L, 1.1,\n        TSAddParams.addParams().retention(10000).encoding(EncodingFormat.COMPRESSED).chunkSize(1000)\n            .duplicatePolicy(DuplicatePolicy.MIN).onDuplicate(DuplicatePolicy.MAX).ignore(50, 12.5).labels(labels)));\n  }\n\n  @Test\n  public void testAdd() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesAdd\", TSCreateParams.createParams().retention(10000).labels(labels)));\n    assertEquals(0, client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams()).size());\n\n    assertEquals(1000L, client.tsAdd(\"seriesAdd\", 1000L, 1.1, TSCreateParams.createParams().retention(10000).labels(null)));\n    assertEquals(2000L, client.tsAdd(\"seriesAdd\", 2000L, 0.9, TSCreateParams.createParams().labels(null)));\n    assertEquals(3200L, client.tsAdd(\"seriesAdd\", 3200L, 1.1, TSCreateParams.createParams().retention(10000)));\n    assertEquals(4500L, client.tsAdd(\"seriesAdd\", 4500L, -1.1));\n\n    TSElement[] rawValues = new TSElement[]{\n      new TSElement(1000L, 1.1),\n      new TSElement(2000L, 0.9),\n      new TSElement(3200L, 1.1),\n      new TSElement(4500L, -1.1)\n    };\n    List<TSElement> values = client.tsRange(\"seriesAdd\", 800L, 3000L);\n    assertEquals(2, values.size());\n    assertEquals(Arrays.asList(rawValues[0], rawValues[1]), values);\n    values = client.tsRange(\"seriesAdd\", 800L, 5000L);\n    assertEquals(4, values.size());\n    assertEquals(Arrays.asList(rawValues), values);\n    assertEquals(Arrays.asList(rawValues), client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams()));\n\n    List<TSElement> expectedCountValues = Arrays.asList(\n        new TSElement(2000L, 1), new TSElement(3200L, 1), new TSElement(4500L, 1));\n    values = client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams(1200L, 4600L).aggregation(AggregationType.COUNT, 1));\n    assertEquals(3, values.size());\n    assertEquals(expectedCountValues, values);\n\n    List<TSElement> expectedAvgValues = Arrays.asList(\n        new TSElement(0L, 1.1), new TSElement(2000L, 1), new TSElement(4000L, -1.1));\n    values = client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams(500L, 4600L).aggregation(AggregationType.AVG, 2000L));\n    assertEquals(3, values.size());\n    assertEquals(expectedAvgValues, values);\n\n    // ensure zero-based index\n    List<TSElement> valuesZeroBased = client.tsRange(\"seriesAdd\",\n        TSRangeParams.rangeParams(0L, 4600L).aggregation(AggregationType.AVG, 2000L));\n    assertEquals(3, valuesZeroBased.size());\n    assertEquals(values, valuesZeroBased);\n\n    List<TSElement> expectedOverallSumValues = Arrays.asList(new TSElement(0L, 2.0));\n    values = client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams(0L, 5000L).aggregation(AggregationType.SUM, 5000L));\n    assertEquals(1, values.size());\n    assertEquals(expectedOverallSumValues, values);\n\n    List<TSElement> expectedOverallMinValues = Arrays.asList(new TSElement(0L, -1.1));\n    values = client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams(0L, 5000L).aggregation(AggregationType.MIN, 5000L));\n    assertEquals(1, values.size());\n    assertEquals(expectedOverallMinValues, values);\n\n    List<TSElement> expectedOverallMaxValues = Arrays.asList(new TSElement(0L, 1.1));\n    values = client.tsRange(\"seriesAdd\", TSRangeParams.rangeParams(0L, 5000L).aggregation(AggregationType.MAX, 5000L));\n    assertEquals(1, values.size());\n    assertEquals(expectedOverallMaxValues, values);\n\n    // MRANGE\n    assertEquals(Collections.emptyMap(), client.tsMRange(TSMRangeParams.multiRangeParams().filter(\"l=v\")));\n    try {\n      client.tsMRange(TSMRangeParams.multiRangeParams(500L, 4600L).aggregation(AggregationType.COUNT, 1));\n      fail();\n//    } catch (JedisDataException e) {\n    } catch (IllegalArgumentException e) {\n    }\n\n    try {\n      client.tsMRange(TSMRangeParams.multiRangeParams(500L, 4600L).aggregation(AggregationType.COUNT, 1).filter((String) null));\n      fail();\n//    } catch (JedisDataException e) {\n    } catch (IllegalArgumentException e) {\n    }\n\n    Map<String, TSMRangeElements> ranges = client.tsMRange(TSMRangeParams.multiRangeParams(500L, 4600L)\n        .aggregation(AggregationType.COUNT, 1).filter(\"l1=v1\"));\n    assertEquals(1, ranges.size());\n\n    TSMRangeElements range = ranges.values().stream().findAny().get();\n    assertEquals(\"seriesAdd\", range.getKey());\n    assertEquals(Collections.emptyMap(), range.getLabels());\n\n    List<TSElement> rangeValues = range.getValue();\n    assertEquals(4, rangeValues.size());\n    assertEquals(new TSElement(1000, 1), rangeValues.get(0));\n    assertNotEquals(new TSElement(1000, 1.1), rangeValues.get(0));\n    assertEquals(2000L, rangeValues.get(1).getTimestamp());\n    assertEquals(\"(2000:1.0)\", rangeValues.get(1).toString());\n\n    // Add with labels\n    Map<String, String> labels2 = new HashMap<>();\n    labels2.put(\"l3\", \"v3\");\n    labels2.put(\"l4\", \"v4\");\n    assertEquals(1000L, client.tsAdd(\"seriesAdd2\", 1000L, 1.1, TSCreateParams.createParams().retention(10000).labels(labels2)));\n    Map<String, TSMRangeElements> ranges2 = client.tsMRange(TSMRangeParams.multiRangeParams(500L, 4600L)\n        .aggregation(AggregationType.COUNT, 1).withLabels().filter(\"l4=v4\"));\n    assertEquals(1, ranges2.size());\n    TSMRangeElements elements2 = ranges2.values().stream().findAny().get();\n    assertEquals(labels2, elements2.getLabels());\n    assertEqualsByProtocol(protocol, null, Arrays.asList(AggregationType.COUNT), elements2.getAggregators());\n\n    Map<String, String> labels3 = new HashMap<>();\n    labels3.put(\"l3\", \"v33\");\n    labels3.put(\"l4\", \"v4\");\n    assertEquals(1000L, client.tsAdd(\"seriesAdd3\", 1000L, 1.1, TSCreateParams.createParams().labels(labels3)));\n    assertEquals(2000L, client.tsAdd(\"seriesAdd3\", 2000L, 1.1, TSCreateParams.createParams().labels(labels3)));\n    assertEquals(3000L, client.tsAdd(\"seriesAdd3\", 3000L, 1.1, TSCreateParams.createParams().labels(labels3)));\n    Map<String, TSMRangeElements> ranges3 = client.tsMRange(TSMRangeParams.multiRangeParams(500L, 4600L)\n        .aggregation(AggregationType.AVG, 1L).withLabels(true).count(2).filter(\"l4=v4\"));\n    assertEquals(2, ranges3.size());\n    ArrayList<TSMRangeElements> ranges3List = new ArrayList<>(ranges3.values());\n    assertEquals(1, ranges3List.get(0).getValue().size());\n    assertEquals(labels2, ranges3List.get(0).getLabels());\n    assertEqualsByProtocol(protocol, null, Arrays.asList(AggregationType.AVG), ranges3List.get(0).getAggregators());\n    assertEquals(2, ranges3List.get(1).getValue().size());\n    assertEquals(labels3, ranges3List.get(1).getLabels());\n    assertEqualsByProtocol(protocol, null, Arrays.asList(AggregationType.AVG), ranges3List.get(1).getAggregators());\n\n    assertEquals(800L, client.tsAdd(\"seriesAdd\", 800L, 1.1));\n    assertEquals(700L, client.tsAdd(\"seriesAdd\", 700L, 1.1, TSCreateParams.createParams().retention(10000)));\n    assertEquals(600L, client.tsAdd(\"seriesAdd\", 600L, 1.1, TSCreateParams.createParams().retention(10000).labels(null)));\n\n    assertEquals(400L, client.tsAdd(\"seriesAdd4\", 400L, 0.4, TSCreateParams.createParams()\n        .retention(7898L).uncompressed().chunkSize(1000L).duplicatePolicy(DuplicatePolicy.SUM)\n        .labels(labels)));\n    assertEquals(\"TSDB-TYPE\", client.type(\"seriesAdd4\"));\n    assertEquals(400L, client.tsAdd(\"seriesAdd4\", 400L, 0.3, TSCreateParams.createParams()\n        .retention(7898L).uncompressed().chunkSize(1000L).duplicatePolicy(DuplicatePolicy.SUM)\n        .labels(labels)));\n    assertEquals(Arrays.asList(new TSElement(400L, 0.7)), client.tsRange(\"seriesAdd4\", 0L, Long.MAX_VALUE));\n\n    // Range on none existing key\n    try {\n      client.tsRange(\"seriesAdd1\", TSRangeParams.rangeParams(500L, 4000L).aggregation(AggregationType.COUNT, 1));\n      fail();\n    } catch (JedisDataException e) {\n    }\n  }\n\n  @Test\n  public void issue75() {\n    client.tsMRange(TSMRangeParams.multiRangeParams().filter(\"id=1\"));\n  }\n\n  @Test\n  public void del() {\n    try {\n      client.tsDel(\"ts-del\", 0, 1);\n      fail();\n    } catch (JedisDataException jde) {\n      // expected\n    }\n\n    assertEquals(\"OK\", client.tsCreate(\"ts-del\", TSCreateParams.createParams().retention(10000L)));\n    assertEquals(0, client.tsDel(\"ts-del\", 0, 1));\n\n    assertEquals(1000L, client.tsAdd(\"ts-del\", 1000L, 1.1, TSCreateParams.createParams().retention(10000)));\n    assertEquals(2000L, client.tsAdd(\"ts-del\", 2000L, 0.9));\n    assertEquals(3200L, client.tsAdd(\"ts-del\", 3200L, 1.1, TSCreateParams.createParams().retention(10000)));\n    assertEquals(4500L, client.tsAdd(\"ts-del\", 4500L, -1.1));\n    assertEquals(4, client.tsRange(\"ts-del\", 0, 5000).size());\n\n    assertEquals(2, client.tsDel(\"ts-del\", 2000, 4000));\n    assertEquals(2, client.tsRange(\"ts-del\", 0, 5000).size());\n    assertEquals(1, client.tsRange(\"ts-del\", 0, 2500).size());\n    assertEquals(1, client.tsRange(\"ts-del\", 2500, 5000).size());\n  }\n\n  @Test\n  public void testValue() {\n    TSElement v = new TSElement(1234, 234.89634);\n    assertEquals(1234, v.getTimestamp());\n    assertEquals(234.89634, v.getValue(), 0);\n\n    assertEquals(v, new TSElement(1234, 234.89634));\n    assertNotEquals(v, new TSElement(1334, 234.89634));\n    assertNotEquals(v, new TSElement(1234, 234.8934));\n    assertNotEquals(1234, v.getValue());\n\n    assertEquals(\"(1234:234.89634)\", v.toString());\n//    assertEquals(-1856758940, v.hashCode());\n    assertEquals(-1856719580, v.hashCode());\n  }\n\n  @Test\n  public void testAddStar() throws InterruptedException {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l11\", \"v11\");\n    labels.put(\"l22\", \"v22\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesAdd2\", TSCreateParams.createParams().retention(10000L).labels(labels)));\n\n    // Use 50ms for cases when Redis is not running locally\n    int delayInMillis = 50;\n    long startTime = System.currentTimeMillis();\n    Thread.sleep(delayInMillis);\n    long add1 = client.tsAdd(\"seriesAdd2\", 1.1);\n    assertTrue(add1 > startTime);\n    Thread.sleep(delayInMillis);\n    long add2 = client.tsAdd(\"seriesAdd2\", 3.2);\n    assertTrue(add2 > add1);\n    Thread.sleep(delayInMillis);\n    long add3 = client.tsAdd(\"seriesAdd2\", 3.2);\n    assertTrue(add3 > add2);\n    Thread.sleep(delayInMillis);\n    long add4 = client.tsAdd(\"seriesAdd2\", -1.2);\n    assertTrue(add4 > add3);\n    Thread.sleep(delayInMillis);\n    long endTime = System.currentTimeMillis();\n    assertTrue(endTime > add4);\n\n    List<TSElement> values = client.tsRange(\"seriesAdd2\", startTime, add3);\n    assertEquals(3, values.size());\n  }\n\n  @Test\n  @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n  public void testMadd() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesAdd1\", TSCreateParams.createParams().retention(10000L).labels(labels)));\n    assertEquals(\"OK\", client.tsCreate(\"seriesAdd2\", TSCreateParams.createParams().retention(10000L).labels(labels)));\n\n    List<Long> result = client.tsMAdd(\n        new KeyValue<>(\"seriesAdd1\", new TSElement(1000L, 1.1)),\n        new KeyValue<>(\"seriesAdd2\", new TSElement(2000L, 3.2)),\n        new KeyValue<>(\"seriesAdd1\", new TSElement(1500L, 2.67)),\n        new KeyValue<>(\"seriesAdd2\", new TSElement(3200L, 54.2)),\n        new KeyValue<>(\"seriesAdd2\", new TSElement(4300L, 21.2)));\n\n    assertEquals(1000L, result.get(0).longValue());\n    assertEquals(2000L, result.get(1).longValue());\n    assertEquals(1500L, result.get(2).longValue());\n    assertEquals(3200L, result.get(3).longValue());\n    assertEquals(4300L, result.get(4).longValue());\n\n    List<TSElement> values1 = client.tsRange(\"seriesAdd1\", 0, Long.MAX_VALUE);\n    assertEquals(2, values1.size());\n    assertEquals(1.1, values1.get(0).getValue(), 0.001);\n    assertEquals(2.67, values1.get(1).getValue(), 0.001);\n\n    List<TSElement> values2 = client.tsRange(\"seriesAdd2\", TSRangeParams.rangeParams(0, Long.MAX_VALUE).count(2));\n    assertEquals(2, values2.size());\n    assertEquals(3.2, values2.get(0).getValue(), 0.001);\n    assertEquals(54.2, values2.get(1).getValue(), 0.001);\n  }\n\n  @Test\n  public void testIncrByDecrBy() throws InterruptedException {\n    assertEquals(\"OK\", client.tsCreate(\"seriesIncDec\",\n        TSCreateParams.createParams().retention(100 * 1000 /*100 sec*/)));\n\n    assertEquals(1L, client.tsAdd(\"seriesIncDec\", 1L, 1), 0);\n    assertEquals(2L, client.tsIncrBy(\"seriesIncDec\", 3, 2L), 0);\n    assertEquals(3L, client.tsDecrBy(\"seriesIncDec\", 2, 3L), 0);\n    List<TSElement> values = client.tsRange(\"seriesIncDec\", 1L, 3L);\n    assertEquals(3, values.size());\n    assertEquals(2, values.get(2).getValue(), 0);\n\n    assertEquals(3L, client.tsDecrBy(\"seriesIncDec\", 2, 3L), 0);\n    values = client.tsRange(\"seriesIncDec\", 1L, Long.MAX_VALUE);\n    assertEquals(3, values.size());\n\n    client.tsIncrBy(\"seriesIncDec\", 100);\n    client.tsDecrBy(\"seriesIncDec\", 33);\n  }\n\n  @Test\n  public void incrByDecrByParams() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n\n    assertEquals(1000L, client.tsIncrBy(\"incr1\", 1.1,\n        TSIncrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)\n            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.FIRST).ignore(50, 12.5).labels(labels)));\n\n    assertEquals(1000L, client.tsIncrBy(\"incr2\", 1.1,\n        TSIncrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)\n            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.MIN).ignore(50, 12.5).labels(labels)));\n\n    assertEquals(1000L, client.tsDecrBy(\"decr1\", 1.1,\n        TSDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)\n            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.LAST).ignore(50, 12.5).labels(labels)));\n\n    assertEquals(1000L, client.tsDecrBy(\"decr2\", 1.1,\n        TSDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)\n            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.MAX).ignore(50, 12.5).labels(labels)));\n  }\n\n  @Test\n  public void align() {\n    client.tsAdd(\"align\", 1, 10d);\n    client.tsAdd(\"align\", 3, 5d);\n    client.tsAdd(\"align\", 11, 10d);\n    client.tsAdd(\"align\", 25, 11d);\n\n    List<TSElement> values = client.tsRange(\"align\", TSRangeParams.rangeParams(1L, 30L).aggregation(AggregationType.COUNT, 10));\n    assertEquals(Arrays.asList(new TSElement(1, 2), new TSElement(11, 1), new TSElement(21, 1)), values);\n\n    values = client.tsRange(\"align\", TSRangeParams.rangeParams(1L, 30L).alignStart().aggregation(AggregationType.COUNT, 10));\n    assertEquals(Arrays.asList(new TSElement(1, 2), new TSElement(11, 1), new TSElement(21, 1)), values);\n\n    values = client.tsRange(\"align\", TSRangeParams.rangeParams(1L, 30L).alignEnd().aggregation(AggregationType.COUNT, 10));\n    assertEquals(Arrays.asList(new TSElement(1, 2), new TSElement(11, 1), new TSElement(21, 1)), values);\n\n    values =\n        client.tsRange(\"align\", TSRangeParams.rangeParams(1L, 30L).align(5).aggregation(AggregationType.COUNT, 10));\n    assertEquals(Arrays.asList(new TSElement(1, 2), new TSElement(11, 1), new TSElement(21, 1)), values);\n  }\n\n  @Test\n  public void rangeFilterBy() {\n\n    TSElement[] rawValues =\n        new TSElement[] {\n          new TSElement(1000L, 1.0),\n          new TSElement(2000L, 0.9),\n          new TSElement(3200L, 1.1),\n          new TSElement(4500L, -1.1)\n        };\n\n    for (TSElement value : rawValues) {\n      client.tsAdd(\"filterBy\", value.getTimestamp(), value.getValue());\n    }\n\n    // RANGE\n    List<TSElement> values = client.tsRange(\"filterBy\", 0L, 5000L);\n    assertEquals(Arrays.asList(rawValues), values);\n\n    values = client.tsRange(\"filterBy\", TSRangeParams.rangeParams(0L, 5000L).filterByTS(1000L, 2000L));\n    assertEquals(Arrays.asList(rawValues[0], rawValues[1]), values);\n\n    values = client.tsRange(\"filterBy\", TSRangeParams.rangeParams(0L, 5000L).filterByValues(1.0, 1.2));\n    assertEquals(Arrays.asList(rawValues[0], rawValues[2]), values);\n\n    values = client.tsRange(\"filterBy\", TSRangeParams.rangeParams(0L, 5000L).filterByTS(1000L, 2000L).filterByValues(1.0, 1.2));\n    assertEquals(Arrays.asList(rawValues[0]), values);\n\n    // REVRANGE\n    values = client.tsRevRange(\"filterBy\", 0L, 5000L);\n    assertEquals(Arrays.asList(rawValues[3], rawValues[2], rawValues[1], rawValues[0]), values);\n\n    values =\n        client.tsRevRange(\"filterBy\", TSRangeParams.rangeParams(0L, 5000L).filterByTS(1000L, 2000L));\n    assertEquals(Arrays.asList(rawValues[1], rawValues[0]), values);\n\n    values =\n        client.tsRevRange(\"filterBy\", TSRangeParams.rangeParams(0L, 5000L).filterByValues(1.0, 1.2));\n    assertEquals(Arrays.asList(rawValues[2], rawValues[0]), values);\n\n    values =\n        client.tsRevRange(\"filterBy\", TSRangeParams.rangeParams(0L, 5000L).filterByTS(1000L, 2000L).filterByValues(1.0, 1.2));\n    assertEquals(Arrays.asList(rawValues[0]), values);\n  }\n\n  @Test\n  public void mrangeFilterBy() {\n\n    Map<String, String> labels = Collections.singletonMap(\"label\", \"multi\");\n    client.tsCreate(\"ts1\", TSCreateParams.createParams().labels(labels));\n    client.tsCreate(\"ts2\", TSCreateParams.createParams().labels(labels));\n    String filter = \"label=multi\";\n\n    TSElement[] rawValues = new TSElement[]{\n      new TSElement(1000L, 1.0),\n      new TSElement(2000L, 0.9),\n      new TSElement(3200L, 1.1),\n      new TSElement(4500L, -1.1)\n    };\n\n    client.tsAdd(\"ts1\", rawValues[0].getTimestamp(), rawValues[0].getValue());\n    client.tsAdd(\"ts2\", rawValues[1].getTimestamp(), rawValues[1].getValue());\n    client.tsAdd(\"ts2\", rawValues[2].getTimestamp(), rawValues[2].getValue());\n    client.tsAdd(\"ts1\", rawValues[3].getTimestamp(), rawValues[3].getValue());\n\n    // MRANGE\n    Map<String, TSMRangeElements> range = client.tsMRange(0L, 5000L, filter);\n    ArrayList<TSMRangeElements> rangeList = new ArrayList<>(range.values());\n    assertEquals(\"ts1\", rangeList.get(0).getKey());\n    assertEquals(Arrays.asList(rawValues[0], rawValues[3]), rangeList.get(0).getValue());\n    assertEquals(\"ts2\", rangeList.get(1).getKey());\n    assertEquals(Arrays.asList(rawValues[1], rawValues[2]), rangeList.get(1).getValue());\n\n    range = client.tsMRange(TSMRangeParams.multiRangeParams(0L, 5000L).filterByTS(1000L, 2000L).filter(filter));\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(\"ts1\", rangeList.get(0).getKey());\n    assertEquals(Arrays.asList(rawValues[0]), rangeList.get(0).getValue());\n    assertEquals(\"ts2\", rangeList.get(1).getKey());\n    assertEquals(Arrays.asList(rawValues[1]), rangeList.get(1).getValue());\n\n    range = client.tsMRange(TSMRangeParams.multiRangeParams(0L, 5000L).filterByValues(1.0, 1.2).filter(filter));\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(\"ts1\", rangeList.get(0).getKey());\n    assertEquals(Arrays.asList(rawValues[0]), rangeList.get(0).getValue());\n    assertEquals(\"ts2\", rangeList.get(1).getKey());\n    assertEquals(Arrays.asList(rawValues[2]), rangeList.get(1).getValue());\n\n    range = client.tsMRange(TSMRangeParams.multiRangeParams(0L, 5000L)\n        .filterByTS(1000L, 2000L).filterByValues(1.0, 1.2).filter(filter));\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(Arrays.asList(rawValues[0]), rangeList.get(0).getValue());\n\n    // MREVRANGE\n    range = client.tsMRevRange(0L, 5000L,  filter);\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(\"ts1\", rangeList.get(0).getKey());\n    assertEquals(Arrays.asList(rawValues[3], rawValues[0]), rangeList.get(0).getValue());\n    assertEquals(\"ts2\", rangeList.get(1).getKey());\n    assertEquals(Arrays.asList(rawValues[2], rawValues[1]), rangeList.get(1).getValue());\n\n    range = client.tsMRevRange(TSMRangeParams.multiRangeParams(0L, 5000L).filterByTS(1000L, 2000L).filter(filter));\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(\"ts1\", rangeList.get(0).getKey());\n    assertEquals(Arrays.asList(rawValues[0]), rangeList.get(0).getValue());\n    assertEquals(\"ts2\", rangeList.get(1).getKey());\n    assertEquals(Arrays.asList(rawValues[1]), rangeList.get(1).getValue());\n\n    range = client.tsMRevRange(TSMRangeParams.multiRangeParams(0L, 5000L).filterByValues(1.0, 1.2).filter(filter));\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(\"ts1\", rangeList.get(0).getKey());\n    assertEquals(Arrays.asList(rawValues[0]), rangeList.get(0).getValue());\n    assertEquals(\"ts2\", rangeList.get(1).getKey());\n    assertEquals(Arrays.asList(rawValues[2]), rangeList.get(1).getValue());\n\n    range = client.tsMRevRange(TSMRangeParams.multiRangeParams(0L, 5000L)\n        .filterByTS(1000L, 2000L).filterByValues(1.0, 1.2).filter(filter));\n    rangeList = new ArrayList<>(range.values());\n    assertEquals(Arrays.asList(rawValues[0]), rangeList.get(0).getValue());\n  }\n\n  @Test\n  public void groupByReduce() {\n    client.tsCreate(\"ts1\", TSCreateParams.createParams().labels(convertMap(\"metric\", \"cpu\", \"metric_name\", \"system\")));\n    client.tsCreate(\"ts2\", TSCreateParams.createParams().labels(convertMap(\"metric\", \"cpu\", \"metric_name\", \"user\")));\n\n    client.tsAdd(\"ts1\", 1L, 90.0);\n    client.tsAdd(\"ts1\", 2L, 45.0);\n    client.tsAdd(\"ts2\", 2L, 99.0);\n\n//    List<TSElements> range = client.tsMRange(TSMRangeParams.multiGetParams(0L, 100L).withLabels()\n//        .groupByReduce(\"metric_name\", \"max\"), \"metric=cpu\");\n    Map<String, TSMRangeElements> range = client.tsMRange(TSMRangeParams.multiRangeParams(0L, 100L).withLabels()\n        .filter(\"metric=cpu\").groupBy(\"metric_name\", \"max\"));\n    assertEquals(2, range.size());\n    ArrayList<TSMRangeElements> rangeList = new ArrayList<>(range.values());\n\n    assertEquals(\"metric_name=system\", rangeList.get(0).getKey());\n    assertEquals(\"system\", rangeList.get(0).getLabels().get(\"metric_name\"));\n    if (protocol != RedisProtocol.RESP3) {\n      assertEquals(\"max\", rangeList.get(0).getLabels().get(\"__reducer__\"));\n      assertEquals(\"ts1\", rangeList.get(0).getLabels().get(\"__source__\"));\n    } else {\n      assertEquals(Arrays.asList(\"max\"), rangeList.get(0).getReducers());\n      assertEquals(Arrays.asList(\"ts1\"), rangeList.get(0).getSources());\n    }\n    assertEquals(Arrays.asList(new TSElement(1, 90), new TSElement(2, 45)), rangeList.get(0).getValue());\n\n    assertEquals(\"metric_name=user\", rangeList.get(1).getKey());\n    assertEquals(\"user\", rangeList.get(1).getLabels().get(\"metric_name\"));\n    if (protocol != RedisProtocol.RESP3) {\n      assertEquals(\"max\", rangeList.get(1).getLabels().get(\"__reducer__\"));\n      assertEquals(\"ts2\", rangeList.get(1).getLabels().get(\"__source__\"));\n    } else {\n      assertEquals(Arrays.asList(\"max\"), rangeList.get(1).getReducers());\n      assertEquals(Arrays.asList(\"ts2\"), rangeList.get(1).getSources());\n    }\n    assertEquals(Arrays.asList(new TSElement(2, 99)), rangeList.get(1).getValue());\n  }\n\n  private Map<String, String> convertMap(String... array) {\n    Map<String, String> map = new HashMap<>(array.length / 2);\n    for (int i = 0; i < array.length; i += 2) {\n      map.put(array[i], array[i + 1]);\n    }\n    return map;\n  }\n\n  @Test\n  public void testGet() {\n\n    // Test for empty result none existing series\n    try {\n      client.tsGet(\"seriesGet\");\n      fail();\n    } catch (JedisDataException e) {\n    }\n\n    assertEquals(\"OK\", client.tsCreate(\"seriesGet\", TSCreateParams.createParams()\n        .retention(100 * 1000 /*100sec retentionTime*/)));\n\n    // Test for empty result\n    assertNull(client.tsGet(\"seriesGet\"));\n\n    // Test returned last Value\n    client.tsAdd(\"seriesGet\", 2558, 8.7);\n    assertEquals(new TSElement(2558, 8.7), client.tsGet(\"seriesGet\"));\n\n    client.tsAdd(\"seriesGet\", 3458, 1.117);\n    assertEquals(new TSElement(3458, 1.117), client.tsGet(\"seriesGet\"));\n  }\n\n  @Test\n  public void testMGet() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesMGet1\", TSCreateParams.createParams()\n        .retention(100 * 1000 /*100sec retentionTime*/).labels(labels)));\n    assertEquals(\"OK\", client.tsCreate(\"seriesMGet2\", TSCreateParams.createParams()\n        .retention(100 * 1000 /*100sec retentionTime*/).labels(labels)));\n\n    // Test for empty result\n    Map<String, TSMGetElement> ranges1 = client.tsMGet(TSMGetParams.multiGetParams().withLabels(false), \"l1=v2\");\n    assertEquals(0, ranges1.size());\n\n    // Test for empty ranges\n    Map<String, TSMGetElement> ranges2 = client.tsMGet(TSMGetParams.multiGetParams().withLabels(true), \"l1=v1\");\n    assertEquals(2, ranges2.size());\n    ArrayList<TSMGetElement> ranges2List = new ArrayList<>(ranges2.values());\n    assertEquals(labels, ranges2List.get(0).getLabels());\n    assertEquals(labels, ranges2List.get(1).getLabels());\n    assertNull(ranges2List.get(0).getValue());\n\n    // Test for returned result on MGet\n    client.tsAdd(\"seriesMGet1\", 1500, 1.3);\n    Map<String, TSMGetElement> ranges3 = client.tsMGet(TSMGetParams.multiGetParams().withLabels(false), \"l1=v1\");\n    assertEquals(2, ranges3.size());\n    ArrayList<TSMGetElement> ranges3List = new ArrayList<>(ranges3.values());\n    assertEquals(Collections.emptyMap(), ranges3List.get(0).getLabels());\n    assertEquals(Collections.emptyMap(), ranges3List.get(1).getLabels());\n    assertEquals(new TSElement(1500, 1.3), ranges3List.get(0).getValue());\n    assertNull(ranges3List.get(1).getValue());\n  }\n\n  @Test\n  public void testQueryIndex() {\n\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesQueryIndex1\", TSCreateParams.createParams()\n        .retention(100 * 1000 /*100sec retentionTime*/).labels(labels)));\n\n    labels.put(\"l2\", \"v22\");\n    labels.put(\"l3\", \"v33\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesQueryIndex2\", TSCreateParams.createParams()\n        .retention(100 * 1000 /*100sec retentionTime*/).labels(labels)));\n\n    assertEquals(Arrays.<String>asList(), client.tsQueryIndex(\"l1=v2\"));\n    assertEquals(Arrays.asList(\"seriesQueryIndex1\", \"seriesQueryIndex2\"), client.tsQueryIndex(\"l1=v1\"));\n    assertEquals(Arrays.asList(\"seriesQueryIndex2\"), client.tsQueryIndex(\"l2=v22\"));\n  }\n\n  @Test\n  public void testInfo() {\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"source\", TSCreateParams.createParams().retention(10000L).labels(labels)));\n    assertEquals(\"OK\", client.tsCreate(\"dest\", TSCreateParams.createParams().retention(20000L)));\n    assertEquals(\"OK\", client.tsCreateRule(\"source\", \"dest\", AggregationType.AVG, 100));\n\n    TSInfo info = client.tsInfo(\"source\");\n    assertEquals((Long) 10000L, info.getProperty(\"retentionTime\"));\n    assertEquals((Long) 4096L, info.getProperty(\"chunkSize\"));\n    assertEquals(\"v1\", info.getLabel(\"l1\"));\n    assertEquals(\"v2\", info.getLabel(\"l2\"));\n    assertNull(info.getLabel(\"l3\"));\n\n    assertEquals(1, info.getRules().size());\n    TSInfo.Rule rule = info.getRule(\"dest\");\n    assertEquals(\"dest\", rule.getCompactionKey());\n    assertEquals(100L, rule.getBucketDuration());\n    assertEquals(AggregationType.AVG, rule.getAggregator());\n\n    try {\n      client.tsInfo(\"none\");\n      fail();\n    } catch (JedisDataException e) {\n      // Error on info on none existing series\n    }\n  }\n\n  @Test\n  public void testInfoDebug() {\n    assertEquals(\"OK\", client.tsCreate(\"source\", TSCreateParams.createParams()));\n\n    TSInfo info = client.tsInfoDebug(\"source\");\n    assertEquals((Long) 0L, info.getProperty(\"retentionTime\"));\n    assertEquals(0, info.getLabels().size());\n    assertEquals(0, info.getRules().size());\n\n    List<Map<String, Object>> chunks = info.getChunks();\n    assertEquals(1, chunks.size());\n    Map<String, Object> chunk = chunks.get(0);\n    assertEquals(0L, chunk.get(\"samples\"));\n    // Don't care what the values are as long as the values are parsed according to types\n    assertTrue(chunk.get(\"size\") instanceof Long);\n    assertTrue(chunk.get(\"startTimestamp\") instanceof Long);\n    assertTrue(chunk.get(\"endTimestamp\") instanceof Long);\n    assertTrue(chunk.get(\"bytesPerSample\") instanceof Double);\n\n    try {\n      client.tsInfoDebug(\"none\");\n      fail();\n    } catch (JedisDataException e) {\n      // Error on info on none existing series\n    }\n  }\n\n  @Test\n  public void testRevRange() {\n\n    Map<String, String> labels = new HashMap<>();\n    labels.put(\"l1\", \"v1\");\n    labels.put(\"l2\", \"v2\");\n    assertEquals(\"OK\", client.tsCreate(\"seriesAdd\", TSCreateParams.createParams().retention(10000L).labels(labels)));\n    assertEquals(Collections.emptyList(), client.tsRevRange(\"seriesAdd\", TSRangeParams.rangeParams()));\n\n    assertEquals(1000L, client.tsAdd(\"seriesRevRange\", 1000L, 1.1, TSCreateParams.createParams().retention(10000)));\n    assertEquals(2000L, client.tsAdd(\"seriesRevRange\", 2000L, 0.9, TSCreateParams.createParams().labels(null)));\n    assertEquals(3200L, client.tsAdd(\"seriesRevRange\", 3200L, 1.1, TSCreateParams.createParams().retention(10000)));\n    assertEquals(4500L, client.tsAdd(\"seriesRevRange\", 4500L, -1.1));\n\n    TSElement[] rawValues = new TSElement[]{\n      new TSElement(4500L, -1.1),\n      new TSElement(3200L, 1.1),\n      new TSElement(2000L, 0.9),\n      new TSElement(1000L, 1.1)\n    };\n    List<TSElement> values = client.tsRevRange(\"seriesRevRange\", 800L, 3000L);\n    assertEquals(2, values.size());\n    assertEquals(Arrays.asList(Arrays.copyOfRange(rawValues, 2, 4)), values);\n    values = client.tsRevRange(\"seriesRevRange\", 800L, 5000L);\n    assertEquals(4, values.size());\n    assertEquals(Arrays.asList(rawValues), values);\n    assertEquals(Arrays.asList(rawValues), client.tsRevRange(\"seriesRevRange\", TSRangeParams.rangeParams()));\n\n    List<TSElement> expectedCountValues = Arrays.asList(\n        new TSElement(4500L, 1), new TSElement(3200L, 1), new TSElement(2000L, 1));\n    values = client.tsRevRange(\"seriesRevRange\", TSRangeParams.rangeParams(1200L, 4600L)\n        .aggregation(AggregationType.COUNT, 1));\n    assertEquals(3, values.size());\n    assertEquals(expectedCountValues, values);\n\n    List<TSElement> expectedAvgValues = Arrays.asList(\n        new TSElement(4000L, -1.1), new TSElement(2000L, 1), new TSElement(0L, 1.1));\n    values = client.tsRevRange(\"seriesRevRange\", TSRangeParams.rangeParams(500L, 4600L)\n        .aggregation(AggregationType.AVG, 2000L));\n    assertEquals(3, values.size());\n    assertEquals(expectedAvgValues, values);\n  }\n\n  @Test\n  public void testMRevRange() {\n\n    assertEquals(Collections.emptyMap(), client.tsMRevRange(TSMRangeParams.multiRangeParams().filter(\"l=v\")));\n\n    Map<String, String> labels1 = new HashMap<>();\n    labels1.put(\"l3\", \"v3\");\n    labels1.put(\"l4\", \"v4\");\n    assertEquals(1000L, client.tsAdd(\"seriesMRevRange1\", 1000L, 1.1,\n        TSCreateParams.createParams().retention(10000).labels(labels1)));\n    assertEquals(2222L, client.tsAdd(\"seriesMRevRange1\", 2222L, 3.1,\n        TSCreateParams.createParams().retention(10000).labels(labels1)));\n    Map<String, TSMRangeElements> ranges1 = client.tsMRevRange(TSMRangeParams.multiRangeParams(500L, 4600L)\n        .aggregation(AggregationType.COUNT, 1).withLabels().filter(\"l4=v4\"));\n    assertEquals(1, ranges1.size());\n    ArrayList<TSMRangeElements> ranges1List = new ArrayList<>(ranges1.values());\n    assertEquals(labels1, ranges1List.get(0).getLabels());\n    assertEquals(Arrays.asList(new TSElement(2222L, 1.0), new TSElement(1000L, 1.0)), ranges1List.get(0).getValue());\n\n    Map<String, String> labels2 = new HashMap<>();\n    labels2.put(\"l3\", \"v3\");\n    labels2.put(\"l4\", \"v44\");\n    assertEquals(1000L, client.tsAdd(\"seriesMRevRange2\", 1000L, 8.88,\n        TSCreateParams.createParams().retention(10000).labels(labels2)));\n    assertEquals(1111L, client.tsAdd(\"seriesMRevRange2\", 1111L, 99.99,\n        TSCreateParams.createParams().retention(10000).labels(labels2)));\n    Map<String, TSMRangeElements> ranges2 = client.tsMRevRange(500L, 4600L, \"l3=v3\");\n    assertEquals(2, ranges2.size());\n    ArrayList<TSMRangeElements> ranges2List = new ArrayList<>(ranges2.values());\n    assertEquals(Collections.emptyMap(), ranges2List.get(0).getLabels());\n    assertEquals(Arrays.asList(new TSElement(2222L, 3.1), new TSElement(1000L, 1.1)), ranges2List.get(0).getValue());\n    assertEquals(Collections.emptyMap(), ranges2List.get(0).getLabels());\n    assertEquals(Arrays.asList(new TSElement(1111L, 99.99), new TSElement(1000L, 8.88)), ranges2List.get(1).getValue());\n\n    Map<String, String> labels3 = new HashMap<>();\n    labels3.put(\"l3\", \"v33\");\n    labels3.put(\"l4\", \"v4\");\n    assertEquals(2200L, client.tsAdd(\"seriesMRevRange3\", 2200L, -1.1, TSCreateParams.createParams().labels(labels3)));\n    assertEquals(2400L, client.tsAdd(\"seriesMRevRange3\", 2400L, 1.1, TSCreateParams.createParams().labels(labels3)));\n    assertEquals(3300L, client.tsAdd(\"seriesMRevRange3\", 3300L, -33, TSCreateParams.createParams().labels(labels3)));\n    Map<String, TSMRangeElements> ranges3 = client.tsMRevRange(TSMRangeParams.multiRangeParams(500L, 4600L)\n        .aggregation(AggregationType.AVG, 500).withLabels().count(5).filter(\"l4=v4\"));\n    assertEquals(2, ranges3.size());\n    ArrayList<TSMRangeElements> ranges3List = new ArrayList<>(ranges3.values());\n    assertEquals(labels1, ranges3List.get(0).getLabels());\n    assertEquals(Arrays.asList(new TSElement(2000L, 3.1), new TSElement(1000L, 1.1)), ranges3List.get(0).getValue());\n    assertEquals(labels3, ranges3List.get(1).getLabels());\n    assertEquals(Arrays.asList(new TSElement(3000L, -33.0), new TSElement(2000L, 0.0)), ranges3List.get(1).getValue());\n  }\n\n  @Test\n  public void latest() {\n    client.tsCreate(\"ts1\");\n    client.tsCreate(\"ts2\");\n    client.tsCreateRule(\"ts1\", \"ts2\", AggregationType.SUM, 10);\n    client.tsAdd(\"ts1\", 1, 1);\n    client.tsAdd(\"ts1\", 2, 3);\n    client.tsAdd(\"ts1\", 11, 7);\n    client.tsAdd(\"ts1\", 13, 1);\n    List<TSElement> range = client.tsRange(\"ts1\", 0, 20);\n    assertEquals(4, range.size());\n\n    final TSElement compact = new TSElement(0, 4);\n    final TSElement latest = new TSElement(10, 8);\n\n    // get\n    assertEquals(compact, client.tsGet(\"ts2\", TSGetParams.getParams()));\n\n    assertEquals(latest, client.tsGet(\"ts2\", TSGetParams.getParams().latest()));\n\n    // range\n    assertEquals(Arrays.asList(compact), client.tsRange(\"ts2\", TSRangeParams.rangeParams(0, 10)));\n\n    assertEquals(Arrays.asList(compact, latest), client.tsRange(\"ts2\", TSRangeParams.rangeParams(0, 10).latest()));\n\n    // revrange\n    assertEquals(Arrays.asList(compact), client.tsRevRange(\"ts2\", TSRangeParams.rangeParams(0, 10)));\n\n    assertEquals(Arrays.asList(latest, compact), client.tsRevRange(\"ts2\", TSRangeParams.rangeParams(0, 10).latest()));\n  }\n\n  @Test\n  public void latestMulti() {\n    client.tsCreate(\"ts1\");\n    client.tsCreate(\"ts2\", TSCreateParams.createParams().label(\"compact\", \"true\"));\n    client.tsCreateRule(\"ts1\", \"ts2\", AggregationType.SUM, 10);\n    client.tsAdd(\"ts1\", 1, 1);\n    client.tsAdd(\"ts1\", 2, 3);\n    client.tsAdd(\"ts1\", 11, 7);\n    client.tsAdd(\"ts1\", 13, 1);\n    List<TSElement> range = client.tsRange(\"ts1\", 0, 20);\n    assertEquals(4, range.size());\n\n    final TSElement compact = new TSElement(0, 4);\n    final TSElement latest = new TSElement(10, 8);\n\n    // mget\n    assertEquals(makeSingletonMap(new TSMGetElement(\"ts2\", null, compact)),\n        client.tsMGet(TSMGetParams.multiGetParams(), \"compact=true\"));\n\n    assertEquals(makeSingletonMap(new TSMGetElement(\"ts2\", null, latest)),\n        client.tsMGet(TSMGetParams.multiGetParams().latest(), \"compact=true\"));\n\n    // mrange\n    assertEquals(makeSingletonMap(new TSMRangeElements(\"ts2\", null, Arrays.asList(compact))),\n        client.tsMRange(TSMRangeParams.multiRangeParams().filter(\"compact=true\")));\n\n    assertEquals(makeSingletonMap(new TSMRangeElements(\"ts2\", null, Arrays.asList(compact, latest))),\n        client.tsMRange(TSMRangeParams.multiRangeParams().latest().filter(\"compact=true\")));\n\n    // mrevrange\n    assertEquals(makeSingletonMap(new TSMRangeElements(\"ts2\", null, Arrays.asList(compact))),\n        client.tsMRevRange(TSMRangeParams.multiRangeParams().filter(\"compact=true\")));\n\n    assertEquals(makeSingletonMap(new TSMRangeElements(\"ts2\", null, Arrays.asList(latest, compact))),\n        client.tsMRevRange(TSMRangeParams.multiRangeParams().latest().filter(\"compact=true\")));\n  }\n\n  private Map<String, TSMGetElement> makeSingletonMap(TSMGetElement value) {\n    return Collections.singletonMap(value.getKey(), value);\n  }\n\n  private Map<String, TSMRangeElements> makeSingletonMap(TSMRangeElements value) {\n    return Collections.singletonMap(value.getKey(), value);\n  }\n\n  @Test\n  public void empty() {\n    client.tsCreate(\"ts\", TSCreateParams.createParams().label(\"l\", \"v\"));\n    client.tsAdd(\"ts\", 1, 1);\n    client.tsAdd(\"ts\", 2, 3);\n    client.tsAdd(\"ts\", 11, 7);\n    client.tsAdd(\"ts\", 13, 1);\n\n    // range\n    List<TSElement> range = client.tsRange(\"ts\", TSRangeParams.rangeParams().aggregation(AggregationType.MAX, 5));\n    assertEquals(2, range.size());\n    range = client.tsRange(\"ts\", TSRangeParams.rangeParams().aggregation(AggregationType.MAX, 5).empty());\n    assertEquals(3, range.size());\n    assertNotNull(range.get(1).getValue()); // any parsable value\n\n    // revrange\n    range = client.tsRevRange(\"ts\", TSRangeParams.rangeParams().aggregation(AggregationType.MIN, 5));\n    assertEquals(2, range.size());\n    range = client.tsRevRange(\"ts\", TSRangeParams.rangeParams().aggregation(AggregationType.MIN, 5).empty());\n    assertEquals(3, range.size());\n    assertNotNull(range.get(1).getValue()); // any parsable value\n\n    // mrange\n    Map<String, TSMRangeElements> mrange = client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.MIN, 5).filter(\"l=v\"));\n    assertEquals(1, mrange.size());\n    ArrayList<TSMRangeElements> mrangeList = new ArrayList<>(mrange.values());\n    assertEquals(2, mrangeList.get(0).getValue().size());\n    mrange = client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.MIN, 5).empty().filter(\"l=v\"));\n    assertEquals(1, mrange.size());\n    mrangeList = new ArrayList<>(mrange.values());\n    assertEquals(3, mrangeList.get(0).getValue().size());\n    assertNotNull(mrangeList.get(0).getValue().get(1).getValue()); // any parsable value\n\n    // mrevrange\n    mrange = client.tsMRevRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.MAX, 5).filter(\"l=v\"));\n    assertEquals(1, mrange.size());\n    mrangeList = new ArrayList<>(mrange.values());\n    assertEquals(2, mrangeList.get(0).getValue().size());\n    mrange = client.tsMRevRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.MAX, 5).empty().filter(\"l=v\"));\n    assertEquals(1, mrange.size());\n    mrangeList = new ArrayList<>(mrange.values());\n    assertEquals(3, mrangeList.get(0).getValue().size());\n    assertNotNull(mrangeList.get(0).getValue().get(1).getValue()); // any parsable value\n  }\n\n  @Test\n  public void bucketTimestamp() {\n    client.tsCreate(\"ts\", TSCreateParams.createParams().label(\"l\", \"v\"));\n    client.tsAdd(\"ts\", 1, 1);\n    client.tsAdd(\"ts\", 2, 3);\n\n    // range / revrange\n    assertEquals(0, client.tsRange(\"ts\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.FIRST, 10).bucketTimestampLow()).get(0).getTimestamp());\n    assertEquals(10, client.tsRange(\"ts\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.LAST, 10).bucketTimestampHigh()).get(0).getTimestamp());\n    assertEquals(5, client.tsRange(\"ts\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.RANGE, 10).bucketTimestampMid()).get(0).getTimestamp());\n    assertEquals(5, client.tsRevRange(\"ts\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.TWA, 10).bucketTimestampMid()).get(0).getTimestamp());\n    assertEquals(5, client.tsRevRange(\"ts\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.TWA, 10).bucketTimestamp(\"mid\")).get(0).getTimestamp());\n\n    // mrange / mrevrange\n    assertEquals(0, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.STD_P, 10).bucketTimestampLow().filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n    assertEquals(10, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.STD_S, 10).bucketTimestampHigh().filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n    assertEquals(5, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.TWA, 10).bucketTimestampMid().filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n    assertEquals(5, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.VAR_P, 10).bucketTimestampMid().filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n    assertEquals(5, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.VAR_S, 10).bucketTimestamp(\"~\").filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n  }\n\n  @Test\n  public void alignTimestamp() {\n    client.tsCreate(\"ts1\");\n    client.tsCreate(\"ts2\");\n    client.tsCreate(\"ts3\");\n    client.tsCreateRule(\"ts1\", \"ts2\", AggregationType.COUNT, 10, 0);\n    client.tsCreateRule(\"ts1\", \"ts3\", AggregationType.COUNT, 10, 1);\n    client.tsAdd(\"ts1\", 1, 1);\n    client.tsAdd(\"ts1\", 10, 3);\n    client.tsAdd(\"ts1\", 21, 7);\n    assertEquals(2, client.tsRange(\"ts2\", TSRangeParams.rangeParams().aggregation(AggregationType.COUNT, 10)).size());\n    assertEquals(1, client.tsRange(\"ts3\", TSRangeParams.rangeParams().aggregation(AggregationType.COUNT, 10)).size());\n  }\n\n  /**\n   * Test for COUNTNAN and COUNTALL aggregation types introduced in RedisTimeSeries 8.6.0.\n   * COUNTNAN counts the number of NaN values in a bucket.\n   * COUNTALL counts all values in a bucket, including NaN values.\n   */\n  @Test\n  @SinceRedisVersion(\"8.5.0\")\n  public void countNanAndCountAll() {\n    // Create a time series with some regular values\n    client.tsCreate(\"ts-countnan\", TSCreateParams.createParams().label(\"type\", \"test\"));\n    client.tsAdd(\"ts-countnan\", 1, 1.0);\n    client.tsAdd(\"ts-countnan\", 2, 2.0);\n    client.tsAdd(\"ts-countnan\", 3, Double.NaN);\n    client.tsAdd(\"ts-countnan\", 4, 4.0);\n    client.tsAdd(\"ts-countnan\", 5, Double.NaN);\n    client.tsAdd(\"ts-countnan\", 11, 11.0);\n    client.tsAdd(\"ts-countnan\", 12, Double.NaN);\n\n    // Test COUNTNAN aggregation - counts NaN values in each bucket\n    List<TSElement> countNanValues = client.tsRange(\"ts-countnan\",\n        TSRangeParams.rangeParams(0L, 20L).aggregation(AggregationType.COUNTNAN, 10));\n    assertEquals(2, countNanValues.size());\n    // First bucket (0-10): 2 NaN values (at timestamps 3 and 5)\n    assertEquals(0L, countNanValues.get(0).getTimestamp());\n    assertEquals(2.0, countNanValues.get(0).getValue(), 0.001);\n    // Second bucket (10-20): 1 NaN value (at timestamp 12)\n    assertEquals(10L, countNanValues.get(1).getTimestamp());\n    assertEquals(1.0, countNanValues.get(1).getValue(), 0.001);\n\n    // Test COUNTALL aggregation - counts all values including NaN\n    List<TSElement> countAllValues = client.tsRange(\"ts-countnan\",\n        TSRangeParams.rangeParams(0L, 20L).aggregation(AggregationType.COUNTALL, 10));\n    assertEquals(2, countAllValues.size());\n    // First bucket (0-10): 5 total values\n    assertEquals(0L, countAllValues.get(0).getTimestamp());\n    assertEquals(5.0, countAllValues.get(0).getValue(), 0.001);\n    // Second bucket (10-20): 2 total values\n    assertEquals(10L, countAllValues.get(1).getTimestamp());\n    assertEquals(2.0, countAllValues.get(1).getValue(), 0.001);\n\n    // Compare with regular COUNT which excludes NaN values\n    List<TSElement> countValues = client.tsRange(\"ts-countnan\",\n        TSRangeParams.rangeParams(0L, 20L).aggregation(AggregationType.COUNT, 10));\n    assertEquals(2, countValues.size());\n    // First bucket (0-10): 3 non-NaN values\n    assertEquals(0L, countValues.get(0).getTimestamp());\n    assertEquals(3.0, countValues.get(0).getValue(), 0.001);\n    // Second bucket (10-20): 1 non-NaN value\n    assertEquals(10L, countValues.get(1).getTimestamp());\n    assertEquals(1.0, countValues.get(1).getValue(), 0.001);\n\n    // Test with MRANGE\n    Map<String, TSMRangeElements> mrangeCountNan = client.tsMRange(\n        TSMRangeParams.multiRangeParams(0L, 20L)\n            .aggregation(AggregationType.COUNTNAN, 10)\n            .filter(\"type=test\"));\n    assertEquals(1, mrangeCountNan.size());\n    TSMRangeElements elements = mrangeCountNan.get(\"ts-countnan\");\n    assertNotNull(elements);\n    assertEquals(2, elements.getValue().size());\n    assertEquals(2.0, elements.getValue().get(0).getValue(), 0.001);\n\n    Map<String, TSMRangeElements> mrangeCountAll = client.tsMRange(\n        TSMRangeParams.multiRangeParams(0L, 20L)\n            .aggregation(AggregationType.COUNTALL, 10)\n            .filter(\"type=test\"));\n    assertEquals(1, mrangeCountAll.size());\n    elements = mrangeCountAll.get(\"ts-countnan\");\n    assertNotNull(elements);\n    assertEquals(2, elements.getValue().size());\n    assertEquals(5.0, elements.getValue().get(0).getValue(), 0.001);\n\n    // Test with REVRANGE\n    List<TSElement> revRangeCountNan = client.tsRevRange(\"ts-countnan\",\n        TSRangeParams.rangeParams(0L, 20L).aggregation(AggregationType.COUNTNAN, 10));\n    assertEquals(2, revRangeCountNan.size());\n    // Results should be in reverse order\n    assertEquals(10L, revRangeCountNan.get(0).getTimestamp());\n    assertEquals(1.0, revRangeCountNan.get(0).getValue(), 0.001);\n    assertEquals(0L, revRangeCountNan.get(1).getTimestamp());\n    assertEquals(2.0, revRangeCountNan.get(1).getValue(), 0.001);\n\n    List<TSElement> revRangeCountAll = client.tsRevRange(\"ts-countnan\",\n        TSRangeParams.rangeParams(0L, 20L).aggregation(AggregationType.COUNTALL, 10));\n    assertEquals(2, revRangeCountAll.size());\n    assertEquals(10L, revRangeCountAll.get(0).getTimestamp());\n    assertEquals(2.0, revRangeCountAll.get(0).getValue(), 0.001);\n    assertEquals(0L, revRangeCountAll.get(1).getTimestamp());\n    assertEquals(5.0, revRangeCountAll.get(1).getValue(), 0.001);\n\n    // Test with MREVRANGE\n    Map<String, TSMRangeElements> mrevrangeCountNan = client.tsMRevRange(\n        TSMRangeParams.multiRangeParams(0L, 20L)\n            .aggregation(AggregationType.COUNTNAN, 10)\n            .filter(\"type=test\"));\n    assertEquals(1, mrevrangeCountNan.size());\n    elements = mrevrangeCountNan.get(\"ts-countnan\");\n    assertNotNull(elements);\n    assertEquals(2, elements.getValue().size());\n    // Results in reverse order\n    assertEquals(1.0, elements.getValue().get(0).getValue(), 0.001);\n    assertEquals(2.0, elements.getValue().get(1).getValue(), 0.001);\n  }\n\n  /**\n   * Test COUNTNAN and COUNTALL with bucket timestamp options.\n   */\n  @Test\n  @SinceRedisVersion(\"8.5.0\")\n  public void countNanAndCountAllWithBucketTimestamp() {\n    client.tsCreate(\"ts-countnan-bucket\", TSCreateParams.createParams().label(\"l\", \"v\"));\n    client.tsAdd(\"ts-countnan-bucket\", 1, 1.0);\n    client.tsAdd(\"ts-countnan-bucket\", 2, Double.NaN);\n    client.tsAdd(\"ts-countnan-bucket\", 3, 3.0);\n\n    // Test COUNTNAN with different bucket timestamp options\n    assertEquals(0, client.tsRange(\"ts-countnan-bucket\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.COUNTNAN, 10).bucketTimestampLow()).get(0).getTimestamp());\n    assertEquals(10, client.tsRange(\"ts-countnan-bucket\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.COUNTNAN, 10).bucketTimestampHigh()).get(0).getTimestamp());\n    assertEquals(5, client.tsRange(\"ts-countnan-bucket\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.COUNTNAN, 10).bucketTimestampMid()).get(0).getTimestamp());\n\n    // Test COUNTALL with different bucket timestamp options\n    assertEquals(0, client.tsRange(\"ts-countnan-bucket\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.COUNTALL, 10).bucketTimestampLow()).get(0).getTimestamp());\n    assertEquals(10, client.tsRange(\"ts-countnan-bucket\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.COUNTALL, 10).bucketTimestampHigh()).get(0).getTimestamp());\n    assertEquals(5, client.tsRange(\"ts-countnan-bucket\", TSRangeParams.rangeParams()\n        .aggregation(AggregationType.COUNTALL, 10).bucketTimestampMid()).get(0).getTimestamp());\n\n    // Test with MRANGE\n    assertEquals(0, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.COUNTNAN, 10).bucketTimestampLow().filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n    assertEquals(10, client.tsMRange(TSMRangeParams.multiRangeParams()\n        .aggregation(AggregationType.COUNTALL, 10).bucketTimestampHigh().filter(\"l=v\"))\n        .values().stream().findAny().get().getValue().get(0).getTimestamp());\n  }\n\n  /**\n   * Test that AggregationType.safeValueOf correctly parses COUNTNAN and COUNTALL.\n   */\n  @Test\n  @SinceRedisVersion(\"8.5.0\")\n  public void aggregationTypeSafeValueOf() {\n    assertEquals(AggregationType.COUNTNAN, AggregationType.safeValueOf(\"COUNTNAN\"));\n    assertEquals(AggregationType.COUNTNAN, AggregationType.safeValueOf(\"countnan\"));\n    assertEquals(AggregationType.COUNTALL, AggregationType.safeValueOf(\"COUNTALL\"));\n    assertEquals(AggregationType.COUNTALL, AggregationType.safeValueOf(\"countall\"));\n    // Verify existing types still work\n    assertEquals(AggregationType.COUNT, AggregationType.safeValueOf(\"COUNT\"));\n    assertEquals(AggregationType.AVG, AggregationType.safeValueOf(\"avg\"));\n    assertEquals(AggregationType.STD_P, AggregationType.safeValueOf(\"STD.P\"));\n    assertEquals(AggregationType.VAR_S, AggregationType.safeValueOf(\"var.s\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/BitPosParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class BitPosParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        BitPosParams firstParam = getDefaultValue();\n        BitPosParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        BitPosParams firstParam = getDefaultValue();\n        BitPosParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        BitPosParams firstParam = getDefaultValue();\n        BitPosParams secondParam = getDefaultValue();\n        secondParam.end(15);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        BitPosParams firstParam = getDefaultValue();\n        BitPosParams secondParam = getDefaultValue();\n        secondParam.start(15);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        BitPosParams firstParam = getDefaultValue();\n        BitPosParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private BitPosParams getDefaultValue() {\n        return new BitPosParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ClientKillParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.args.ClientType;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ClientKillParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ClientKillParams firstParam = getDefaultValue();\n        ClientKillParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ClientKillParams firstParam = getDefaultValue();\n        ClientKillParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n    \n    @Test\n    public void checkEqualsVariousParams() {\n        ClientKillParams firstParam = getDefaultValue();\n        firstParam.type(ClientType.NORMAL);\n        ClientKillParams secondParam = getDefaultValue();\n        secondParam.skipMe(ClientKillParams.SkipMe.NO);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ClientKillParams firstParam = getDefaultValue();\n        firstParam.type(ClientType.NORMAL);\n        ClientKillParams secondParam = getDefaultValue();\n        secondParam.skipMe(ClientKillParams.SkipMe.NO);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ClientKillParams firstParam = getDefaultValue();\n        ClientKillParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ClientKillParams getDefaultValue() {\n        return new ClientKillParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/CommandListFilterByParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class CommandListFilterByParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        CommandListFilterByParams firstParam = getDefaultValue();\n        CommandListFilterByParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        CommandListFilterByParams firstParam = getDefaultValue();\n        CommandListFilterByParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        CommandListFilterByParams firstParam = getDefaultValue();\n        firstParam.filterByAclCat(\"admin\");\n        CommandListFilterByParams secondParam = getDefaultValue();\n        secondParam.filterByModule(\"JSON\");\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        CommandListFilterByParams firstParam = getDefaultValue();\n        firstParam.filterByAclCat(\"admin\");\n        CommandListFilterByParams secondParam = getDefaultValue();\n        secondParam.filterByModule(\"JSON\");\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        CommandListFilterByParams firstParam = getDefaultValue();\n        CommandListFilterByParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private CommandListFilterByParams getDefaultValue() {\n        return new CommandListFilterByParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/FailoverParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class FailoverParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        FailoverParams firstParam = getDefaultValue();\n        FailoverParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        FailoverParams firstParam = getDefaultValue();\n        FailoverParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        FailoverParams firstParam = getDefaultValue();\n        firstParam.timeout(15);\n        FailoverParams secondParam = getDefaultValue();\n        secondParam.timeout(20);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        FailoverParams firstParam = getDefaultValue();\n        firstParam.timeout(15);\n        FailoverParams secondParam = getDefaultValue();\n        secondParam.timeout(20);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        FailoverParams firstParam = getDefaultValue();\n        FailoverParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private FailoverParams getDefaultValue() {\n        return new FailoverParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/GeoAddParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class GeoAddParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        GeoAddParams firstParam = getDefaultValue();\n        GeoAddParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        GeoAddParams firstParam = getDefaultValue();\n        GeoAddParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        GeoAddParams firstParam = getDefaultValue();\n        firstParam.nx();\n        GeoAddParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        GeoAddParams firstParam = getDefaultValue();\n        firstParam.nx();\n        GeoAddParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        GeoAddParams firstParam = getDefaultValue();\n        GeoAddParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private GeoAddParams getDefaultValue() {\n        return new GeoAddParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/GetExParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class GetExParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        GetExParams firstParam = getDefaultValue();\n        GetExParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        GetExParams firstParam = getDefaultValue();\n        GetExParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        GetExParams firstParam = getDefaultValue();\n        firstParam.ex(15);\n        GetExParams secondParam = getDefaultValue();\n        secondParam.px(20);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        GetExParams firstParam = getDefaultValue();\n        firstParam.ex(15);\n        GetExParams secondParam = getDefaultValue();\n        secondParam.px(20);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        GetExParams firstParam = getDefaultValue();\n        GetExParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private GetExParams getDefaultValue() {\n        return new GetExParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/HotkeysParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.args.HotkeysMetric;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class HotkeysParamsTest {\n\n  private HotkeysParams getDefaultValue() {\n    return new HotkeysParams();\n  }\n\n  @Nested\n  class EqualityAndHashCodeTests {\n\n    @Test\n    public void equalsWithIdenticalParams() {\n      HotkeysParams firstParam = getDefaultValue();\n      HotkeysParams secondParam = getDefaultValue();\n      assertEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void hashCodeWithIdenticalParams() {\n      HotkeysParams firstParam = getDefaultValue();\n      HotkeysParams secondParam = getDefaultValue();\n      assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void equalsWithDifferentParams() {\n      HotkeysParams firstParam = getDefaultValue();\n      firstParam.metrics(HotkeysMetric.CPU).count(10);\n      HotkeysParams secondParam = getDefaultValue();\n      secondParam.metrics(HotkeysMetric.NET).count(20);\n      assertNotEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void hashCodeWithDifferentParams() {\n      HotkeysParams firstParam = getDefaultValue();\n      firstParam.metrics(HotkeysMetric.CPU).count(10);\n      HotkeysParams secondParam = getDefaultValue();\n      secondParam.metrics(HotkeysMetric.NET).count(20);\n      assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void equalsWithNull() {\n      HotkeysParams firstParam = getDefaultValue();\n      HotkeysParams secondParam = null;\n      assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void equalsWithSameInstance() {\n      HotkeysParams param = getDefaultValue();\n      assertTrue(param.equals(param));\n    }\n\n    @Test\n    public void equalsWithDifferentMetrics() {\n      HotkeysParams firstParam = getDefaultValue();\n      firstParam.metrics(HotkeysMetric.CPU);\n      HotkeysParams secondParam = getDefaultValue();\n      secondParam.metrics(HotkeysMetric.NET);\n      assertNotEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void equalsWithDifferentSlots() {\n      HotkeysParams firstParam = getDefaultValue();\n      firstParam.metrics(HotkeysMetric.CPU).slots(1, 2, 3);\n      HotkeysParams secondParam = getDefaultValue();\n      secondParam.metrics(HotkeysMetric.CPU).slots(4, 5, 6);\n      assertNotEquals(firstParam, secondParam);\n    }\n  }\n\n  @Nested\n  class ValidationTests {\n\n    @Test\n    public void metricsNullThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.metrics(null);\n      });\n      assertEquals(\"metrics must not be null\", exception.getMessage());\n    }\n\n    @Test\n    public void metricsEmptyArrayThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.metrics(new HotkeysMetric[0]);\n      });\n      assertEquals(\"at least one metric is required\", exception.getMessage());\n    }\n\n    @Test\n    public void countTooLowThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.count(9);\n      });\n      assertEquals(\"count must be between 1 and 64\", exception.getMessage());\n    }\n\n    @Test\n    public void countTooHighThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.count(65);\n      });\n      assertEquals(\"count must be between 1 and 64\", exception.getMessage());\n    }\n\n    @Test\n    public void countBoundaryMinValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.count(10));\n    }\n\n    @Test\n    public void countBoundaryMaxValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.count(64));\n    }\n\n    @Test\n    public void durationNegativeThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.duration(-1);\n      });\n      assertEquals(\"duration must be >= 0\", exception.getMessage());\n    }\n\n    @Test\n    public void durationZeroValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.duration(0));\n    }\n\n    @Test\n    public void sampleTooLowThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.sample(0);\n      });\n      assertEquals(\"sample must be >= 1\", exception.getMessage());\n    }\n\n    @Test\n    public void sampleOneValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.sample(1));\n    }\n\n    @Test\n    public void slotTooLowThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.slots(-1);\n      });\n      assertEquals(\"each slot must be between 0 and 16383\", exception.getMessage());\n    }\n\n    @Test\n    public void slotTooHighThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        params.slots(16384);\n      });\n      assertEquals(\"each slot must be between 0 and 16383\", exception.getMessage());\n    }\n\n    @Test\n    public void slotBoundaryMinValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.slots(0));\n    }\n\n    @Test\n    public void slotBoundaryMaxValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.slots(16383));\n    }\n\n    @Test\n    public void slotsMultipleWithInvalidThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      assertThrows(IllegalArgumentException.class, () -> {\n        params.slots(0, 100, 16384);\n      });\n    }\n\n    @Test\n    public void slotsNullValid() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.slots((int[]) null));\n    }\n  }\n\n  @Nested\n  class BuilderTests {\n\n    @Test\n    public void staticFactoryMethodReturnsInstance() {\n      HotkeysParams params = HotkeysParams.hotkeysParams();\n      assertNotNull(params);\n      assertInstanceOf(HotkeysParams.class, params);\n    }\n\n    @Test\n    public void methodChainingWorks() {\n      HotkeysParams params = HotkeysParams.hotkeysParams()\n          .metrics(HotkeysMetric.CPU, HotkeysMetric.NET).count(20).duration(60).sample(5)\n          .slots(0, 100, 200);\n      assertNotNull(params);\n    }\n\n    @Test\n    public void builderPatternProducesCorrectState() {\n      HotkeysParams firstParam = HotkeysParams.hotkeysParams()\n          .metrics(HotkeysMetric.CPU, HotkeysMetric.NET).count(20).duration(60).sample(5)\n          .slots(0, 100);\n\n      HotkeysParams secondParam = HotkeysParams.hotkeysParams()\n          .metrics(HotkeysMetric.CPU, HotkeysMetric.NET).count(20).duration(60).sample(5)\n          .slots(0, 100);\n\n      assertEquals(firstParam, secondParam);\n      assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n  }\n\n  @Nested\n  class EdgeCaseBehaviorTests {\n\n    @Test\n    public void slotsEmptyArrayNotAdded() {\n      HotkeysParams params = getDefaultValue();\n      assertDoesNotThrow(() -> params.metrics(HotkeysMetric.CPU).slots(new int[0]));\n    }\n\n    @Test\n    public void lastBuilderCallWins() {\n      HotkeysParams firstParam = getDefaultValue();\n      firstParam.metrics(HotkeysMetric.CPU).count(10).count(20);\n\n      HotkeysParams secondParam = getDefaultValue();\n      secondParam.metrics(HotkeysMetric.CPU).count(20);\n\n      assertEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void equalsWithSameMetricsDifferentOrder() {\n      HotkeysParams firstParam = getDefaultValue();\n      firstParam.metrics(HotkeysMetric.CPU, HotkeysMetric.NET);\n\n      HotkeysParams secondParam = getDefaultValue();\n      secondParam.metrics(HotkeysMetric.NET, HotkeysMetric.CPU);\n\n      assertNotEquals(firstParam, secondParam);\n    }\n  }\n\n  @Nested\n  class AddParamsTests {\n\n    @Test\n    public void addParamsWithMinimalConfiguration() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.CPU);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 1 CPU\n      assertEquals(4, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.CPU, args.get(3));\n    }\n\n    @Test\n    public void addParamsWithAllOptions() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.CPU, HotkeysMetric.NET).count(20).duration(60).sample(5)\n          .slots(100, 200, 300);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 2 CPU NET COUNT 20 DURATION 60 SAMPLE 5 SLOTS 3 100 200 300\n      assertEquals(16, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.CPU, args.get(3));\n      assertEquals(HotkeysMetric.NET, args.get(4));\n      assertEquals(Protocol.Keyword.COUNT, args.get(5));\n      assertEquals(Protocol.Keyword.DURATION, args.get(7));\n      assertEquals(Protocol.Keyword.SAMPLE, args.get(9));\n      assertEquals(Protocol.Keyword.SLOTS, args.get(11));\n    }\n\n    @Test\n    public void addParamsWithEmptySlotsNotAdded() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.CPU).slots(new int[0]);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 1 CPU (no SLOTS)\n      assertEquals(4, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.CPU, args.get(3));\n    }\n\n    @Test\n    public void addParamsWithMultipleMetrics() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.CPU, HotkeysMetric.NET);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 2 CPU NET\n      assertEquals(5, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.CPU, args.get(3));\n      assertEquals(HotkeysMetric.NET, args.get(4));\n    }\n\n    @Test\n    public void addParamsWithOnlyCount() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.CPU).count(30);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 1 CPU COUNT 30\n      assertEquals(6, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.CPU, args.get(3));\n      assertEquals(Protocol.Keyword.COUNT, args.get(4));\n    }\n\n    @Test\n    public void addParamsWithOnlyDuration() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.NET).duration(120);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 1 NET DURATION 120\n      assertEquals(6, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.NET, args.get(3));\n      assertEquals(Protocol.Keyword.DURATION, args.get(4));\n    }\n\n    @Test\n    public void addParamsWithOnlySample() {\n      HotkeysParams params = getDefaultValue();\n      params.metrics(HotkeysMetric.CPU).sample(10);\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n      params.addParams(args);\n\n      // Expected: HOTKEYS METRICS 1 CPU SAMPLE 10\n      assertEquals(6, args.size());\n      assertEquals(Protocol.Keyword.METRICS, args.get(1));\n      assertEquals(HotkeysMetric.CPU, args.get(3));\n      assertEquals(Protocol.Keyword.SAMPLE, args.get(4));\n    }\n\n    @Test\n    public void addParamsWithoutMetricsThrowsException() {\n      HotkeysParams params = getDefaultValue();\n      // Don't set metrics\n\n      CommandArguments args = new CommandArguments(Protocol.Command.HOTKEYS);\n\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n        () -> params.addParams(args));\n      assertEquals(\"metrics must not be null\", exception.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/LCSParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class LCSParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        LCSParams firstParam = getDefaultValue();\n        LCSParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        LCSParams firstParam = getDefaultValue();\n        LCSParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        LCSParams firstParam = getDefaultValue();\n        firstParam.idx();\n        LCSParams secondParam = getDefaultValue();\n        secondParam.len();\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        LCSParams firstParam = getDefaultValue();\n        firstParam.idx();\n        LCSParams secondParam = getDefaultValue();\n        secondParam.len();\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        LCSParams firstParam = getDefaultValue();\n        LCSParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private LCSParams getDefaultValue() {\n        return new LCSParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/LPosParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class LPosParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        LPosParams firstParam = getDefaultValue();\n        LPosParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        LPosParams firstParam = getDefaultValue();\n        LPosParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        LPosParams firstParam = getDefaultValue();\n        firstParam.rank(1);\n        LPosParams secondParam = getDefaultValue();\n        secondParam.rank(2);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        LPosParams firstParam = getDefaultValue();\n        firstParam.rank(1);\n        LPosParams secondParam = getDefaultValue();\n        secondParam.rank(2);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        LPosParams firstParam = getDefaultValue();\n        LPosParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private LPosParams getDefaultValue() {\n        return new LPosParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/LolwutParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class LolwutParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        LolwutParams firstParam = getDefaultValue();\n        LolwutParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        LolwutParams firstParam = getDefaultValue();\n        LolwutParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        LolwutParams firstParam = getDefaultValue();\n        firstParam.version(1);\n        LolwutParams secondParam = getDefaultValue();\n        secondParam.version(2);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        LolwutParams firstParam = getDefaultValue();\n        firstParam.version(1);\n        LolwutParams secondParam = getDefaultValue();\n        secondParam.version(2);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        LolwutParams firstParam = getDefaultValue();\n        LolwutParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private LolwutParams getDefaultValue() {\n        return new LolwutParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/MigrateParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class MigrateParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        MigrateParams firstParam = getDefaultValue();\n        MigrateParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        MigrateParams firstParam = getDefaultValue();\n        MigrateParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        MigrateParams firstParam = getDefaultValue();\n        firstParam.auth(\"123\");\n        MigrateParams secondParam = getDefaultValue();\n        secondParam.auth(\"234\");\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        MigrateParams firstParam = getDefaultValue();\n        firstParam.auth(\"123\");\n        MigrateParams secondParam = getDefaultValue();\n        secondParam.auth(\"234\");\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        MigrateParams firstParam = getDefaultValue();\n        MigrateParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private MigrateParams getDefaultValue() {\n        return new MigrateParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ModuleLoadExParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ModuleLoadExParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ModuleLoadExParams firstParam = getDefaultValue();\n        ModuleLoadExParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ModuleLoadExParams firstParam = getDefaultValue();\n        ModuleLoadExParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ModuleLoadExParams firstParam = getDefaultValue();\n        firstParam.arg(\"123\");\n        ModuleLoadExParams secondParam = getDefaultValue();\n        secondParam.arg(\"234\");\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ModuleLoadExParams firstParam = getDefaultValue();\n        firstParam.arg(\"123\");\n        ModuleLoadExParams secondParam = getDefaultValue();\n        secondParam.arg(\"234\");\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ModuleLoadExParams firstParam = getDefaultValue();\n        ModuleLoadExParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ModuleLoadExParams getDefaultValue() {\n        return new ModuleLoadExParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/RestoreParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class RestoreParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        RestoreParams firstParam = getDefaultValue();\n        RestoreParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        RestoreParams firstParam = getDefaultValue();\n        RestoreParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        RestoreParams firstParam = getDefaultValue();\n        firstParam.idleTime(14);\n        RestoreParams secondParam = getDefaultValue();\n        secondParam.idleTime(15);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        RestoreParams firstParam = getDefaultValue();\n        firstParam.idleTime(14);\n        RestoreParams secondParam = getDefaultValue();\n        secondParam.idleTime(15);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        RestoreParams firstParam = getDefaultValue();\n        RestoreParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private RestoreParams getDefaultValue() {\n        return new RestoreParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ScanParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ScanParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ScanParams firstParam = getDefaultValue();\n        ScanParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ScanParams firstParam = getDefaultValue();\n        ScanParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ScanParams firstParam = getDefaultValue();\n        firstParam.count(15);\n        ScanParams secondParam = getDefaultValue();\n        secondParam.count(16);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ScanParams firstParam = getDefaultValue();\n        firstParam.count(15);\n        ScanParams secondParam = getDefaultValue();\n        secondParam.count(16);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ScanParams firstParam = getDefaultValue();\n        ScanParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ScanParams getDefaultValue() {\n        return new ScanParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/SetParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class SetParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        SetParams firstParam = getDefaultValue();\n        SetParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        SetParams firstParam = getDefaultValue();\n        SetParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        SetParams firstParam = getDefaultValue();\n        firstParam.nx();\n        SetParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        SetParams firstParam = getDefaultValue();\n        firstParam.nx();\n        SetParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        SetParams firstParam = getDefaultValue();\n        SetParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private SetParams getDefaultValue() {\n        return new SetParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ShutdownParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ShutdownParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ShutdownParams firstParam = getDefaultValue();\n        ShutdownParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ShutdownParams firstParam = getDefaultValue();\n        ShutdownParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ShutdownParams firstParam = getDefaultValue();\n        firstParam.force();\n        ShutdownParams secondParam = getDefaultValue();\n        secondParam.nosave();\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ShutdownParams firstParam = getDefaultValue();\n        firstParam.force();\n        ShutdownParams secondParam = getDefaultValue();\n        secondParam.nosave();\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ShutdownParams firstParam = getDefaultValue();\n        ShutdownParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ShutdownParams getDefaultValue() {\n        return new ShutdownParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/SortingParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class SortingParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        SortingParams firstParam = getDefaultValue();\n        SortingParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        SortingParams firstParam = getDefaultValue();\n        SortingParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        SortingParams firstParam = getDefaultValue();\n        firstParam.limit(15, 20);\n        SortingParams secondParam = getDefaultValue();\n        secondParam.limit(10, 15);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        SortingParams firstParam = getDefaultValue();\n        firstParam.limit(15, 20);\n        SortingParams secondParam = getDefaultValue();\n        secondParam.limit(10, 15);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        SortingParams firstParam = getDefaultValue();\n        SortingParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private SortingParams getDefaultValue() {\n        return new SortingParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XAddParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XAddParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XAddParams firstParam = getDefaultValue();\n        XAddParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XAddParams firstParam = getDefaultValue();\n        XAddParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.id(15);\n        XAddParams secondParam = getDefaultValue();\n        secondParam.id(20);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.id(15);\n        XAddParams secondParam = getDefaultValue();\n        secondParam.id(20);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XAddParams firstParam = getDefaultValue();\n        XAddParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkEqualsIdmpAutoParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmpAuto(\"producer1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmpAuto(\"producer1\");\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdmpAutoParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmpAuto(\"producer1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmpAuto(\"producer1\");\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousIdmpAutoParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmpAuto(\"producer1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmpAuto(\"producer2\");\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkEqualsIdmpParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmp(\"producer1\", \"iid1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmp(\"producer1\", \"iid1\");\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdmpParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmp(\"producer1\", \"iid1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmp(\"producer1\", \"iid1\");\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousIdmpParams() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmp(\"producer1\", \"iid1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmp(\"producer1\", \"iid2\");\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkEqualsIdmpAutoVsIdmp() {\n        XAddParams firstParam = getDefaultValue();\n        firstParam.idmpAuto(\"producer1\");\n        XAddParams secondParam = getDefaultValue();\n        secondParam.idmp(\"producer1\", \"iid1\");\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XAddParams getDefaultValue() {\n        return new XAddParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XAutoClaimParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XAutoClaimParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XAutoClaimParams firstParam = getDefaultValue();\n        XAutoClaimParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XAutoClaimParams firstParam = getDefaultValue();\n        XAutoClaimParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XAutoClaimParams firstParam = getDefaultValue();\n        firstParam.count(15);\n        XAutoClaimParams secondParam = getDefaultValue();\n        secondParam.count(20);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XAutoClaimParams firstParam = getDefaultValue();\n        firstParam.count(15);\n        XAutoClaimParams secondParam = getDefaultValue();\n        secondParam.count(20);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XAutoClaimParams firstParam = getDefaultValue();\n        XAutoClaimParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XAutoClaimParams getDefaultValue() {\n        return new XAutoClaimParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XCfgSetParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XCfgSetParamsTest {\n\n  @Test\n  public void checkEqualsIdenticalParams() {\n    XCfgSetParams firstParam = getDefaultValue();\n    XCfgSetParams secondParam = getDefaultValue();\n    assertTrue(firstParam.equals(secondParam));\n  }\n\n  @Test\n  public void checkHashCodeIdenticalParams() {\n    XCfgSetParams firstParam = getDefaultValue();\n    XCfgSetParams secondParam = getDefaultValue();\n    assertEquals(firstParam.hashCode(), secondParam.hashCode());\n  }\n\n  @Test\n  public void checkEqualsVariousParams() {\n    XCfgSetParams firstParam = getDefaultValue();\n    firstParam.idmpDuration(100);\n    XCfgSetParams secondParam = getDefaultValue();\n    secondParam.idmpDuration(200);\n    assertFalse(firstParam.equals(secondParam));\n  }\n\n  @Test\n  public void checkHashCodeVariousParams() {\n    XCfgSetParams firstParam = getDefaultValue();\n    firstParam.idmpDuration(100);\n    XCfgSetParams secondParam = getDefaultValue();\n    secondParam.idmpDuration(200);\n    assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n  }\n\n  @Test\n  public void checkEqualsWithNull() {\n    XCfgSetParams firstParam = getDefaultValue();\n    XCfgSetParams secondParam = null;\n    assertFalse(firstParam.equals(secondParam));\n  }\n\n  @Test\n  public void testIdmpDurationValidRange() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    // Test minimum valid value\n    assertDoesNotThrow(() -> params.idmpDuration(1));\n\n    // Test maximum valid value\n    assertDoesNotThrow(() -> params.idmpDuration(86400));\n\n    // Test value in range\n    assertDoesNotThrow(() -> params.idmpDuration(100));\n  }\n\n  @Test\n  public void testIdmpDurationBelowMinimum() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n      () -> params.idmpDuration(0));\n    assertEquals(\"IDMP-DURATION must be between 1 and 86400 seconds\", exception.getMessage());\n\n    exception = assertThrows(IllegalArgumentException.class, () -> params.idmpDuration(-1));\n    assertEquals(\"IDMP-DURATION must be between 1 and 86400 seconds\", exception.getMessage());\n  }\n\n  @Test\n  public void testIdmpDurationAboveMaximum() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n      () -> params.idmpDuration(86401));\n    assertEquals(\"IDMP-DURATION must be between 1 and 86400 seconds\", exception.getMessage());\n  }\n\n  @Test\n  public void testIdmpMaxsizeValidRange() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    // Test minimum valid value\n    assertDoesNotThrow(() -> params.idmpMaxsize(1));\n\n    // Test maximum valid value\n    assertDoesNotThrow(() -> params.idmpMaxsize(10000));\n\n    // Test value in range\n    assertDoesNotThrow(() -> params.idmpMaxsize(100));\n  }\n\n  @Test\n  public void testIdmpMaxsizeBelowMinimum() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n      () -> params.idmpMaxsize(0));\n    assertEquals(\"IDMP-MAXSIZE must be between 1 and 10000\", exception.getMessage());\n\n    exception = assertThrows(IllegalArgumentException.class, () -> params.idmpMaxsize(-1));\n    assertEquals(\"IDMP-MAXSIZE must be between 1 and 10000\", exception.getMessage());\n  }\n\n  @Test\n  public void testIdmpMaxsizeAboveMaximum() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n      () -> params.idmpMaxsize(10001));\n    assertEquals(\"IDMP-MAXSIZE must be between 1 and 10000\", exception.getMessage());\n  }\n\n  @Test\n  public void testBothParametersValid() {\n    XCfgSetParams params = new XCfgSetParams();\n\n    assertDoesNotThrow(() -> params.idmpDuration(1000).idmpMaxsize(500));\n  }\n\n  private XCfgSetParams getDefaultValue() {\n    return new XCfgSetParams();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XClaimParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XClaimParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XClaimParams firstParam = getDefaultValue();\n        XClaimParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XClaimParams firstParam = getDefaultValue();\n        XClaimParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XClaimParams firstParam = getDefaultValue();\n        firstParam.time(20);\n        XClaimParams secondParam = getDefaultValue();\n        secondParam.time(21);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XClaimParams firstParam = getDefaultValue();\n        firstParam.time(20);\n        XClaimParams secondParam = getDefaultValue();\n        secondParam.time(21);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XClaimParams firstParam = getDefaultValue();\n        XClaimParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XClaimParams getDefaultValue() {\n        return new XClaimParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XPendingParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.StreamEntryID;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XPendingParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XPendingParams firstParam = getDefaultValue();\n        XPendingParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XPendingParams firstParam = getDefaultValue();\n        XPendingParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XPendingParams firstParam = getDefaultValue();\n        firstParam.start(StreamEntryID.XGROUP_LAST_ENTRY);\n        XPendingParams secondParam = getDefaultValue();\n        secondParam.start(StreamEntryID.NEW_ENTRY);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XPendingParams firstParam = getDefaultValue();\n        firstParam.start(StreamEntryID.XGROUP_LAST_ENTRY);\n        XPendingParams secondParam = getDefaultValue();\n        secondParam.start(StreamEntryID.NEW_ENTRY);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XPendingParams firstParam = getDefaultValue();\n        XPendingParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XPendingParams getDefaultValue() {\n        return new XPendingParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XReadGroupParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XReadGroupParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XReadGroupParams firstParam = getDefaultValue();\n        XReadGroupParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XReadGroupParams firstParam = getDefaultValue();\n        XReadGroupParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XReadGroupParams firstParam = getDefaultValue();\n        firstParam.block(14);\n        XReadGroupParams secondParam = getDefaultValue();\n        secondParam.block(15);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XReadGroupParams firstParam = getDefaultValue();\n        firstParam.block(14);\n        XReadGroupParams secondParam = getDefaultValue();\n        secondParam.block(15);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XReadGroupParams firstParam = getDefaultValue();\n        XReadGroupParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XReadGroupParams getDefaultValue() {\n        return new XReadGroupParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XReadParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XReadParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XReadParams firstParam = getDefaultValue();\n        XReadParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XReadParams firstParam = getDefaultValue();\n        XReadParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XReadParams firstParam = getDefaultValue();\n        firstParam.block(14);\n        XReadParams secondParam = getDefaultValue();\n        secondParam.block(15);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XReadParams firstParam = getDefaultValue();\n        firstParam.block(14);\n        XReadParams secondParam = getDefaultValue();\n        secondParam.block(15);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XReadParams firstParam = getDefaultValue();\n        XReadParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XReadParams getDefaultValue() {\n        return new XReadParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/XTrimParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XTrimParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        XTrimParams firstParam = getDefaultValue();\n        XTrimParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        XTrimParams firstParam = getDefaultValue();\n        XTrimParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        XTrimParams firstParam = getDefaultValue();\n        firstParam.maxLen(15);\n        XTrimParams secondParam = getDefaultValue();\n        secondParam.maxLen(16);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        XTrimParams firstParam = getDefaultValue();\n        firstParam.maxLen(15);\n        XTrimParams secondParam = getDefaultValue();\n        secondParam.maxLen(16);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        XTrimParams firstParam = getDefaultValue();\n        XTrimParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private XTrimParams getDefaultValue() {\n        return new XTrimParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ZAddParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ZAddParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ZAddParams firstParam = getDefaultValue();\n        ZAddParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ZAddParams firstParam = getDefaultValue();\n        ZAddParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ZAddParams firstParam = getDefaultValue();\n        firstParam.nx();\n        ZAddParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ZAddParams firstParam = getDefaultValue();\n        firstParam.nx();\n        ZAddParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ZAddParams firstParam = getDefaultValue();\n        ZAddParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ZAddParams getDefaultValue() {\n        return new ZAddParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ZIncrByParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ZIncrByParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ZIncrByParams firstParam = getDefaultValue();\n        ZIncrByParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ZIncrByParams firstParam = getDefaultValue();\n        ZIncrByParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ZIncrByParams firstParam = getDefaultValue();\n        firstParam.nx();\n        ZIncrByParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ZIncrByParams firstParam = getDefaultValue();\n        firstParam.nx();\n        ZIncrByParams secondParam = getDefaultValue();\n        secondParam.xx();\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ZIncrByParams firstParam = getDefaultValue();\n        ZIncrByParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ZIncrByParams getDefaultValue() {\n        return new ZIncrByParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ZParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ZParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ZParams firstParam = getDefaultValue();\n        ZParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ZParams firstParam = getDefaultValue();\n        ZParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ZParams firstParam = getDefaultValue();\n        firstParam.aggregate(ZParams.Aggregate.MIN);\n        ZParams secondParam = getDefaultValue();\n        secondParam.aggregate(ZParams.Aggregate.MAX);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ZParams firstParam = getDefaultValue();\n        firstParam.aggregate(ZParams.Aggregate.MIN);\n        ZParams secondParam = getDefaultValue();\n        secondParam.aggregate(ZParams.Aggregate.MAX);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ZParams firstParam = getDefaultValue();\n        ZParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    private ZParams getDefaultValue() {\n        return new ZParams();\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/params/ZRangeParamsTest.java",
    "content": "package redis.clients.jedis.params;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.args.RawableFactory;\nimport redis.clients.jedis.util.CommandArgumentsMatchers;\nimport redis.clients.jedis.util.ProtocolTestUtil;\n\nimport java.io.IOException;\nimport java.util.Iterator;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static redis.clients.jedis.util.CommandArgumentsMatchers.*;\n\npublic class ZRangeParamsTest {\n\n    @Test\n    public void checkEqualsIdenticalParams() {\n        ZRangeParams firstParam = getDefaultValue();\n        ZRangeParams secondParam = getDefaultValue();\n        assertTrue(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeIdenticalParams() {\n        ZRangeParams firstParam = getDefaultValue();\n        ZRangeParams secondParam = getDefaultValue();\n        assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsVariousParams() {\n        ZRangeParams firstParam = getDefaultValue();\n        firstParam.limit(15, 20);\n        ZRangeParams secondParam = getDefaultValue();\n        secondParam.limit(16, 21);\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void checkHashCodeVariousParams() {\n        ZRangeParams firstParam = getDefaultValue();\n        firstParam.limit(15, 20);\n        ZRangeParams secondParam = getDefaultValue();\n        secondParam.limit(16, 21);\n        assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void checkEqualsWithNull() {\n        ZRangeParams firstParam = getDefaultValue();\n        ZRangeParams secondParam = null;\n        assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Nested\n    class BuilderTests {\n\n        @Test\n        public void testZrangeParamsIntMinMax() {\n            int min = 0;\n            int max = 1;\n            ZRangeParams params = ZRangeParams.zrangeParams(min, max);\n            CommandArguments args = new CommandArguments(Protocol.Command.ZRANGE);\n            params.addParams(args);\n\n            assertThat(args, hasArgumentCount(3));\n\n            assertThat(args, hasArguments(\n                Protocol.Command.ZRANGE,\n                RawableFactory.from(min),\n                RawableFactory.from(max)\n            ));\n        }\n\n        //Test that long values are serialized as long (not double) to avoid precision loss\n        @Test\n        public void testZrangeParamsLongMinMax() {\n            long min = Integer.MAX_VALUE + 1L;\n            long max = Integer.MAX_VALUE + 2L;\n\n            ZRangeParams params = ZRangeParams.zrangeParams(min, max);\n            CommandArguments args = new CommandArguments(Protocol.Command.ZRANGE);\n            params.addParams(args);\n\n            assertThat(args, hasArgumentCount(3));\n\n            assertThat(args, hasArguments(\n                Protocol.Command.ZRANGE,\n                RawableFactory.from(min),\n                RawableFactory.from(max)\n            ));\n        }\n\n        //Test that int factory method delegates to long constructor and produces the same Rawable encoding\n        @Test\n        public void testZrangeParamsIntMinMaxDelegatesToLong() {\n            int min = 100;\n            int max = 200;\n            ZRangeParams intParams = ZRangeParams.zrangeParams(min, max);\n            ZRangeParams longParams = ZRangeParams.zrangeParams((long) min, (long) max);\n            CommandArguments intArgs = new CommandArguments(Protocol.Command.ZRANGE);\n            intParams.addParams(intArgs);\n            CommandArguments longArgs = new CommandArguments(Protocol.Command.ZRANGE);\n            longParams.addParams(longArgs);\n\n            // Both should produce identical arguments\n            assertEquals(intArgs.size(), longArgs.size());\n            Iterator<Rawable> intIter = intArgs.iterator();\n            Iterator<Rawable> longIter = longArgs.iterator();\n            while (intIter.hasNext() && longIter.hasNext()) {\n                assertEquals(intIter.next(), longIter.next());\n            }\n        }\n\n        // Test that zrangeParams(double, double) produces BYSCORE args\n        @Test\n        public void testZrangeParamsByScore() {\n            double min = 0.1;\n            double max = 1.1;\n            ZRangeParams params = ZRangeParams.zrangeByScoreParams(min, max);\n            CommandArguments args = new CommandArguments(Protocol.Command.ZRANGE);\n            params.addParams(args);\n\n            assertThat(args, hasArgumentCount(4));\n            assertThat(args, hasArguments(\n                Protocol.Command.ZRANGE,\n                RawableFactory.from(min),\n                RawableFactory.from(max),\n                Protocol.Keyword.BYSCORE\n            ));\n        }\n\n        // Test the actual RESP protocol output\n        @Test\n        public void testProtocolOutputWithLongValues() throws IOException {\n            long largeStart = 3_000_000_000L;\n            long largeEnd = 3_000_000_099L;\n\n            ZRangeParams params = ZRangeParams.zrangeParams(largeStart, largeEnd);\n            CommandArguments args = new CommandArguments(Protocol.Command.ZRANGE);\n            params.addParams(args);\n\n            // Capture the RESP protocol output\n            String respOutput = ProtocolTestUtil.captureCommandOutput(args);\n\n            // RESP protocol uses CRLF (\\r\\n) line endings\n            String expected = \"*3\\r\\n\" + \"$6\\r\\n\" + \"ZRANGE\\r\\n\" + \"$10\\r\\n\" + \"3000000000\\r\\n\" + \"$10\\r\\n\" + \"3000000099\\r\\n\";\n            assertEquals(expected, respOutput);\n        }\n\n    }\n\n    private ZRangeParams getDefaultValue() {\n        return new ZRangeParams(0, 0);\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java",
    "content": "package redis.clients.jedis.prefix;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisClient;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.BeforeAll;\n\n@Tag(\"integration\")\npublic class JedisPooledPrefixedKeysTest extends PrefixedKeysTest<RedisClient> {\n\n  private static EndpointConfig ENDPOINT;\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    ENDPOINT = Endpoints.getRedisEndpoint(\"standalone1\");\n  }\n\n  @Override\n  RedisClient nonPrefixingJedis() {\n    return RedisClient.builder()\n        .hostAndPort(ENDPOINT.getHostAndPort())\n        .clientConfig(ENDPOINT.getClientConfigBuilder().timeoutMillis(500).build())\n        .build();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java",
    "content": "package redis.clients.jedis.prefix;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport redis.clients.jedis.AbstractPipeline;\nimport redis.clients.jedis.AbstractTransaction;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.resps.Tuple;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.PrefixedKeyArgumentPreProcessor;\nimport redis.clients.jedis.util.SafeEncoder;\nimport redis.clients.jedis.util.TestEnvUtil;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic abstract class PrefixedKeysTest<T extends UnifiedJedis> {\n\n    @RegisterExtension\n    public static EnvCondition envCondition = new EnvCondition();\n\n    abstract T nonPrefixingJedis();\n\n    T prefixingJedis() {\n        T jedis = nonPrefixingJedis();\n        jedis.setKeyArgumentPreProcessor(new PrefixedKeyArgumentPreProcessor(\"test-prefix:\"));\n        return jedis;\n    }\n\n    @AfterEach\n    public void cleanUp() {\n        try (UnifiedJedis jedis = prefixingJedis()) {\n            jedis.flushAll();\n        }\n    }\n\n    @Test\n    public void prefixesKeys() {\n        try (UnifiedJedis jedis = prefixingJedis()) {\n            jedis.set(\"foo1\", \"bar1\");\n            jedis.set(SafeEncoder.encode(\"foo2\"), SafeEncoder.encode(\"bar2\"));\n            AbstractPipeline pipeline = jedis.pipelined();\n            pipeline.incr(\"foo3\");\n            pipeline.zadd(\"foo4\", 1234, \"bar4\");\n            pipeline.sync();\n        }\n\n        try (UnifiedJedis jedis = nonPrefixingJedis()) {\n            assertEquals(\"bar1\", jedis.get(\"test-prefix:foo1\"));\n            assertEquals(\"bar2\", jedis.get(\"test-prefix:foo2\"));\n            assertEquals(\"1\", jedis.get(\"test-prefix:foo3\"));\n            assertEquals(new Tuple(\"bar4\", 1234d), jedis.zpopmax(\"test-prefix:foo4\"));\n        }\n    }\n\n    @Test\n    @ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n    public void prefixesKeysInTransaction() {\n        try (UnifiedJedis jedis = prefixingJedis()) {\n            AbstractTransaction transaction = jedis.multi();\n            transaction.set(\"foo1\", \"bar1-from-transaction\");\n            transaction.hset(\"foo2\", \"bar2-key\", \"bar2-value\");\n            transaction.exec();\n        }\n\n        try (UnifiedJedis jedis = nonPrefixingJedis()) {\n            assertEquals(\"bar1-from-transaction\", jedis.get(\"test-prefix:foo1\"));\n            assertEquals(\"bar2-value\", jedis.hget(\"test-prefix:foo2\", \"bar2-key\"));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/prefix/RedisClusterPrefixedKeysTest.java",
    "content": "package redis.clients.jedis.prefix;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.RedisClusterClient;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@Tag(\"integration\")\npublic class RedisClusterPrefixedKeysTest extends PrefixedKeysTest<RedisClusterClient> {\n\n  private static EndpointConfig endpoint;\n  private static JedisClientConfig CLIENT_CONFIG;\n  private static Set<HostAndPort> NODES;\n\n  @BeforeAll\n  public static void prepareEndpoint() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-stable\");\n    CLIENT_CONFIG = endpoint.getClientConfigBuilder().build();\n    NODES = new HashSet<>(endpoint.getHostsAndPorts());\n  }\n\n  @Override\n  RedisClusterClient nonPrefixingJedis() {\n    return RedisClusterClient.builder()\n        .nodes(NODES)\n        .clientConfig(CLIENT_CONFIG)\n        .build();\n  }\n\n  @Override\n  @Test\n  public void prefixesKeysInTransaction() {\n    assertThrows(UnsupportedOperationException.class, () -> super.prefixesKeysInTransaction());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/prefix/RedisSentinelClientPrefixedKeysTest.java",
    "content": "package redis.clients.jedis.prefix;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.RedisSentinelClient;\nimport org.junit.jupiter.api.Tag;\n\n@Tag(\"integration\")\npublic class RedisSentinelClientPrefixedKeysTest extends PrefixedKeysTest<RedisSentinelClient> {\n\n  private static final String MASTER_NAME = \"mymaster\";\n  private static final JedisClientConfig MASTER_CLIENT_CONFIG = DefaultJedisClientConfig.builder().password(\"foobared\").build();\n  private static Set<HostAndPort> SENTINEL_NODES;\n  private static final JedisClientConfig SENTINEL_CLIENT_CONFIG = DefaultJedisClientConfig.builder().build();\n\n  @BeforeAll\n  public static void prepareEndpoints() {\n    SENTINEL_NODES = new HashSet<>(\n        Arrays.asList(\n            Endpoints.getRedisEndpoint(\"sentinel-standalone2-1\").getHostAndPort(),\n            Endpoints.getRedisEndpoint(\"sentinel-standalone2-3\").getHostAndPort()));\n  }\n\n  @Override\n  RedisSentinelClient nonPrefixingJedis() {\n    return RedisSentinelClient.builder()\n        .masterName(MASTER_NAME)\n        .clientConfig(MASTER_CLIENT_CONFIG)\n        .sentinels(SENTINEL_NODES)\n        .sentinelClientConfig(SENTINEL_CLIENT_CONFIG)\n        .build();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/providers/HealthStatusManagerTest.java",
    "content": "package redis.clients.jedis.providers;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.mcf.HealthCheckStrategy;\nimport redis.clients.jedis.mcf.HealthStatus;\nimport redis.clients.jedis.mcf.HealthStatusListener;\nimport redis.clients.jedis.mcf.HealthStatusManager;\nimport redis.clients.jedis.mcf.TestHealthCheckStrategy;\n\npublic class HealthStatusManagerTest {\n\n  private final HostAndPort endpoint = new HostAndPort(\"localhost\", 6379);\n\n  @Test\n  void manager_event_emission_order_basic() throws InterruptedException {\n    HealthStatusManager manager = new HealthStatusManager();\n\n    CountDownLatch eventLatch = new CountDownLatch(1);\n    HealthStatusListener listener = event -> eventLatch.countDown();\n\n    manager.registerListener(endpoint, listener);\n\n    HealthCheckStrategy immediateHealthy = new TestHealthCheckStrategy(\n        HealthCheckStrategy.Config.builder().interval(50).timeout(25).build(),\n        e -> HealthStatus.HEALTHY);\n\n    manager.add(endpoint, immediateHealthy);\n\n    assertTrue(eventLatch.await(2, TimeUnit.SECONDS), \"Should receive health status event\");\n\n    manager.remove(endpoint);\n  }\n\n  @Test\n  void manager_hasHealthCheck_add_remove() {\n    HealthStatusManager manager = new HealthStatusManager();\n    assertFalse(manager.hasHealthCheck(endpoint));\n\n    HealthCheckStrategy strategy = new TestHealthCheckStrategy(\n        HealthCheckStrategy.Config.builder().interval(50).timeout(25).build(),\n        e -> HealthStatus.HEALTHY);\n\n    manager.add(endpoint, strategy);\n    assertTrue(manager.hasHealthCheck(endpoint));\n    manager.remove(endpoint);\n    assertFalse(manager.hasHealthCheck(endpoint));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/providers/MultiClusterPooledConnectionProviderTest.java",
    "content": ""
  },
  {
    "path": "src/test/java/redis/clients/jedis/providers/MultiDbProviderHealthStatusChangeTest.java",
    "content": "package redis.clients.jedis.providers;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.MockedConstruction;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport redis.clients.jedis.Connection;\nimport redis.clients.jedis.ConnectionPool;\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.MultiDbConfig;\nimport redis.clients.jedis.mcf.HealthStatus;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.mcf.MultiDbConnectionProviderHelper;\n\n/**\n * Tests for MultiDbConnectionProvider event handling behavior during initialization and throughout\n * its lifecycle with HealthStatusChangeEvents.\n */\n@ExtendWith(MockitoExtension.class)\npublic class MultiDbProviderHealthStatusChangeTest {\n\n  private HostAndPort endpoint1;\n  private HostAndPort endpoint2;\n  private HostAndPort endpoint3;\n  private JedisClientConfig clientConfig;\n\n  @BeforeEach\n  void setUp() {\n    endpoint1 = new HostAndPort(\"localhost\", 6879);\n    endpoint2 = new HostAndPort(\"localhost\", 6880);\n    endpoint3 = new HostAndPort(\"localhost\", 6881);\n    clientConfig = DefaultJedisClientConfig.builder().build();\n  }\n\n  private MockedConstruction<ConnectionPool> mockConnectionPool() {\n    Connection mockConnection = mock(Connection.class);\n    lenient().when(mockConnection.ping()).thenReturn(true);\n    return mockConstruction(ConnectionPool.class, (mock, context) -> {\n      when(mock.getResource()).thenReturn(mockConnection);\n      doNothing().when(mock).close();\n    });\n  }\n\n  @Test\n  void postInit_unhealthy_active_sets_grace_and_fails_over() throws Exception {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockConnectionPool()) {\n      // Create databases without health checks\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build();\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(0.5f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n\n        assertFalse(provider.getDatabase(endpoint1).isInGracePeriod());\n        assertEquals(provider.getDatabase(), provider.getDatabase(endpoint1));\n\n        // This should process immediately since initialization is complete\n        assertDoesNotThrow(() -> {\n          MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n            HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n        }, \"Post-initialization events should be processed immediately\");\n\n        // Verify the database has changed according to the UNHEALTHY status\n        assertTrue(provider.getDatabase(endpoint1).isInGracePeriod(),\n          \"UNHEALTHY status on active database should cause a grace period\");\n        assertNotEquals(provider.getDatabase(), provider.getDatabase(endpoint1),\n          \"UNHEALTHY status on active database should cause a failover\");\n      }\n    }\n  }\n\n  @Test\n  void postInit_nonActive_changes_do_not_switch_active() throws Exception {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockConnectionPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build();\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(0.5f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Verify initial state\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase(),\n          \"Should start with endpoint1 active\");\n\n        // Simulate multiple rapid events for the same endpoint (post-init behavior)\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // After first UNHEALTHY on active database: it enters grace period and provider fails over\n        assertTrue(provider.getDatabase(endpoint1).isInGracePeriod(),\n          \"Active database should enter grace period\");\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase(),\n          \"Should fail over to endpoint2\");\n\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n\n        // Healthy event for non-active database should not immediately revert active database\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase(),\n          \"Active database should remain endpoint2\");\n        assertTrue(provider.getDatabase(endpoint1).isInGracePeriod(),\n          \"Grace period should still be in effect\");\n\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n\n        // Further UNHEALTHY for non-active database is a no-op\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase(),\n          \"Active database unchanged\");\n        assertTrue(provider.getDatabase(endpoint1).isInGracePeriod(), \"Still in grace period\");\n      }\n    }\n  }\n\n  @Test\n  void init_selects_highest_weight_healthy_when_checks_disabled() throws Exception {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockConnectionPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(2.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // This test verifies that multiple endpoints are properly initialized\n\n        // Verify both databases are initialized properly\n        assertNotNull(provider.getDatabase(endpoint1), \"Database 1 should be available\");\n        assertNotNull(provider.getDatabase(endpoint2), \"Database 2 should be available\");\n\n        // Both should be healthy (no health checks = assumed healthy)\n        assertTrue(provider.getDatabase(endpoint1).isHealthy(), \"Database 1 should be healthy\");\n        assertTrue(provider.getDatabase(endpoint2).isHealthy(), \"Database 2 should be healthy\");\n      }\n    }\n  }\n\n  @Test\n  void init_single_database_initializes_and_is_healthy() throws Exception {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockConnectionPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1 }).build();\n\n      // This test verifies that the provider initializes correctly and doesn't lose events\n      // In practice, with health checks disabled, no events should be generated during init\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Verify successful initialization\n        assertNotNull(provider.getDatabase(), \"Provider should have initialized successfully\");\n        assertEquals(provider.getDatabase(endpoint1), provider.getDatabase(),\n          \"Should have selected the configured database\");\n        assertTrue(provider.getDatabase().isHealthy(),\n          \"Database should be healthy (assumed healthy with no health checks)\");\n      }\n    }\n  }\n\n  // ========== POST-INITIALIZATION EVENT ORDERING TESTS ==========\n\n  @Test\n  void postInit_two_hop_failover_chain_respected() throws Exception {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockConnectionPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(0.5f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database3 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint3, clientConfig).weight(0.2f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2, database3 }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // First event: endpoint1 (active) becomes UNHEALTHY -> failover to endpoint2, endpoint1\n        // enters grace\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n        assertTrue(provider.getDatabase(endpoint1).isInGracePeriod(),\n          \"Endpoint1 should be in grace after unhealthy\");\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase(),\n          \"Should have failed over to endpoint2\");\n\n        // Second event: endpoint2 (now active) becomes UNHEALTHY -> failover to endpoint3\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint2,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY);\n        assertTrue(provider.getDatabase(endpoint2).isInGracePeriod(),\n          \"Endpoint2 should be in grace after unhealthy\");\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase(),\n          \"Should have failed over to endpoint3\");\n\n        // Third event: endpoint1 becomes HEALTHY again -> no immediate switch due to grace period\n        // behavior\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY);\n        assertEquals(provider.getDatabase(endpoint3), provider.getDatabase(),\n          \"Active database should remain endpoint3\");\n      }\n    }\n  }\n\n  @Test\n  void postInit_rapid_events_respect_grace_and_keep_active_stable() throws Exception {\n    try (MockedConstruction<ConnectionPool> mockedPool = mockConnectionPool()) {\n      MultiDbConfig.DatabaseConfig database1 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint1, clientConfig).weight(1.0f).healthCheckEnabled(false).build();\n\n      MultiDbConfig.DatabaseConfig database2 = MultiDbConfig.DatabaseConfig\n          .builder(endpoint2, clientConfig).weight(0.5f).healthCheckEnabled(false).build();\n\n      MultiDbConfig config = new MultiDbConfig.Builder(\n          new MultiDbConfig.DatabaseConfig[] { database1, database2 }).build();\n\n      try (MultiDbConnectionProvider provider = new MultiDbConnectionProvider(config)) {\n        // Verify initial state\n        assertEquals(HealthStatus.HEALTHY, provider.getDatabase(endpoint1).getHealthStatus(),\n          \"Should start as HEALTHY\");\n\n        // Send rapid sequence of events post-init\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY); // triggers failover and grace\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.UNHEALTHY, HealthStatus.HEALTHY); // non-active database becomes healthy\n        MultiDbConnectionProviderHelper.onHealthStatusChange(provider, endpoint1,\n          HealthStatus.HEALTHY, HealthStatus.UNHEALTHY); // still non-active and in grace; no change\n\n        // Final expectations: endpoint1 is in grace, provider remains on endpoint2\n        assertTrue(provider.getDatabase(endpoint1).isInGracePeriod(),\n          \"Endpoint1 should be in grace period\");\n        assertEquals(provider.getDatabase(endpoint2), provider.getDatabase(),\n          \"Active database should remain endpoint2\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/providers/SentineledConnectionProviderReconnectionTest.java",
    "content": "package redis.clients.jedis.providers;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisClientConfig;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.providers.SentineledConnectionProvider.SentinelConnectionFactory;\nimport redis.clients.jedis.util.Delay;\n\n/**\n * Unit tests for SentineledConnectionProvider reconnection logic. Tests connection provider's\n * ability to reconnect to sentinel nodes with configured delay.\n */\n@ExtendWith(MockitoExtension.class)\n@Tag(\"unit\")\npublic class SentineledConnectionProviderReconnectionTest {\n\n  private static final String MASTER_NAME = \"mymaster\";\n\n  private static final HostAndPort SENTINEL_1 = new HostAndPort(\"localhost\", 26379);\n\n  private static final HostAndPort SENTINEL_2 = new HostAndPort(\"localhost\", 26380);\n\n  private static final HostAndPort MASTER = new HostAndPort(\"localhost\", 6379);\n\n  private Set<HostAndPort> sentinels;\n\n  private JedisClientConfig masterConfig;\n\n  private JedisClientConfig sentinelConfig;\n\n  private SentineledConnectionProvider provider;\n\n  @Mock\n  private Jedis mockJedis;\n\n  @Mock\n  private SentinelConnectionFactory sentinelConnectionFactory;\n\n  @Mock\n  private Delay reconnectDelay;\n\n  @BeforeEach\n  void setUp() {\n    sentinels = new HashSet<>();\n    sentinels.add(SENTINEL_1);\n    sentinels.add(SENTINEL_2);\n\n    masterConfig = mock(JedisClientConfig.class);\n    sentinelConfig = mock(JedisClientConfig.class);\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (provider != null) {\n      provider.close();\n    }\n  }\n\n  @Test\n  void testReconnectToSentinelWithConfiguredDelay() throws InterruptedException {\n    // Capture delay values passed to sleeper\n    CopyOnWriteArrayList<Long> capturedDelays = new CopyOnWriteArrayList<>();\n    // await for 3 reconnect attempts\n    CountDownLatch reconnectAttempts = new CountDownLatch(3);\n\n    // Mock dependencies\n    SentineledConnectionProvider.Sleeper capturingSleeper = millis -> {\n      capturedDelays.add(millis);\n      reconnectAttempts.countDown();\n    };\n\n    // Simulate sentinel connection failures (disconnect scenario)\n    long expectedDelay = 100L;\n    when(mockJedis.sentinelGetMasterAddrByName(MASTER_NAME))\n        .thenReturn(Arrays.asList(MASTER.getHost(), String.valueOf(MASTER.getPort())));\n\n    Jedis failingJedis = mock(Jedis.class);\n    doThrow(new JedisConnectionException(\"Connection lost\")).when(failingJedis)\n        .subscribe(any(JedisPubSub.class), anyString());\n    when(sentinelConnectionFactory.createConnection(any(), any()))\n        .thenAnswer(invocation -> mockJedis).thenAnswer(invocation -> failingJedis);\n    when(reconnectDelay.delay(anyLong())).thenReturn(Duration.ofMillis(expectedDelay));\n\n    // Create provider\n    provider = new SentineledConnectionProvider(MASTER_NAME, masterConfig, null, null, sentinels,\n        sentinelConfig, reconnectDelay, sentinelConnectionFactory, capturingSleeper);\n\n    // Verify reconnection attempts happen with configured delay\n    assertTrue(reconnectAttempts.await(100, TimeUnit.MILLISECONDS),\n      \"Should attempt to reconnect at least 3 times after disconnect\");\n\n    // Assert all delays match the configured value\n    assertTrue(capturedDelays.size() >= 3, \"Should have captured at least 3 delay values\");\n    for (Long delay : capturedDelays) {\n      assertEquals(expectedDelay, delay,\n        \"Sleeper should be called with configured delay of \" + expectedDelay + \"ms\");\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/resps/LibraryInfoTest.java",
    "content": "package redis.clients.jedis.resps;\n\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.util.RedisInputStream;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class LibraryInfoTest {\n\n  private static Object parseRespResponse(String respResponse) {\n    InputStream is = new ByteArrayInputStream(respResponse.getBytes());\n    return Protocol.read(new RedisInputStream(is));\n  }\n\n  @Test\n  public void buildLibraryInfoResp2Standard() {\n    // Simulate standard RESP2 response for FUNCTION LIST (without WITHCODE)\n    // Format: [library_name, <value>, engine, <value>, functions, <value>]\n    String respResponse = \"*6\\r\\n\" + \"$12\\r\\nlibrary_name\\r\\n\" + \"$5\\r\\nmylib\\r\\n\"\n        + \"$6\\r\\nengine\\r\\n\" + \"$3\\r\\nLUA\\r\\n\" + \"$9\\r\\nfunctions\\r\\n\" + \"*1\\r\\n\" + // functions\n                                                                                    // array with 1\n                                                                                    // element\n        \"*6\\r\\n\" + // function info with 3 key-value pairs\n        \"$4\\r\\nname\\r\\n\" + \"$6\\r\\nmyfunc\\r\\n\" + \"$11\\r\\ndescription\\r\\n\" + \"$-1\\r\\n\" + // null\n                                                                                       // description\n        \"$5\\r\\nflags\\r\\n\" + \"*0\\r\\n\"; // empty flags array\n\n    Object data = parseRespResponse(respResponse);\n    LibraryInfo result = LibraryInfo.LIBRARY_INFO.build(data);\n\n    assertNotNull(result);\n    assertEquals(\"mylib\", result.getLibraryName());\n    assertEquals(\"LUA\", result.getEngine());\n    assertNotNull(result.getFunctions());\n    assertEquals(1, result.getFunctions().size());\n    assertEquals(\"myfunc\", result.getFunctions().get(0).get(\"name\"));\n    assertNull(result.getLibraryCode());\n  }\n\n  @Test\n  public void buildLibraryInfoResp2WithCode() {\n    // Simulate RESP2 response for FUNCTION LIST WITHCODE\n    // Format: [library_name, <value>, engine, <value>, functions, <value>, library_code, <value>]\n    String respResponse = \"*8\\r\\n\" + \"$12\\r\\nlibrary_name\\r\\n\" + \"$5\\r\\nmylib\\r\\n\"\n        + \"$6\\r\\nengine\\r\\n\" + \"$3\\r\\nLUA\\r\\n\" + \"$9\\r\\nfunctions\\r\\n\" + \"*1\\r\\n\" + // functions\n                                                                                    // array with 1\n                                                                                    // element\n        \"*6\\r\\n\" + // function info with 3 key-value pairs\n        \"$4\\r\\nname\\r\\n\" + \"$6\\r\\nmyfunc\\r\\n\" + \"$11\\r\\ndescription\\r\\n\" + \"$-1\\r\\n\" + // null\n                                                                                       // description\n        \"$5\\r\\nflags\\r\\n\" + \"*0\\r\\n\" + // empty flags array\n        \"$12\\r\\nlibrary_code\\r\\n\"\n        + \"$50\\r\\n#!LUA name=mylib\\nredis.register_function('myfunc')\\r\\n\";\n\n    Object data = parseRespResponse(respResponse);\n    LibraryInfo result = LibraryInfo.LIBRARY_INFO.build(data);\n\n    assertNotNull(result);\n    assertEquals(\"mylib\", result.getLibraryName());\n    assertEquals(\"LUA\", result.getEngine());\n    assertNotNull(result.getFunctions());\n    assertEquals(1, result.getFunctions().size());\n    assertEquals(\"myfunc\", result.getFunctions().get(0).get(\"name\"));\n    assertNotNull(result.getLibraryCode());\n    assertTrue(result.getLibraryCode().contains(\"#!LUA name=mylib\"));\n  }\n\n  @Test\n  public void buildLibraryInfoResp2WithExtraConsistentField() {\n    // Simulate Redis Enterprise RESP2 response with extra \"consistent\" field\n    // This is the bug scenario from CAE-2120\n    // Format: [library_name, <value>, engine, <value>, consistent, <value>, functions, <value>]\n    String respResponse = \"*8\\r\\n\" + \"$12\\r\\nlibrary_name\\r\\n\" + \"$5\\r\\nmylib\\r\\n\"\n        + \"$6\\r\\nengine\\r\\n\" + \"$3\\r\\nLUA\\r\\n\" + \"$10\\r\\nconsistent\\r\\n\" + \":1\\r\\n\" + // consistent\n                                                                                      // value\n                                                                                      // (integer)\n        \"$9\\r\\nfunctions\\r\\n\" + \"*1\\r\\n\" + // functions array with 1 element\n        \"*6\\r\\n\" + // function info with 3 key-value pairs\n        \"$4\\r\\nname\\r\\n\" + \"$6\\r\\nmyfunc\\r\\n\" + \"$11\\r\\ndescription\\r\\n\" + \"$-1\\r\\n\" + // null\n                                                                                       // description\n        \"$5\\r\\nflags\\r\\n\" + \"*0\\r\\n\"; // empty flags array\n\n    Object data = parseRespResponse(respResponse);\n    LibraryInfo result = LibraryInfo.LIBRARY_INFO.build(data);\n\n    assertNotNull(result);\n    assertEquals(\"mylib\", result.getLibraryName());\n    assertEquals(\"LUA\", result.getEngine());\n    assertNotNull(result.getFunctions());\n    assertEquals(1, result.getFunctions().size());\n    assertEquals(\"myfunc\", result.getFunctions().get(0).get(\"name\"));\n    assertNull(result.getLibraryCode());\n  }\n\n  @Test\n  public void buildLibraryInfoResp2WithExtraConsistentFieldAndCode() {\n    // Simulate Redis Enterprise RESP2 response with extra \"consistent\" field and WITHCODE\n    // Format: [library_name, <value>, engine, <value>, consistent, <value>, functions, <value>,\n    // library_code, <value>]\n    String respResponse = \"*10\\r\\n\" + \"$12\\r\\nlibrary_name\\r\\n\" + \"$5\\r\\nmylib\\r\\n\"\n        + \"$6\\r\\nengine\\r\\n\" + \"$3\\r\\nLUA\\r\\n\" + \"$10\\r\\nconsistent\\r\\n\" + \":1\\r\\n\" + // consistent\n                                                                                      // value\n                                                                                      // (integer)\n        \"$9\\r\\nfunctions\\r\\n\" + \"*1\\r\\n\" + // functions array with 1 element\n        \"*6\\r\\n\" + // function info with 3 key-value pairs\n        \"$4\\r\\nname\\r\\n\" + \"$6\\r\\nmyfunc\\r\\n\" + \"$11\\r\\ndescription\\r\\n\" + \"$-1\\r\\n\" + // null\n                                                                                       // description\n        \"$5\\r\\nflags\\r\\n\" + \"*0\\r\\n\" + // empty flags array\n        \"$12\\r\\nlibrary_code\\r\\n\"\n        + \"$50\\r\\n#!LUA name=mylib\\nredis.register_function('myfunc')\\r\\n\";\n\n    Object data = parseRespResponse(respResponse);\n    LibraryInfo result = LibraryInfo.LIBRARY_INFO.build(data);\n\n    assertNotNull(result);\n    assertEquals(\"mylib\", result.getLibraryName());\n    assertEquals(\"LUA\", result.getEngine());\n    assertNotNull(result.getFunctions());\n    assertEquals(1, result.getFunctions().size());\n    assertEquals(\"myfunc\", result.getFunctions().get(0).get(\"name\"));\n    assertNotNull(result.getLibraryCode());\n    assertTrue(result.getLibraryCode().contains(\"#!LUA name=mylib\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java",
    "content": "package redis.clients.jedis.resps;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class StreamEntryDeletionResultTest {\n\n  @Test\n  public void testFromCode() {\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, StreamEntryDeletionResult.fromCode(-1));\n    assertEquals(StreamEntryDeletionResult.DELETED, StreamEntryDeletionResult.fromCode(1));\n    assertEquals(StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED,\n      StreamEntryDeletionResult.fromCode(2));\n  }\n\n  @Test\n  public void testFromCodeInvalid() {\n    assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromCode(0));\n    assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromCode(3));\n    assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromCode(-2));\n  }\n\n  @Test\n  public void testFromLong() {\n    assertEquals(StreamEntryDeletionResult.NOT_FOUND, StreamEntryDeletionResult.fromLong(-1L));\n    assertEquals(StreamEntryDeletionResult.DELETED, StreamEntryDeletionResult.fromLong(1L));\n    assertEquals(StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED,\n      StreamEntryDeletionResult.fromLong(2L));\n  }\n\n  @Test\n  public void testFromLongNull() {\n    assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromLong(null));\n  }\n\n  @Test\n  public void testGetCode() {\n    assertEquals(-1, StreamEntryDeletionResult.NOT_FOUND.getCode());\n    assertEquals(1, StreamEntryDeletionResult.DELETED.getCode());\n    assertEquals(2,\n      StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED.getCode());\n  }\n\n  @Test\n  public void testToString() {\n    assertEquals(\"NOT_FOUND(-1)\", StreamEntryDeletionResult.NOT_FOUND.toString());\n    assertEquals(\"DELETED(1)\", StreamEntryDeletionResult.DELETED.toString());\n    assertEquals(\"NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED(2)\",\n      StreamEntryDeletionResult.NOT_DELETED_UNACKNOWLEDGED_OR_STILL_REFERENCED.toString());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/ActiveActiveFailoverIT.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Tags;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.MultiDbConfig.DatabaseConfig;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.mcf.DatabaseSwitchEvent;\nimport redis.clients.jedis.mcf.MultiDbConnectionProvider;\nimport redis.clients.jedis.util.ClientTestUtil;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static redis.clients.jedis.Protocol.DEFAULT_TIMEOUT;\n\n@Tags({ @Tag(\"failover\"), @Tag(\"scenario\") })\npublic class ActiveActiveFailoverIT {\n  private static final Logger log = LoggerFactory.getLogger(ActiveActiveFailoverIT.class);\n  private static final int NUM_OF_THREADS = 18;\n  private static final int SOCKET_TIMEOUT_MS = DEFAULT_TIMEOUT;\n  private static final int CONNECTION_TIMEOUT_MS = DEFAULT_TIMEOUT;\n  private static final long NETWORK_FAILURE_INTERVAL = 15L;\n\n  private static EndpointConfig endpoint;\n\n  private final FaultInjectionClient faultClient = new FaultInjectionClient();\n\n  @BeforeAll\n  public static void beforeClass() {\n    try {\n      ActiveActiveFailoverIT.endpoint = Endpoints.getRedisEndpoint(\"re-active-active\");\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false);\n    }\n  }\n\n  @Test\n  public void testFailover() {\n\n    JedisClientConfig config = endpoint.getClientConfigBuilder()\n        .socketTimeoutMillis(SOCKET_TIMEOUT_MS).connectionTimeoutMillis(CONNECTION_TIMEOUT_MS)\n        .build();\n\n    DatabaseConfig primary = DatabaseConfig.builder(endpoint.getHostAndPort(0), config)\n        .connectionPoolConfig(RecommendedSettings.poolConfig).weight(1.0f).build();\n\n    DatabaseConfig secondary = DatabaseConfig.builder(endpoint.getHostAndPort(1), config)\n        .connectionPoolConfig(RecommendedSettings.poolConfig).weight(0.5f).build();\n\n    MultiDbConfig multiConfig = MultiDbConfig.builder().database(primary).database(secondary)\n        .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder().slidingWindowSize(1) // SLIDING\n                                                                                           // WINDOW\n                                                                                           // SIZE\n                                                                                           // IN\n                                                                                           // SECONDS\n            .failureRateThreshold(10.0f) // percentage of failures to trigger circuit breaker\n            .build())\n        .failbackSupported(true).failbackCheckInterval(1000).gracePeriod(2000)\n        .commandRetry(MultiDbConfig.RetryConfig.builder().waitDuration(10).maxAttempts(1)\n            .exponentialBackoffMultiplier(1).build())\n        .fastFailover(true).retryOnFailover(false).build();\n    class FailoverReporter implements Consumer<DatabaseSwitchEvent> {\n\n      String currentClusterName = \"not set\";\n\n      boolean failoverHappened = false;\n\n      Instant failoverAt = null;\n\n      boolean failbackHappened = false;\n\n      Instant failbackAt = null;\n\n      public String getCurrentClusterName() {\n        return currentClusterName;\n      }\n\n      @Override\n      public void accept(DatabaseSwitchEvent e) {\n        this.currentClusterName = e.getDatabaseName();\n        log.info(\n          \"\\n\\n====FailoverEvent=== \\nJedis failover to cluster: {}\\n====FailoverEvent===\\n\\n\",\n          e.getDatabaseName());\n\n        if (failoverHappened) {\n          failbackHappened = true;\n          failbackAt = Instant.now();\n        } else {\n          failoverHappened = true;\n          failoverAt = Instant.now();\n        }\n      }\n    }\n\n    FailoverReporter reporter = new FailoverReporter();\n\n    MultiDbClient client = MultiDbClient.builder().multiDbConfig(multiConfig)\n        .databaseSwitchListener(reporter).build();\n\n    AtomicLong executedCommands = new AtomicLong(0);\n    AtomicLong retryingThreadsCounter = new AtomicLong(0);\n    AtomicLong failedCommandsAfterFailover = new AtomicLong(0);\n    AtomicReference<Instant> lastFailedCommandAt = new AtomicReference<>();\n    Endpoint primaryEndpoint = client.getActiveDatabaseEndpoint();\n\n    // Start thread that imitates an application that uses the client\n    MultiThreadedFakeApp fakeApp = new MultiThreadedFakeApp(client, (UnifiedJedis c) -> {\n\n      long threadId = Thread.currentThread().getId();\n\n      int attempt = 0;\n      int maxTries = 500;\n      int retryingDelay = 5;\n      while (true) {\n        boolean attemptToExecuteOnFailedCluster = true;\n        try {\n          Map<String, String> executionInfo = new HashMap<String, String>() {\n            {\n              put(\"threadId\", String.valueOf(threadId));\n              put(\"cluster\", reporter.getCurrentClusterName());\n            }\n          };\n          attemptToExecuteOnFailedCluster = client.getActiveDatabaseEndpoint() == primaryEndpoint;\n          client.xadd(\"execution_log\", StreamEntryID.NEW_ENTRY, executionInfo);\n          executedCommands.incrementAndGet();\n\n          if (attempt > 0) {\n            log.info(\"Thread {} recovered after {} ms. Threads still not recovered: {}\", threadId,\n              attempt * retryingDelay, retryingThreadsCounter.decrementAndGet());\n          }\n\n          break;\n        } catch (JedisConnectionException e) {\n\n          if (reporter.failoverHappened && !reporter.failbackHappened\n              && attemptToExecuteOnFailedCluster) {\n            failedCommandsAfterFailover.incrementAndGet();\n            lastFailedCommandAt.set(Instant.now());\n          }\n\n          if (attempt == 0) {\n            long failedThreads = retryingThreadsCounter.incrementAndGet();\n            log.warn(\"Thread {} failed to execute command. Failed threads: {}\", threadId,\n              failedThreads);\n          }\n          try {\n            Thread.sleep(retryingDelay);\n          } catch (InterruptedException ie) {\n            throw new RuntimeException(ie);\n          }\n          if (++attempt == maxTries) throw e;\n        }\n      }\n      return true;\n    }, NUM_OF_THREADS);\n    fakeApp.setKeepExecutingForSeconds(30);\n    Thread t = new Thread(fakeApp);\n    t.start();\n\n    while (executedCommands.get() == 0) {\n      // Wait for fake app to start\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        throw new RuntimeException(e);\n      }\n    }\n    log.info(\"Fake app started.\");\n\n    HashMap<String, Object> params = new HashMap<>();\n    params.put(\"bdb_id\", endpoint.getBdbId());\n    params.put(\"delay\", NETWORK_FAILURE_INTERVAL);\n\n    FaultInjectionClient.TriggerActionResponse actionResponse = null;\n\n    try {\n      log.info(\"Triggering network_failure for ~{} seconds\", NETWORK_FAILURE_INTERVAL);\n      actionResponse = faultClient.triggerAction(\"network_failure\", params);\n    } catch (IOException e) {\n      fail(\"Fault Injection Server error:\" + e.getMessage());\n    }\n\n    log.info(\"Action id: {}\", actionResponse.getActionId());\n    fakeApp.setAction(actionResponse);\n\n    try {\n      t.join();\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n\n    MultiDbConnectionProvider provider = ClientTestUtil.getConnectionProvider(client);\n    ConnectionPool pool1 = provider.getDatabase(endpoint.getHostAndPort(0)).getConnectionPool();\n    ConnectionPool pool2 = provider.getDatabase(endpoint.getHostAndPort(1)).getConnectionPool();\n\n    await().atMost(Duration.ofSeconds(1)).until(() -> pool1.getNumActive() == 0);\n    await().atMost(Duration.ofSeconds(1)).until(() -> pool2.getNumActive() == 0);\n\n    log.info(\"Connection pool {}: active: {}, idle: {}\", endpoint.getHostAndPort(0),\n      pool1.getNumActive(), pool1.getNumIdle());\n    log.info(\"Connection pool {}: active: {}, idle: {}\", endpoint.getHostAndPort(1),\n      pool2.getNumActive(), pool2.getNumIdle());\n    log.info(\"Failover happened at: {}\", reporter.failoverAt);\n    log.info(\"Failback happened at: {}\", reporter.failbackAt);\n    log.info(\"Last failed command at: {}\", lastFailedCommandAt.get());\n    log.info(\"Failed commands after failover: {}\", failedCommandsAfterFailover.get());\n    if (lastFailedCommandAt.get() == null) {\n      log.info(\"No failed commands after failover!\");\n    } else {\n      Duration fullFailoverTime = Duration.between(reporter.failoverAt, lastFailedCommandAt.get());\n      log.info(\"Full failover time: {} s\", fullFailoverTime.getSeconds());\n    }\n    assertEquals(0, pool1.getNumActive());\n    assertTrue(fakeApp.capturedExceptions().isEmpty());\n    assertTrue(reporter.failoverHappened);\n    assertTrue(reporter.failbackHappened);\n    assertThat(Duration.between(reporter.failoverAt, reporter.failbackAt).getSeconds(),\n      greaterThanOrEqualTo(NETWORK_FAILURE_INTERVAL));\n\n    client.close();\n  }\n\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/ClusterTopologyRefreshIT.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tags;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Tag;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.providers.ClusterConnectionProvider;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.is;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.Mockito.*;\n\n@Tags({ @Tag(\"scenario\") })\npublic class ClusterTopologyRefreshIT {\n\n  private static final Logger log = LoggerFactory.getLogger(ClusterTopologyRefreshIT.class);\n\n  private static EndpointConfig endpoint;\n\n  private final FaultInjectionClient faultClient = new FaultInjectionClient();\n\n  @BeforeAll\n  public static void beforeClass() {\n    try {\n      ClusterTopologyRefreshIT.endpoint = Endpoints.getRedisEndpoint(\"re-single-shard-oss-cluster\");\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false);\n    }\n  }\n\n  @Test\n  public void testWithPool() {\n    Set<HostAndPort> jedisClusterNode = new HashSet<>();\n    jedisClusterNode.add(endpoint.getHostAndPort());\n\n    JedisClientConfig config = endpoint.getClientConfigBuilder()\n        .socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS)\n        .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build();\n\n    try (RedisClusterClient client = RedisClusterClient.builder().nodes(jedisClusterNode)\n        .clientConfig(config).maxAttempts(RecommendedSettings.MAX_RETRIES)\n        .maxTotalRetriesDuration(RecommendedSettings.MAX_TOTAL_RETRIES_DURATION).build()) {\n      Set<String> initialNodes = client.getClusterNodes().keySet();\n      assertEquals(1, initialNodes.size(), \"Was this BDB used to run this test before?\");\n\n      AtomicLong commandsExecuted = new AtomicLong();\n\n      // Start thread that imitates an application that uses the client\n      FakeApp fakeApp = new FakeApp(client, (UnifiedJedis c) -> {\n        long i = commandsExecuted.getAndIncrement();\n        client.set(String.valueOf(i), String.valueOf(i));\n        return true;\n      });\n\n      Thread t = new Thread(fakeApp);\n      t.start();\n\n      HashMap<String, Object> params = new HashMap<>();\n      params.put(\"bdb_id\", endpoint.getBdbId());\n      params.put(\"actions\", \"[\\\"reshard\\\",\\\"failover\\\"]\");\n\n      FaultInjectionClient.TriggerActionResponse actionResponse = null;\n\n      try {\n        log.info(\"Triggering Resharding and Failover\");\n        actionResponse = faultClient.triggerAction(\"sequence_of_actions\", params);\n      } catch (IOException e) {\n        fail(\"Fault Injection Server error:\" + e.getMessage());\n      }\n\n      log.info(\"Action id: {}\", actionResponse.getActionId());\n      fakeApp.setAction(actionResponse);\n\n      try {\n        t.join();\n      } catch (InterruptedException e) {\n        throw new RuntimeException(e);\n      }\n\n      assertTrue(fakeApp.capturedExceptions().isEmpty());\n\n      log.info(\"Commands executed: {}\", commandsExecuted.get());\n      for (long i = 0; i < commandsExecuted.get(); i++) {\n        assertTrue(client.exists(String.valueOf(i)));\n      }\n\n      Set<String> afterReshardNodes = client.getClusterNodes().keySet();\n      assertThat(\"After set should have more nodes than initial set\", afterReshardNodes.size(),\n        greaterThan(initialNodes.size()));\n\n      boolean hasNewNode = afterReshardNodes.stream().anyMatch(n -> !initialNodes.contains(n));\n      assertThat(\"After set should have a node not in initial set\", hasNewNode, is(true));\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/ConnectionInterruptionIT.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Tags;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.providers.ConnectionProvider;\nimport redis.clients.jedis.providers.PooledConnectionProvider;\nimport redis.clients.jedis.util.SafeEncoder;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@Tags({ @Tag(\"scenario\") })\npublic class ConnectionInterruptionIT {\n\n  private static final Logger log = LoggerFactory.getLogger(ConnectionInterruptionIT.class);\n\n  private static EndpointConfig endpoint;\n\n  private final FaultInjectionClient faultClient = new FaultInjectionClient();\n\n  @BeforeAll\n  public static void beforeClass() {\n    try {\n      ConnectionInterruptionIT.endpoint = Endpoints.getRedisEndpoint(\"re-standalone\");\n    } catch (IllegalArgumentException e) {\n      log.warn(\"Skipping test because no Redis endpoint is configured\");\n      assumeTrue(false);\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = { \"dmc_restart\", \"network_failure\" })\n  public void testWithPool(String triggerAction) {\n    ConnectionProvider connectionProvider = new PooledConnectionProvider(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().build(), RecommendedSettings.poolConfig);\n\n    UnifiedJedis client = new UnifiedJedis(connectionProvider, RecommendedSettings.MAX_RETRIES,\n        RecommendedSettings.MAX_TOTAL_RETRIES_DURATION);\n    String keyName = \"counter\";\n    client.set(keyName, \"0\");\n    assertEquals(\"0\", client.get(keyName));\n\n    AtomicLong commandsExecuted = new AtomicLong();\n\n    // Start thread that imitates an application that uses the client\n    FakeApp fakeApp = new FakeApp(client, (UnifiedJedis c) -> {\n      assertTrue(client.incr(keyName) > 0);\n      long currentCount = commandsExecuted.getAndIncrement();\n      log.info(\"Command executed {}\", currentCount);\n      return true;\n    });\n    fakeApp.setKeepExecutingForSeconds(RecommendedSettings.DEFAULT_TIMEOUT_MS / 1000 * 2);\n    Thread t = new Thread(fakeApp);\n    t.start();\n\n    HashMap<String, Object> params = new HashMap<>();\n    params.put(\"bdb_id\", endpoint.getBdbId());\n\n    FaultInjectionClient.TriggerActionResponse actionResponse = null;\n\n    try {\n      log.info(\"Triggering {}\", triggerAction);\n      actionResponse = faultClient.triggerAction(triggerAction, params);\n    } catch (IOException e) {\n      fail(\"Fault Injection Server error:\" + e.getMessage());\n    }\n\n    log.info(\"Action id: {}\", actionResponse.getActionId());\n    fakeApp.setAction(actionResponse);\n\n    try {\n      t.join();\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n\n    log.info(\"Commands executed: {}\", commandsExecuted.get());\n    assertEquals(commandsExecuted.get(), Long.parseLong(client.get(keyName)));\n    assertTrue(fakeApp.capturedExceptions().isEmpty());\n\n    client.close();\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = { \"dmc_restart\", \"network_failure\" })\n  public void testWithPubSub(String triggerAction) {\n    ConnectionProvider connectionProvider = new PooledConnectionProvider(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().build(), RecommendedSettings.poolConfig);\n\n    UnifiedJedis client = new UnifiedJedis(connectionProvider, RecommendedSettings.MAX_RETRIES,\n        RecommendedSettings.MAX_TOTAL_RETRIES_DURATION);\n\n    AtomicLong messagesSent = new AtomicLong();\n    AtomicLong messagesReceived = new AtomicLong();\n\n    final Thread subscriberThread = getSubscriberThread(messagesReceived, connectionProvider);\n\n    // Start thread that imitates a publisher that uses the client\n    FakeApp fakeApp = new FakeApp(client, (UnifiedJedis c) -> {\n      log.info(\"Publishing message\");\n      long consumed = client.publish(\"test\", String.valueOf(messagesSent.getAndIncrement()));\n      return consumed > 0;\n    });\n    fakeApp.setKeepExecutingForSeconds(10);\n    Thread t = new Thread(fakeApp);\n    t.start();\n\n    HashMap<String, Object> params = new HashMap<>();\n    params.put(\"bdb_id\", endpoint.getBdbId());\n\n    FaultInjectionClient.TriggerActionResponse actionResponse = null;\n\n    try {\n      log.info(\"Triggering {}\", triggerAction);\n      actionResponse = faultClient.triggerAction(triggerAction, params);\n    } catch (IOException e) {\n      fail(\"Fault Injection Server error:\" + e.getMessage());\n    }\n\n    log.info(\"Action id: {}\", actionResponse.getActionId());\n    fakeApp.setAction(actionResponse);\n\n    try {\n      t.join();\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n\n    if (subscriberThread.isAlive()) subscriberThread.interrupt();\n\n    assertEquals(messagesSent.get() - 1, messagesReceived.get());\n    assertTrue(fakeApp.capturedExceptions().isEmpty());\n\n    client.close();\n  }\n\n  private static Thread getSubscriberThread(AtomicLong messagesReceived,\n      ConnectionProvider connectionProvider) {\n    final JedisPubSubBase<String> pubSub = new JedisPubSubBase<String>() {\n\n      @Override\n      public void onMessage(String channel, String message) {\n        messagesReceived.incrementAndGet();\n        log.info(\"Received message: {}\", message);\n      }\n\n      @Override\n      protected String encode(byte[] raw) {\n        return SafeEncoder.encode(raw);\n      }\n    };\n\n    final Thread subscriberThread = new Thread(() -> {\n      try {\n        pubSub.proceed(connectionProvider.getConnection(), \"test\");\n        fail(\"PubSub should have been interrupted\");\n      } catch (JedisConnectionException e) {\n        log.info(\"Expected exception in Subscriber: {}\", e.getMessage());\n        assertTrue(e.getMessage().contains(\"Unexpected end of stream.\"));\n      }\n    });\n    subscriberThread.start();\n    return subscriberThread;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/FakeApp.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\nimport redis.clients.jedis.exceptions.JedisException;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class FakeApp implements Runnable {\n\n  protected static final Logger log = LoggerFactory.getLogger(FakeApp.class);\n\n  public void setKeepExecutingForSeconds(int keepExecutingForSeconds) {\n    this.keepExecutingForSeconds = keepExecutingForSeconds;\n  }\n\n  protected int keepExecutingForSeconds = 60;\n\n  protected FaultInjectionClient.TriggerActionResponse actionResponse = null;\n  protected final UnifiedJedis client;\n  protected final ExecutedAction action;\n  protected List<JedisException> exceptions = new ArrayList<>();\n\n  @FunctionalInterface\n  public interface ExecutedAction {\n    boolean run(UnifiedJedis client);\n  }\n\n  public FakeApp(UnifiedJedis client, ExecutedAction action) {\n    this.client = client;\n    this.action = action;\n  }\n\n  public void setAction(FaultInjectionClient.TriggerActionResponse actionResponse) {\n    this.actionResponse = actionResponse;\n  }\n\n  public List<JedisException> capturedExceptions() {\n    return exceptions;\n  }\n\n  public void run() {\n    log.info(\"Starting FakeApp\");\n\n    int checkEachSeconds = 5;\n    int timeoutSeconds = 120;\n\n    while (actionResponse == null || !actionResponse.isCompleted(\n        Duration.ofSeconds(checkEachSeconds), Duration.ofSeconds(keepExecutingForSeconds),\n        Duration.ofSeconds(timeoutSeconds))) {\n      try {\n        boolean success = action.run(client);\n\n        if (!success) break;\n      } catch (JedisConnectionException e) {\n        log.error(\"Error executing action\", e);\n        exceptions.add(e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/FaultInjectionClient.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\nimport com.google.gson.FieldNamingPolicy;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.annotations.Expose;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.client5.http.fluent.Request;\nimport com.google.gson.Gson;\nimport org.apache.hc.client5.http.fluent.Response;\nimport org.apache.hc.client5.http.impl.classic.CloseableHttpClient;\nimport org.apache.hc.client5.http.impl.classic.HttpClientBuilder;\nimport org.apache.hc.core5.http.ContentType;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class FaultInjectionClient {\n\n  private static final String BASE_URL;\n\n  static final int CONNECTION_REQUEST_TIMEOUT = 3000;\n  static final int RESPONSE_TIMEOUT = 3000;\n\n  static {\n    BASE_URL = System.getenv().getOrDefault(\"FAULT_INJECTION_API_URL\", \"http://127.0.0.1:20324\");\n  }\n\n  private static final Logger log = LoggerFactory.getLogger(FaultInjectionClient.class);\n\n  public static class TriggerActionResponse {\n    @Expose\n    private final String actionId;\n\n    private Instant lastRequestTime = null;\n\n    private Instant completedAt = null;\n\n    private Instant firstRequestAt = null;\n\n    public TriggerActionResponse(String actionId) {\n      this.actionId = actionId;\n    }\n\n    public String getActionId() {\n      return actionId;\n    }\n\n    public boolean isCompleted(Duration checkInterval, Duration delayAfter, Duration timeout) {\n      if (completedAt != null) {\n        return Duration.between(completedAt, Instant.now()).compareTo(delayAfter) >= 0;\n      }\n\n      if (firstRequestAt != null && Duration.between(firstRequestAt, Instant.now())\n          .compareTo(timeout) >= 0) {\n        throw new RuntimeException(\"Timeout\");\n      }\n\n      if (lastRequestTime == null || Duration.between(lastRequestTime, Instant.now())\n          .compareTo(checkInterval) >= 0) {\n        lastRequestTime = Instant.now();\n\n        if (firstRequestAt == null) {\n          firstRequestAt = lastRequestTime;\n        }\n\n        CloseableHttpClient httpClient = getHttpClient();\n\n        Request request = Request.get(BASE_URL + \"/action/\" + actionId);\n\n        try {\n          Response response = request.execute(httpClient);\n          String result = response.returnContent().asString();\n\n          log.info(\"Action status: {}\", result);\n\n          if (result.contains(\"success\")) {\n            completedAt = Instant.now();\n            return Duration.between(completedAt, Instant.now()).compareTo(delayAfter) >= 0;\n          }\n\n        } catch (IOException e) {\n          throw new RuntimeException(\"Fault injection proxy error \", e);\n        }\n      }\n      return false;\n    }\n  }\n\n  private static CloseableHttpClient getHttpClient() {\n    RequestConfig requestConfig = RequestConfig.custom()\n        .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)\n        .setResponseTimeout(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS).build();\n\n    return HttpClientBuilder.create()\n        .setDefaultRequestConfig(requestConfig).build();\n  }\n\n  public TriggerActionResponse triggerAction(String actionType, HashMap<String, Object> parameters)\n      throws IOException {\n    Gson gson = new GsonBuilder()\n        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)\n        .excludeFieldsWithoutExposeAnnotation()\n        .create();\n\n    HashMap<String, Object> payload = new HashMap<>();\n    payload.put(\"type\", actionType);\n    payload.put(\"parameters\", parameters);\n\n    String jsonString = gson.toJson(payload);\n\n    CloseableHttpClient httpClient = getHttpClient();\n    Request request = Request.post(BASE_URL + \"/action\");\n    request.bodyString(jsonString, ContentType.APPLICATION_JSON);\n\n    try {\n      String result = request.execute(httpClient).returnContent().asString();\n      return gson.fromJson(result, new TypeToken<TriggerActionResponse>() {\n      }.getType());\n    } catch (IOException e) {\n      e.printStackTrace();\n      throw e;\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/LagAwareStrategySslIT.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Tags;\nimport org.junit.jupiter.api.Test;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport redis.clients.jedis.DefaultRedisCredentials;\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisCredentials;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.SslVerifyMode;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.mcf.HealthStatus;\nimport redis.clients.jedis.mcf.LagAwareStrategy;\nimport redis.clients.jedis.util.TlsUtil;\n\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport java.io.FileOutputStream;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.KeyStore;\nimport java.security.cert.Certificate;\nimport java.security.cert.X509Certificate;\nimport java.util.function.Supplier;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n/**\n * Test class demonstrating SSL configuration for LagAwareStrategy\n */\n@Tags({ @Tag(\"scenario\") })\npublic class LagAwareStrategySslIT {\n  private static EndpointConfig crdb;\n  private static Endpoint restEndpoint;\n  private static Supplier<RedisCredentials> credentialsSupplier;\n  private static final String certificateAlias = \"redis-enterprise\";\n  private static final char[] trustStorePassword = \"changeit\".toCharArray();\n\n  // truststore with certificate from REST API endpoint\n  private static final String trustStorePfx = LagAwareStrategySslIT.class.getSimpleName();\n  private static final Path crdbTrustStore = Paths.get(trustStorePfx + \"-crdb.jks\");\n  // empty truststore to test untrusted cert\n  private static final Path dummyTrustStore = Paths.get(trustStorePfx + \"-dummy.jks\");\n\n  private SSLSocketFactory origSslSocketFactory;\n\n  @BeforeAll\n  public static void beforeClass() {\n\n    crdb = Endpoints.getRedisEndpoint(\"re-active-active\");\n    restEndpoint = RestEndpointUtil.getRestAPIEndpoint(crdb);\n    credentialsSupplier = () -> new DefaultRedisCredentials(\"test@redis.com\", \"test123\");\n    try {\n      TlsUtil.createEmptyTruststore(dummyTrustStore, trustStorePassword);\n      createTrustedTruststore(crdbTrustStore, restEndpoint.getHost(), restEndpoint.getPort(),\n        certificateAlias, trustStorePassword);\n    } catch (Exception e) {\n      fail(\"Failed to create test truststore\", e);\n    }\n  }\n\n  @BeforeEach\n  public void enforceEmptyDefaultTrustStore() {\n    TlsUtil.setCustomTrustStore(dummyTrustStore, new String(trustStorePassword));\n    origSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();\n  }\n\n  @AfterEach\n  public void restoreDefaultTrustStore() {\n    TlsUtil.restoreOriginalTrustStore();\n    HttpsURLConnection.setDefaultSSLSocketFactory(origSslSocketFactory);\n  }\n\n  @ParameterizedTest(name = \"SSL mode {0} should be HEALTHY\")\n  @EnumSource(value = SslVerifyMode.class, names = { \"FULL\", \"CA\", \"INSECURE\" })\n  public void healthyWhenUsingCustomTruststore(SslVerifyMode sslVerifyMode) throws Exception {\n    // Create SSL options with custom truststore\n    SslOptions sslOptions = SslOptions.builder()\n        .truststore(crdbTrustStore.toFile(), trustStorePassword).sslVerifyMode(sslVerifyMode)\n        .build();\n\n    // Create LagAwareStrategy config with SSL support using builder\n    LagAwareStrategy.Config config = LagAwareStrategy.Config\n        .builder(restEndpoint, credentialsSupplier).sslOptions(sslOptions).build();\n\n    try (LagAwareStrategy lagAwareStrategy = new LagAwareStrategy(config)) {\n      assertEquals(HealthStatus.HEALTHY, lagAwareStrategy.doHealthCheck(crdb.getHostAndPort()));\n    }\n  }\n\n  @Test\n  void usingDefaultTruststoreWithUntrustedCertificateInsecure() {\n    // Create SSL options without specifying truststore\n    SslOptions sslOptions = SslOptions.builder().sslVerifyMode(SslVerifyMode.INSECURE).build();\n\n    // Create LagAwareStrategy config with SSL support using builder\n    LagAwareStrategy.Config config = LagAwareStrategy.Config\n        .builder(restEndpoint, credentialsSupplier).sslOptions(sslOptions).build();\n\n    try (LagAwareStrategy lagAwareStrategy = new LagAwareStrategy(config)) {\n      assertEquals(HealthStatus.HEALTHY, lagAwareStrategy.doHealthCheck(crdb.getHostAndPort()));\n    }\n  }\n\n  @ParameterizedTest(name = \"SSL mode {0} should result in {1}\")\n  @CsvSource({ \"FULL, UNHEALTHY\", \"CA, UNHEALTHY\" })\n  void usingDefaultTruststoreWithUntrustedCertificateThrowsException(SslVerifyMode sslVerifyMode,\n      HealthStatus expected) {\n    // Create SSL options without specifying truststore\n    SslOptions sslOptions = SslOptions.builder().sslVerifyMode(sslVerifyMode).build();\n\n    // Create LagAwareStrategy config with SSL support using builder\n    LagAwareStrategy.Config config = LagAwareStrategy.Config\n        .builder(restEndpoint, credentialsSupplier).sslOptions(sslOptions).build();\n\n    try (LagAwareStrategy lagAwareStrategy = new LagAwareStrategy(config)) {\n      JedisException ex = assertThrows(JedisException.class, () -> {\n        lagAwareStrategy.doHealthCheck(crdb.getHostAndPort());\n      });\n    }\n  }\n\n  @ParameterizedTest(name = \"SSL mode {0} should result in {1}\")\n  @CsvSource({ \"FULL, HEALTHY\", \"CA, HEALTHY\", \"INSECURE, HEALTHY\" })\n  void healthyWhenUsingDefaultTruststoreWithTrustedCertificate(SslVerifyMode sslVerifyMode,\n      HealthStatus expected) {\n    // Create SSL options without specifying truststore\n    SslOptions sslOptions = SslOptions.builder().sslVerifyMode(sslVerifyMode).build();\n\n    // Create LagAwareStrategy config with SSL support using builder\n    LagAwareStrategy.Config config = LagAwareStrategy.Config\n        .builder(restEndpoint, credentialsSupplier).sslOptions(sslOptions).build();\n\n    // Verify configuration - trusted cert should pass\n    // Default SSL context is used if no user defined trust store is configured\n    // Configure default SSL context to trust our custom CA\n    // and reinitialize default SSL context for HttpsURLConnection\n    TlsUtil.setCustomTrustStore(crdbTrustStore, new String(trustStorePassword));\n    SSLSocketFactory orig = HttpsURLConnection.getDefaultSSLSocketFactory();\n    try (LagAwareStrategy lagAwareStrategy = new LagAwareStrategy(config)) {\n      HttpsURLConnection\n          .setDefaultSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());\n      assertEquals(expected, lagAwareStrategy.doHealthCheck(crdb.getHostAndPort()));\n    } finally {\n      TlsUtil.restoreOriginalTrustStore();\n      HttpsURLConnection.setDefaultSSLSocketFactory(orig);\n    }\n  }\n\n  @Test\n  public void fallbackToDefaultSslContextWhenSslOptionsAreNull() {\n    // Test that SSL options can be null\n    // If SSL options are null, we should fallback to default SSL context\n    LagAwareStrategy.Config config = LagAwareStrategy.Config\n        .builder(restEndpoint, credentialsSupplier).build();\n\n    // Verify configuration - untrusted cert should fail\n    TlsUtil.setCustomTrustStore(dummyTrustStore, new String(trustStorePassword));\n    SSLSocketFactory orig = HttpsURLConnection.getDefaultSSLSocketFactory();\n    try (LagAwareStrategy lagAwareStrategy = new LagAwareStrategy(config)) {\n      assertThrows(JedisException.class, () -> {\n        lagAwareStrategy.doHealthCheck(crdb.getHostAndPort());\n      });\n    } finally {\n      TlsUtil.restoreOriginalTrustStore();\n      HttpsURLConnection.setDefaultSSLSocketFactory(orig);\n    }\n\n    // Verify configuration - trusted cert should pass\n    // Default SSL context is used if no SSL options are provided\n    // Configure default SSL context to trust our custom CA\n    // and reinitialize default SSL context for HttpsURLConnection\n    TlsUtil.setCustomTrustStore(crdbTrustStore, new String(trustStorePassword));\n    orig = HttpsURLConnection.getDefaultSSLSocketFactory();\n    try (LagAwareStrategy lagAwareStrategy = new LagAwareStrategy(config)) {\n      HttpsURLConnection\n          .setDefaultSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());\n      assertEquals(HealthStatus.HEALTHY, lagAwareStrategy.doHealthCheck(crdb.getHostAndPort()));\n    } finally {\n      TlsUtil.restoreOriginalTrustStore();\n      HttpsURLConnection.setDefaultSSLSocketFactory(orig);\n    }\n  }\n\n  /**\n   * Downloads the server certificate from a host and stores it as a JKS file.\n   * @param jks path where the JKS file will be created\n   * @param host host to connect to (e.g., redis.example.com)\n   * @param port TLS port (e.g., 9443)\n   * @param alias alias to store the certificate under\n   * @param password password for the JKS\n   */\n  static void createTrustedTruststore(Path jks, String host, int port, String alias,\n      char[] password) throws Exception {\n    // Use SSL socket to fetch server cert\n    // Disable certificate verification\n    TrustManager[] trustAll = new TrustManager[] { new X509TrustManager() {\n      public void checkClientTrusted(X509Certificate[] chain, String authType) {\n      }\n\n      public void checkServerTrusted(X509Certificate[] chain, String authType) {\n      }\n\n      public X509Certificate[] getAcceptedIssuers() {\n        return new X509Certificate[0];\n      }\n    } };\n    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n    sslContext.init(null, trustAll, new java.security.SecureRandom());\n    SSLSocketFactory factory = sslContext.getSocketFactory();\n\n    try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {\n      socket.startHandshake();\n      Certificate[] serverCerts = socket.getSession().getPeerCertificates();\n\n      // Create an empty KeyStore\n      KeyStore ks = KeyStore.getInstance(\"JKS\");\n      ks.load(null, password);\n\n      // Add server certificate (first in chain)\n      ks.setCertificateEntry(alias, serverCerts[0]);\n\n      // Write KeyStore to disk\n      try (FileOutputStream fos = new FileOutputStream(jks.toFile())) {\n        ks.store(fos, password);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/MultiThreadedFakeApp.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport io.github.resilience4j.ratelimiter.RateLimiter;\nimport io.github.resilience4j.ratelimiter.RateLimiterConfig;\nimport io.github.resilience4j.ratelimiter.RateLimiterRegistry;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\nimport java.time.Duration;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\npublic class MultiThreadedFakeApp extends FakeApp {\n\n  static final int QUEUE_CAPACITY = 100000;\n\n  private final ExecutorService executorService;\n  private final RateLimiter rateLimiter;\n\n  public MultiThreadedFakeApp(UnifiedJedis client, FakeApp.ExecutedAction action, int numThreads) {\n    this(client, action, numThreads, null);\n  }\n\n  public MultiThreadedFakeApp(UnifiedJedis client, FakeApp.ExecutedAction action, int numThreads, RateLimiterConfig config) {\n    super(client, action);\n    this.executorService = new ThreadPoolExecutor(\n        numThreads,\n        numThreads,\n        0L,\n        TimeUnit.MILLISECONDS,\n        new LinkedBlockingQueue<Runnable>(QUEUE_CAPACITY),\n        new ThreadPoolExecutor.CallerRunsPolicy()\n    );\n    if (config != null) {\n      this.rateLimiter = RateLimiterRegistry.of(config).rateLimiter(\"fakeAppLimiter\");\n    } else {\n      this.rateLimiter = null;\n    }\n  }\n\n  @Override\n  public void run() {\n    log.info(\"Starting FakeApp\");\n\n    int checkEachSeconds = 5;\n    int timeoutSeconds = 120;\n\n    while (actionResponse == null || !actionResponse.isCompleted(\n        Duration.ofSeconds(checkEachSeconds), Duration.ofSeconds(keepExecutingForSeconds),\n        Duration.ofSeconds(timeoutSeconds))) {\n      try {\n        if (rateLimiter != null) {\n          RateLimiter.waitForPermission(rateLimiter);\n        }\n        executorService.submit(() -> action.run(client));\n      } catch (JedisConnectionException e) {\n        log.error(\"Error executing action\", e);\n        exceptions.add(e);\n      }\n    }\n\n    executorService.shutdown();\n\n    try {\n      if (!executorService.awaitTermination(keepExecutingForSeconds, TimeUnit.SECONDS)) {\n        executorService.shutdownNow();\n      }\n    } catch (InterruptedException e) {\n      log.error(\"Error waiting for executor service to terminate\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/RecommendedSettings.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport redis.clients.jedis.ConnectionPoolConfig;\n\nimport java.time.Duration;\n\npublic class RecommendedSettings {\n\n  public static ConnectionPoolConfig poolConfig;\n\n  static {\n    poolConfig = new ConnectionPoolConfig();\n    ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();\n    poolConfig.setMaxTotal(8);\n    poolConfig.setMaxIdle(8);\n    poolConfig.setMinIdle(0);\n    poolConfig.setBlockWhenExhausted(true);\n    poolConfig.setMaxWait(Duration.ofSeconds(1));\n    poolConfig.setTestWhileIdle(true);\n    poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(1));\n  }\n\n  public static int MAX_RETRIES = 5;\n\n  public static Duration MAX_TOTAL_RETRIES_DURATION = Duration.ofSeconds(10);\n\n  public static int DEFAULT_TIMEOUT_MS = 5000;\n\n\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/scenario/RestEndpointUtil.java",
    "content": "package redis.clients.jedis.scenario;\n\nimport redis.clients.jedis.Endpoint;\nimport redis.clients.jedis.EndpointConfig;\n\npublic class RestEndpointUtil {\n\n  public static Endpoint getRestAPIEndpoint(EndpointConfig config) {\n    return new Endpoint() {\n      @Override\n      public String getHost() {\n        // convert this to Redis FQDN by removing the node prefix\n        // \"dns\":\"redis-10232.c1.taki-active-active-test-c114170a.cto.redislabs.com\"\n        String host = config.getHost();\n        // trim until the first dot\n        return host.substring(host.indexOf('.') + 1);\n      }\n\n      @Override\n      public int getPort() {\n        // default port for REST API\n        return 9443;\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/search/hybrid/FTHybridPostProcessingParamsTest.java",
    "content": "package redis.clients.jedis.search.hybrid;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.args.RawableFactory;\nimport redis.clients.jedis.search.Apply;\nimport redis.clients.jedis.search.Filter;\nimport redis.clients.jedis.search.Limit;\nimport redis.clients.jedis.search.SearchProtocol;\nimport redis.clients.jedis.search.aggr.Group;\nimport redis.clients.jedis.search.aggr.Reducers;\nimport redis.clients.jedis.search.aggr.SortedField;\n\nimport java.util.Iterator;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static redis.clients.jedis.search.SearchProtocol.SearchKeyword.LOAD;\n\npublic class FTHybridPostProcessingParamsTest {\n\n  private FTHybridPostProcessingParams.Builder builder;\n\n  @BeforeEach\n  void setUp() {\n    builder = FTHybridPostProcessingParams.builder();\n  }\n\n  @Nested\n  class EqualityAndHashCodeTests {\n\n    @Test\n    public void equalsWithIdenticalParams() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\", \"field2\").build();\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field1\", \"field2\").build();\n      assertEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void hashCodeWithIdenticalParams() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\", \"field2\").build();\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field1\", \"field2\").build();\n      assertEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void equalsWithDifferentLoadFields() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\").build();\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field2\").build();\n      assertNotEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void hashCodeWithDifferentLoadFields() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\").build();\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field2\").build();\n      assertNotEquals(firstParam.hashCode(), secondParam.hashCode());\n    }\n\n    @Test\n    public void equalsWithNull() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\").build();\n      FTHybridPostProcessingParams secondParam = null;\n      assertFalse(firstParam.equals(secondParam));\n    }\n\n    @Test\n    public void equalsWithSameInstance() {\n      FTHybridPostProcessingParams param = builder.load(\"field1\").build();\n      assertTrue(param.equals(param));\n    }\n\n    @Test\n    public void equalsLoadAllVsLoadFields() {\n      FTHybridPostProcessingParams firstParam = builder.loadAll().build();\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field1\").build();\n      assertNotEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void equalsWithDifferentLimit() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\").limit(Limit.of(0, 10))\n          .build();\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field1\").limit(Limit.of(0, 20)).build();\n      assertNotEquals(firstParam, secondParam);\n    }\n  }\n\n  @Nested\n  class LoadValidationTests {\n\n    @Test\n    public void loadNullThrowsException() {\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n        () -> builder.load((String[]) null));\n      assertEquals(\"Fields must not be null\", exception.getMessage());\n    }\n\n    @Test\n    public void loadEmptyArrayThrowsException() {\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n        () -> builder.load(new String[0]));\n      assertEquals(\"At least one field is required\", exception.getMessage());\n    }\n\n    @Test\n    public void loadWithWildcardThrowsException() {\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n        () -> builder.load(\"*\"));\n      assertEquals(\"Cannot use '*' in load(). Use loadAll() instead to load all fields.\",\n        exception.getMessage());\n    }\n\n    @Test\n    public void loadWithWildcardMixedWithFieldsThrowsException() {\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n        () -> builder.load(\"field1\", \"*\", \"field2\"));\n      assertEquals(\"Cannot use '*' in load(). Use loadAll() instead to load all fields.\",\n        exception.getMessage());\n    }\n\n    @Test\n    public void loadWithNullFieldThrowsException() {\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n        () -> builder.load(\"field1\", null, \"field2\"));\n      assertEquals(\"Field names cannot be null\", exception.getMessage());\n    }\n\n    @Test\n    public void loadAllDoesNotThrow() {\n      assertDoesNotThrow(() -> builder.loadAll());\n    }\n\n    @Test\n    public void loadWithValidFieldsDoesNotThrow() {\n      assertDoesNotThrow(() -> builder.load(\"field1\", \"field2\", \"field3\"));\n    }\n  }\n\n  @Nested\n  class BuilderTests {\n\n    @Test\n    public void lastLoadCallWins() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\").load(\"field2\").build();\n\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field2\").build();\n\n      assertEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void loadAllOverridesLoad() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\", \"field2\").loadAll().build();\n\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder().loadAll()\n          .build();\n\n      assertEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void loadOverridesLoadAll() {\n      FTHybridPostProcessingParams firstParam = builder.loadAll().load(\"field1\").build();\n\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field1\").build();\n\n      assertEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void equalsWithSameFieldsDifferentOrder() {\n      FTHybridPostProcessingParams firstParam = builder.load(\"field1\", \"field2\").build();\n\n      FTHybridPostProcessingParams secondParam = FTHybridPostProcessingParams.builder()\n          .load(\"field2\", \"field1\").build();\n\n      assertNotEquals(firstParam, secondParam);\n    }\n\n    @Test\n    public void loadWithAtPrefixPreserved() {\n      FTHybridPostProcessingParams params = builder.load(\"@field1\", \"field2\").build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Both should have @ prefix in the output\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@field1\"), iter.next());\n      assertEquals(RawableFactory.from(\"@field2\"), iter.next());\n    }\n  }\n\n  @Nested\n  class SortByTests {\n\n    @Test\n    public void sortByWithSingleField() {\n      FTHybridPostProcessingParams params = builder.sortBy(SortedField.asc(\"@price\")).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID SORTBY 2 @price ASC\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.SORTBY, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next()); // 1 field * 2 = 2\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(RawableFactory.from(\"ASC\"), iter.next());\n    }\n\n    @Test\n    public void sortByWithMultipleFields() {\n      FTHybridPostProcessingParams params = builder\n          .sortBy(SortedField.asc(\"@price\"), SortedField.desc(\"@rating\"), SortedField.asc(\"@brand\"))\n          .build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID SORTBY 6 @price ASC @rating DESC @brand ASC\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.SORTBY, iter.next());\n      assertEquals(RawableFactory.from(6), iter.next()); // 3 fields * 2 = 6\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(RawableFactory.from(\"ASC\"), iter.next());\n      assertEquals(RawableFactory.from(\"@rating\"), iter.next());\n      assertEquals(RawableFactory.from(\"DESC\"), iter.next());\n      assertEquals(RawableFactory.from(\"@brand\"), iter.next());\n      assertEquals(RawableFactory.from(\"ASC\"), iter.next());\n    }\n\n    @Test\n    public void sortByWithLoadAndLimit() {\n      FTHybridPostProcessingParams params = builder.load(\"price\", \"rating\")\n          .sortBy(SortedField.desc(\"@price\")).limit(Limit.of(0, 10)).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 2 @price @rating SORTBY 2 @price DESC LIMIT 0 10\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(RawableFactory.from(\"@rating\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.SORTBY, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(RawableFactory.from(\"DESC\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.LIMIT, iter.next());\n      assertEquals(RawableFactory.from(0), iter.next());\n      assertEquals(RawableFactory.from(10), iter.next());\n    }\n\n    @Test\n    public void lastSortByCallWins() {\n      // When sortBy is called multiple times, the last call should win\n      FTHybridPostProcessingParams params = builder.sortBy(SortedField.asc(\"@price\"))\n          .sortBy(SortedField.desc(\"@rating\")).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID SORTBY 2 @rating DESC (only the last sortBy)\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.SORTBY, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@rating\"), iter.next());\n      assertEquals(RawableFactory.from(\"DESC\"), iter.next());\n    }\n\n    @Test\n    public void lastSortByNoSortCallWins() {\n      // When both sortBy and noSort are set, noSort should take precedence\n      FTHybridPostProcessingParams params = builder.sortBy(SortedField.asc(\"@price\")).noSort()\n          .build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID NOSORT (sortBy should be ignored)\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.NOSORT, iter.next());\n      assertFalse(iter.hasNext());\n    }\n\n    @Test\n    public void lastNoSortSortByCallWins() {\n      // When both sortBy and noSort are set, noSort should take precedence\n      FTHybridPostProcessingParams params = builder.noSort().sortBy(SortedField.asc(\"@price\"))\n          .build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID NOSORT (sortBy should be ignored)\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.SORTBY, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(RawableFactory.from(\"ASC\"), iter.next());\n      assertFalse(iter.hasNext());\n    }\n\n    @Test\n    public void sortByWithEmptyArrayThrowsException() {\n      IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {\n        builder.sortBy((SortedField[]) null);\n      });\n      assertEquals(\"Sort by fields must not be null\", exception.getMessage());\n    }\n  }\n\n  @Nested\n  class AddParamsTests {\n\n    @Test\n    public void addParamsWithLoadSpecificFields() {\n      FTHybridPostProcessingParams params = builder.load(\"field1\", \"field2\").build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 2 @field1 @field2\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@field1\"), iter.next());\n      assertEquals(RawableFactory.from(\"@field2\"), iter.next());\n    }\n\n    @Test\n    public void addParamsWithLoadAll() {\n      FTHybridPostProcessingParams params = builder.loadAll().build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD *\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(\"*\"), iter.next());\n    }\n\n    @Test\n    public void addParamsWithNoLoad() {\n      FTHybridPostProcessingParams params = builder.limit(Limit.of(0, 10)).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LIMIT 0 10 (no LOAD)\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.LIMIT, iter.next());\n      assertEquals(RawableFactory.from(0), iter.next());\n      assertEquals(RawableFactory.from(10), iter.next());\n    }\n\n    @Test\n    public void addParamsWithLoadAndGroupBy() {\n      FTHybridPostProcessingParams params = builder.load(\"brand\", \"price\")\n          .groupBy(new Group(\"@brand\").reduce(Reducers.count().as(\"count\"))).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 2 @brand @price GROUPBY 1 @brand REDUCE COUNT 0 AS count\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@brand\"), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.GROUPBY, iter.next());\n      // Continue with GROUPBY assertions...\n    }\n\n    @Test\n    public void addParamsWithLoadAndApply() {\n      FTHybridPostProcessingParams params = builder.load(\"price\")\n          .apply(Apply.of(\"@price * 0.9\", \"discounted\")).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 1 @price APPLY @price * 0.9 AS discounted\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(1), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.APPLY, iter.next());\n      // Continue with APPLY assertions...\n    }\n\n    @Test\n    public void addParamsWithLoadAndSortBy() {\n      FTHybridPostProcessingParams params = builder.load(\"price\", \"rating\")\n          .sortBy(SortedField.asc(\"@price\")).build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 2 @price @rating SORTBY 2 @price ASC\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(RawableFactory.from(\"@rating\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.SORTBY, iter.next());\n      // Continue with SORTBY assertions...\n    }\n\n    @Test\n    public void addParamsWithLoadAndNoSort() {\n      FTHybridPostProcessingParams params = builder.load(\"field1\").noSort().build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 1 @field1 NOSORT\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(1), iter.next());\n      assertEquals(RawableFactory.from(\"@field1\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.NOSORT, iter.next());\n    }\n\n    @Test\n    public void addParamsWithLoadAndFilter() {\n      FTHybridPostProcessingParams params = builder.load(\"price\").filter(Filter.of(\"@price > 100\"))\n          .build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 1 @price FILTER @price > 100\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(1), iter.next());\n      assertEquals(RawableFactory.from(\"@price\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.FILTER, iter.next());\n      // Continue with FILTER assertions...\n    }\n\n    @Test\n    public void addParamsWithLoadAndLimit() {\n      FTHybridPostProcessingParams params = builder.load(\"field1\", \"field2\").limit(Limit.of(10, 20))\n          .build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Expected: FT.HYBRID LOAD 2 @field1 @field2 LIMIT 10 20\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(2), iter.next());\n      assertEquals(RawableFactory.from(\"@field1\"), iter.next());\n      assertEquals(RawableFactory.from(\"@field2\"), iter.next());\n      assertEquals(SearchProtocol.SearchKeyword.LIMIT, iter.next());\n      assertEquals(RawableFactory.from(10), iter.next());\n      assertEquals(RawableFactory.from(20), iter.next());\n    }\n\n    @Test\n    public void addParamsFieldWithoutAtPrefixGetsPrefix() {\n      FTHybridPostProcessingParams params = builder.load(\"field1\").build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Field without @ should get @ prefix\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(1), iter.next());\n      assertEquals(RawableFactory.from(\"@field1\"), iter.next());\n    }\n\n    @Test\n    public void addParamsFieldWithAtPrefixNotDuplicated() {\n      FTHybridPostProcessingParams params = builder.load(\"@field1\").build();\n\n      CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.HYBRID);\n      params.addParams(args);\n\n      // Field with @ should not get another @ prefix\n      Iterator<Rawable> iter = args.iterator();\n      assertEquals(SearchProtocol.SearchCommand.HYBRID, iter.next());\n      assertEquals(LOAD, iter.next());\n      assertEquals(RawableFactory.from(1), iter.next());\n      assertEquals(RawableFactory.from(\"@field1\"), iter.next());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ACLJedisIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.Jedis;\n\n/**\n * SSL/TLS tests for {@link Jedis} with ACL authentication (username + password).\n * <p>\n * This test class focuses on testing the ssl(true) flag approach (using system truststore) with ACL\n * credentials.\n */\n\npublic class ACLJedisIT extends JedisTlsTestBase {\n  /**\n   * Tests SSL connection with explicit ACL credentials (username + password).\n   */\n  @Test\n  public void connectWithSsl() {\n    try (Jedis jedis = new Jedis(aclEndpoint.getHost(), aclEndpoint.getPort(), true)) {\n      jedis.auth(aclEndpoint.getUsername(), aclEndpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using DefaultJedisClientConfig with ACL credentials.\n   */\n  @Test\n  public void connectWithConfig() {\n    try (Jedis jedis = new Jedis(aclEndpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().ssl(true).build())) {\n      jedis.auth(aclEndpoint.getUsername(), aclEndpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using URL with credentials.\n   */\n  @Test\n  public void connectWithUrl() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    // Test with default user endpoint\n    try (\n        Jedis jedis = new Jedis(endpoint.getURIBuilder().defaultCredentials().build().toString())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n    // Test with ACL user endpoint\n    try (Jedis jedis = new Jedis(\n        aclEndpoint.getURIBuilder().defaultCredentials().build().toString())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using URI with credentials.\n   */\n  @Test\n  public void connectWithUri() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    // Test with default user endpoint\n    try (Jedis jedis = new Jedis(endpoint.getURIBuilder().defaultCredentials().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n    // Test with ACL user endpoint\n    try (Jedis jedis = new Jedis(aclEndpoint.getURIBuilder().defaultCredentials().build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ACLRedisClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.RedisClient;\n\n/**\n * SSL/TLS tests for {@link RedisClient} with ACL authentication (username + password).\n * <p>\n * This test class focuses on testing the ssl(true) flag approach (using system truststore) with ACL\n * credentials.\n */\n\npublic class ACLRedisClientIT extends RedisClientTlsTestBase {\n  /**\n   * Tests SSL connection with explicit ACL credentials (username + password).\n   */\n  @Test\n  public void connectWithSsl() {\n    try (\n        RedisClient client = RedisClient.builder()\n            .hostAndPort(aclEndpoint.getHost(), aclEndpoint.getPort())\n            .clientConfig(DefaultJedisClientConfig.builder().ssl(true)\n                .user(aclEndpoint.getUsername()).password(aclEndpoint.getPassword()).build())\n            .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using endpoint's client config builder (credentials from endpoint).\n   */\n  @Test\n  public void connectWithConfig() {\n    try (\n        RedisClient client = RedisClient.builder().hostAndPort(aclEndpoint.getHostAndPort())\n            .clientConfig(DefaultJedisClientConfig.builder().ssl(true)\n                .user(aclEndpoint.getUsername()).password(aclEndpoint.getPassword()).build())\n            .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using URL with credentials.\n   */\n  @Test\n  public void connectWithUrl() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    // Test with default user endpoint\n    try (RedisClient client = RedisClient\n        .create(endpoint.getURIBuilder().defaultCredentials().build().toString())) {\n      assertEquals(\"PONG\", client.ping());\n    }\n    // Test with ACL user endpoint\n    try (RedisClient client = RedisClient\n        .create(aclEndpoint.getURIBuilder().defaultCredentials().build().toString())) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using URI with credentials.\n   */\n  @Test\n  public void connectWithUri() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    // Test with default user endpoint\n    try (RedisClient client = RedisClient\n        .create(endpoint.getURIBuilder().defaultCredentials().build())) {\n      assertEquals(\"PONG\", client.ping());\n    }\n    // Test with ACL user endpoint\n    try (RedisClient client = RedisClient\n        .create(aclEndpoint.getURIBuilder().defaultCredentials().build())) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ACLRedisSentinelClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisSentinelClient;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionCondition;\nimport redis.clients.jedis.util.TestEnvUtil;\n\n/**\n * SSL/TLS tests for {@link RedisSentinelClient} with ACL authentication (username + password).\n * <p>\n * This test class focuses on testing the ssl(true) flag approach (using system truststore) rather\n * than explicit SslOptions configuration.\n */\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = false)\npublic class ACLRedisSentinelClientIT extends RedisSentinelTlsTestBase {\n\n  // Endpoint for master with ACL authentication\n  private static EndpointConfig aclEndpoint;\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @RegisterExtension\n  public static RedisVersionCondition versionCondition = new RedisVersionCondition(\n      () -> Endpoints.getRedisEndpoint(\"standalone0-acl-tls\"));\n\n  @BeforeAll\n  public static void setUp() {\n    aclEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl-tls\");\n  }\n\n  /**\n   * Tests SSL connection with explicit ACL credentials (username + password).\n   */\n  @Test\n  public void connectWithSsl() {\n    DefaultJedisClientConfig masterConfig = DefaultJedisClientConfig.builder()\n        .clientName(\"master-client\").ssl(true).user(aclEndpoint.getUsername())\n        .password(aclEndpoint.getPassword()).hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = Endpoints.getRedisEndpoint(\"sentinel-standalone0-tls\")\n        .getClientConfigBuilder().clientName(\"sentinel-client\").ssl(true)\n        .hostAndPortMapper(SENTINEL_SSL_PORT_MAPPER).build();\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using endpoint's client config builder (credentials from endpoint).\n   */\n  @Test\n  public void connectWithConfig() {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").ssl(true).hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = Endpoints.getRedisEndpoint(\"sentinel-standalone0-tls\")\n        .getClientConfigBuilder().clientName(\"sentinel-client\").ssl(true)\n        .hostAndPortMapper(SENTINEL_SSL_PORT_MAPPER).build();\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests SSL connection using SslOptions with truststore configuration.\n   */\n  @Test\n  public void connectWithSslOptions() {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").sslOptions(sslOptions)\n        .hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithSsl(sslOptions);\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ClientAuthIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.UnifiedJedis;\n\n/**\n * Abstract integration test class for mTLS (mutual TLS) certificate-based authentication.\n * <p>\n * Defines common test methods that verify client certificate authentication works correctly across\n * different Redis deployment types (standalone, cluster).\n * <p>\n * Subclasses must implement factory methods to create environment-specific clients and execute\n * commands.\n */\npublic abstract class ClientAuthIT extends ClientAuthTestBase {\n\n  /**\n   * Creates a client with the specified SSL options.\n   * <p>\n   * Subclasses provide environment-specific implementations (e.g., RedisClient for standalone,\n   * RedisClusterClient for cluster).\n   * @param sslOptions SSL configuration for mTLS\n   * @return UnifiedJedis client configured with mTLS\n   */\n  protected abstract UnifiedJedis createClient(SslOptions sslOptions);\n\n  /**\n   * Executes ACL WHOAMI command and returns the authenticated username.\n   * <p>\n   * @param client the connected client\n   * @return the authenticated username\n   */\n  private String aclWhoAmI(UnifiedJedis client) {\n\n    return client.executeCommand(new CommandObject<>(\n        new CommandArguments(Protocol.Command.ACL).add(\"WHOAMI\"), BuilderFactory.STRING));\n  }\n\n  /**\n   * Tests mTLS connection with mtls-user1 certificate.\n   * <p>\n   * Verifies that ACL WHOAMI returns the expected username based on Redis version.\n   */\n  @Test\n  public void connectWithMtlsUser1() {\n    SslOptions sslOptions = createMtlsSslOptionsUser1();\n\n    try (UnifiedJedis client = createClient(sslOptions)) {\n      assertEquals(\"PONG\", client.ping());\n      assertExpectedUsername(client, aclWhoAmI(client), MTLS_USER_1);\n    }\n  }\n\n  /**\n   * Tests mTLS connection with mtls-user2 certificate.\n   * <p>\n   * Verifies that a different certificate authenticates as a different user.\n   */\n  @Test\n  public void connectWithMtlsUser2() {\n    SslOptions sslOptions = createMtlsSslOptionsUser2();\n\n    try (UnifiedJedis client = createClient(sslOptions)) {\n      assertEquals(\"PONG\", client.ping());\n      assertExpectedUsername(client, aclWhoAmI(client), MTLS_USER_2);\n    }\n  }\n\n  /**\n   * Tests mTLS connection with mtls-user-without-acl certificate.\n   * <p>\n   * Verifies that when using a certificate for a user without a corresponding ACL user configured\n   * in Redis, the connection succeeds and ACL WHOAMI returns \"default\".\n   */\n  @Test\n  public void connectWithMtlsUserWithoutAcl() {\n    SslOptions sslOptions = createMtlsSslOptionsUserWithoutAcl();\n\n    try (UnifiedJedis client = createClient(sslOptions)) {\n      assertEquals(\"PONG\", client.ping());\n      assertEquals(\"default\", aclWhoAmI(client));\n    }\n  }\n\n  /**\n   * Tests that mTLS authenticated users can perform basic Redis operations.\n   */\n  @Test\n  public void performBasicOperationsWithMtls() {\n    SslOptions sslOptions = createMtlsSslOptionsUser1();\n\n    try (UnifiedJedis client = createClient(sslOptions)) {\n      String key = \"mtls-test-key\";\n      String value = \"mtls-test-value\";\n\n      assertEquals(\"OK\", client.set(key, value));\n      assertEquals(value, client.get(key));\n      assertEquals(1, client.del(key));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ClientAuthJedisIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.SslOptions;\n\n/**\n * Integration tests for mTLS (mutual TLS) certificate-based authentication with Jedis.\n * <p>\n * These tests verify that: - Client certificate authentication works correctly with Jedis - The\n * authenticated user matches the certificate CN (Redis 8.6+) or is \"default\" (older versions) -\n * Different client certificates authenticate as different users\n */\npublic class ClientAuthJedisIT extends ClientAuthTestBase {\n\n  @BeforeAll\n  public static void setUpStandaloneMtlsStores() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone-mtls\");\n    setUpMtlsStoresForEndpoint(endpoint, ClientAuthJedisIT.class.getSimpleName());\n  }\n\n  /**\n   * Tests mTLS connection with mtls-user1 certificate using Jedis. Verifies that ACL WHOAMI returns\n   * the expected username based on Redis version.\n   */\n  @Test\n  public void connectWithMtlsUser1() {\n    SslOptions sslOptions = createMtlsSslOptionsUser1();\n\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      // Verify username based on Redis version\n      assertExpectedUsername(jedis, jedis.aclWhoAmI(), MTLS_USER_1);\n    }\n  }\n\n  /**\n   * Tests mTLS connection with mtls-user2 certificate using Jedis. Verifies that a different\n   * certificate authenticates as a different user.\n   */\n  @Test\n  public void connectWithMtlsUser2() {\n    SslOptions sslOptions = createMtlsSslOptionsUser2();\n\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      // Verify username based on Redis version\n      assertExpectedUsername(jedis, jedis.aclWhoAmI(), MTLS_USER_2);\n    }\n  }\n\n  /**\n   * Tests mTLS connection with mtls-user-without-acl certificate using Jedis. Verifies that when\n   * using a certificate for a user without a corresponding ACL user configured in Redis, the\n   * connection succeeds and ACL WHOAMI returns \"default\".\n   */\n  @Test\n  public void connectWithMtlsUserWithoutAcl() {\n    SslOptions sslOptions = createMtlsSslOptionsUserWithoutAcl();\n\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertEquals(\"default\", jedis.aclWhoAmI());\n    }\n  }\n\n  /**\n   * Tests mTLS connection using host and port separately.\n   */\n  @Test\n  public void connectWithHostAndPort() {\n    SslOptions sslOptions = createMtlsSslOptionsUser1();\n\n    try (Jedis jedis = new Jedis(endpoint.getHost(), endpoint.getPort(),\n        DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n      assertExpectedUsername(jedis, jedis.aclWhoAmI(), MTLS_USER_1);\n    }\n  }\n\n  /**\n   * Tests that mTLS authenticated users can perform basic Redis operations with Jedis.\n   */\n  @Test\n  public void performBasicOperationsWithMtls() {\n    SslOptions sslOptions = createMtlsSslOptionsUser1();\n\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())) {\n      // Test basic operations\n      String key = \"mtls-jedis-test-key\";\n      String value = \"mtls-jedis-test-value\";\n\n      assertEquals(\"OK\", jedis.set(key, value));\n      assertEquals(value, jedis.get(key));\n      assertEquals(1, jedis.del(key));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ClientAuthRedisClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport org.junit.jupiter.api.BeforeAll;\n\nimport redis.clients.jedis.*;\n\n/**\n * Integration tests for mTLS (mutual TLS) certificate-based authentication with standalone Redis.\n * <p>\n * Extends {@link ClientAuthIT} to provide standalone-specific client creation and command\n * execution.\n */\npublic class ClientAuthRedisClientIT extends ClientAuthIT {\n\n  @BeforeAll\n  public static void setUpStandaloneMtlsStores() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone-mtls\");\n    setUpMtlsStoresForEndpoint(endpoint, ClientAuthRedisClientIT.class.getSimpleName());\n  }\n\n  @Override\n  protected UnifiedJedis createClient(SslOptions sslOptions) {\n    return RedisClient.builder().hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(DefaultJedisClientConfig.builder().sslOptions(sslOptions).build()).build();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ClientAuthRedisClusterClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.*;\n\n/**\n * Integration tests for mTLS (mutual TLS) certificate-based authentication with Redis Cluster.\n * <p>\n * Extends {@link ClientAuthIT} to provide cluster-specific client creation and command execution.\n * Also includes cluster-specific tests like node discovery.\n */\npublic class ClientAuthRedisClusterClientIT extends ClientAuthIT {\n\n  private static final int DEFAULT_REDIRECTIONS = 5;\n  private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig();\n\n  @BeforeAll\n  public static void setUpClusterMtlsStores() {\n    endpoint = Endpoints.getRedisEndpoint(\"cluster-mtls\");\n    setUpMtlsStoresForEndpoint(endpoint, ClientAuthRedisClusterClientIT.class.getSimpleName());\n  }\n\n  @Override\n  protected UnifiedJedis createClient(SslOptions sslOptions) {\n    return RedisClusterClient.builder().nodes(new HashSet<>(endpoint.getHostsAndPorts()))\n        .clientConfig(DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build();\n  }\n\n  /**\n   * Cluster-specific test: Verifies that cluster node discovery works with mTLS.\n   */\n  @Test\n  public void discoverClusterNodesWithMtls() {\n    SslOptions sslOptions = createMtlsSslOptionsUser1();\n\n    try (RedisClusterClient cluster = RedisClusterClient.builder()\n        .nodes(Collections.singleton(endpoint.getHostAndPort()))\n        .clientConfig(DefaultJedisClientConfig.builder().sslOptions(sslOptions).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      Map<String, ?> clusterNodes = cluster.getClusterNodes();\n      // Should discover all 3 cluster nodes\n      assertEquals(3, clusterNodes.size());\n      assertTrue(clusterNodes.containsKey(endpoint.getHostAndPort(0).toString()));\n      assertTrue(clusterNodes.containsKey(endpoint.getHostAndPort(1).toString()));\n      assertTrue(clusterNodes.containsKey(endpoint.getHostAndPort(2).toString()));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/ClientAuthTestBase.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport io.redis.test.utils.RedisVersion;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.SslVerifyMode;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.util.EnvCondition;\nimport redis.clients.jedis.util.RedisVersionUtil;\nimport redis.clients.jedis.util.TestEnvUtil;\nimport redis.clients.jedis.util.TlsUtil;\n\n/**\n * Abstract base class for mTLS (mutual TLS) authentication tests.\n * <p>\n * This class provides common setup for tests that verify certificate-based client authentication.\n * It configures both truststore (for server verification) and keystore (for client authentication).\n * <p>\n * The mTLS setup requires: - A truststore containing the CA certificate to verify the Redis server\n * - A keystore containing the client certificate and private key for client authentication\n */\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = false)\npublic abstract class ClientAuthTestBase {\n\n  private static final String TRUSTSTORE_PASSWORD = \"changeit\";\n  private static final String KEYSTORE_PASSWORD = \"changeit\";\n\n  /** Default mTLS user for testing */\n  protected static final String MTLS_USER_1 = \"mtls-user1\";\n  protected static final String MTLS_USER_2 = \"mtls-user2\";\n  protected static final String MTLS_USER_WITHOUT_ACL = \"mtls-user-without-acl\";\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  protected static EndpointConfig endpoint;\n  protected static Path trustStorePath;\n  protected static Path keyStorePath1;\n  protected static Path keyStorePath2;\n  protected static Path keyStorePathUserWithoutAcl;\n\n  /**\n   * Sets up mTLS stores for a specific targetEndpioint. Should be called by subclasses in\n   * their @BeforeAll method.\n   * @param targetEndpioint the targetEndpioint to configure mTLS for\n   * @param testClassName the test class name for truststore naming\n   */\n  protected static void setUpMtlsStoresForEndpoint(EndpointConfig targetEndpioint,\n      String testClassName) {\n    // Create truststore with CA certificate for server verification\n    List<Path> trustedCertLocation = Collections\n        .singletonList(targetEndpioint.getCertificatesLocation());\n    trustStorePath = TlsUtil.createAndSaveTestTruststore(testClassName, trustedCertLocation,\n      TRUSTSTORE_PASSWORD);\n    TlsUtil.setCustomTrustStore(trustStorePath, TRUSTSTORE_PASSWORD);\n\n    // Use pre-generated PKCS12 keystores from Docker container\n    // The container generates .p12 files with password \"changeit\" for each TLS_CLIENT_CNS entry\n    Path certLocation = targetEndpioint.getCertificatesLocation();\n    keyStorePath1 = TlsUtil.clientKeystorePath(certLocation, MTLS_USER_1);\n    keyStorePath2 = TlsUtil.clientKeystorePath(certLocation, MTLS_USER_2);\n    keyStorePathUserWithoutAcl = TlsUtil.clientKeystorePath(certLocation, MTLS_USER_WITHOUT_ACL);\n  }\n\n  @AfterAll\n  public static void tearDownMtlsStores() {\n    TlsUtil.restoreOriginalTrustStore();\n  }\n\n  /**\n   * Creates SslOptions configured for mTLS with the specified client keystore.\n   * @param keystorePath path to the client keystore\n   * @return SslOptions configured for mTLS\n   */\n  protected static SslOptions createMtlsSslOptions(Path keystorePath) {\n    return SslOptions.builder().truststore(trustStorePath.toFile()).trustStoreType(\"jceks\")\n        .keystore(keystorePath.toFile(), KEYSTORE_PASSWORD.toCharArray()).keyStoreType(\"PKCS12\")\n        .sslVerifyMode(SslVerifyMode.FULL).build();\n  }\n\n  /**\n   * Creates SslOptions for mtls-user1.\n   */\n  protected static SslOptions createMtlsSslOptionsUser1() {\n    return createMtlsSslOptions(keyStorePath1);\n  }\n\n  /**\n   * Creates SslOptions for mtls-user2.\n   */\n  protected static SslOptions createMtlsSslOptionsUser2() {\n    return createMtlsSslOptions(keyStorePath2);\n  }\n\n  /**\n   * Creates SslOptions for mtls-user-without-acl.\n   */\n  protected static SslOptions createMtlsSslOptionsUserWithoutAcl() {\n    return createMtlsSslOptions(keyStorePathUserWithoutAcl);\n  }\n\n  /**\n   * Asserts the expected username based on Redis version.\n   * <p>\n   * Redis 8.6+ supports automatic certificate-based authentication via tls-auth-clients-user CN,\n   * where the username is extracted from the client certificate's Common Name. For Redis versions\n   * below 8.6, the user will be \"default\" since cert-based auth is not supported.\n   * @param jedis the connected Jedis client to check version\n   * @param actualUsername the actual username from ACL WHOAMI\n   * @param expectedCertUser the expected username from client certificate CN\n   */\n  protected static void assertExpectedUsername(redis.clients.jedis.Jedis jedis,\n      String actualUsername, String expectedCertUser) {\n    RedisVersion version = RedisVersionUtil.getRedisVersion(jedis);\n    assertUsernameForVersion(version, actualUsername, expectedCertUser);\n  }\n\n  /**\n   * Asserts the expected username based on Redis version for UnifiedJedis clients (RedisClient,\n   * RedisClusterClient, etc.).\n   * @param jedis the connected UnifiedJedis client to check version\n   * @param actualUsername the actual username from ACL WHOAMI\n   * @param expectedCertUser the expected username from client certificate CN\n   */\n  protected static void assertExpectedUsername(UnifiedJedis jedis, String actualUsername,\n      String expectedCertUser) {\n    RedisVersion version = RedisVersionUtil.getRedisVersion(jedis);\n    assertUsernameForVersion(version, actualUsername, expectedCertUser);\n  }\n\n  private static void assertUsernameForVersion(RedisVersion version, String actualUsername,\n      String expectedCertUser) {\n    if (version.isGreaterThanOrEqualTo(RedisVersion.V8_6_0)) {\n      assertEquals(expectedCertUser, actualUsername,\n        \"Redis \" + version + \" supports cert-based auth, expected username from certificate CN\");\n    } else {\n      List<String> allowedUsers = Arrays.asList(\"default\", expectedCertUser);\n      assertTrue(allowedUsers.contains(actualUsername),\n        \"Redis \" + version + \" does not support cert-based auth, expected 'default' or '\"\n            + expectedCertUser + \"' but was '\" + actualUsername + \"'\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/JedisIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisClientConfig;\n\n/**\n * SSL/TLS tests for {@link Jedis} with basic authentication (password-only, no ACL).\n * <p>\n * Uses the system truststore (ssl=true flag) for SSL connections.\n */\npublic class JedisIT extends JedisTlsTestBase {\n\n  @Test\n  public void connectWithSsl() {\n    try (Jedis jedis = new Jedis(endpoint.getHost(), endpoint.getPort(), true)) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void connectWithConfig() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().ssl(true).build())) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  @Test\n  public void connectWithConfigInterface() {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(), new JedisClientConfig() {\n      @Override\n      public boolean isSsl() {\n        return true;\n      }\n    })) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests opening a default SSL/TLS connection to redis using \"rediss://\" scheme url.\n   */\n  @Test\n  public void connectWithUrl() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    try (Jedis jedis = new Jedis(endpoint.getURI().toString())) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests opening a default SSL/TLS connection to redis.\n   */\n  @Test\n  public void connectWithUri() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    try (Jedis jedis = new Jedis(endpoint.getURI())) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/JedisSentinelPoolIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisSentinelPool;\n\n/**\n * SSL/TLS tests for {@link JedisSentinelPool} using system truststore (ssl=true flag).\n * <p>\n * Tests various combinations of SSL on master and sentinel connections:\n * <ul>\n * <li>Sentinel without SSL, Redis master with SSL</li>\n * <li>Sentinel with SSL, Redis master without SSL</li>\n * <li>Both sentinel and Redis master with SSL</li>\n * </ul>\n */\npublic class JedisSentinelPoolIT extends RedisSentinelTlsTestBase {\n\n  private static final GenericObjectPoolConfig<Jedis> POOL_CONFIG = new GenericObjectPoolConfig<>();\n\n  // Endpoints for different SSL configurations\n  private static EndpointConfig aclTlsEndpoint;\n  private static EndpointConfig aclEndpoint;\n  private static EndpointConfig sentinelTlsEndpoint;\n\n  @BeforeAll\n  public static void setUp() {\n    aclTlsEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl-tls\");\n    aclEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl\");\n    sentinelTlsEndpoint = Endpoints.getRedisEndpoint(\"sentinel-standalone0-tls\");\n  }\n\n  /**\n   * Tests sentinel without SSL connecting to Redis master with SSL.\n   */\n  @Test\n  public void sentinelWithoutSslConnectsToRedisWithSsl() {\n    DefaultJedisClientConfig masterConfig = aclTlsEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithoutSsl();\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, masterConfig,\n        sentinelConfig)) {\n      pool.getResource().close();\n    }\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, POOL_CONFIG,\n        masterConfig, sentinelConfig)) {\n      pool.getResource().close();\n    }\n  }\n\n  /**\n   * Tests sentinel with SSL connecting to Redis master without SSL.\n   */\n  @Test\n  public void sentinelWithSslConnectsToRedisWithoutSsl() {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").build();\n\n    DefaultJedisClientConfig sentinelConfig = sentinelTlsEndpoint.getClientConfigBuilder()\n        .clientName(\"sentinel-client\").hostAndPortMapper(SENTINEL_SSL_PORT_MAPPER).build();\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, masterConfig,\n        sentinelConfig)) {\n      pool.getResource().close();\n    }\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, POOL_CONFIG,\n        masterConfig, sentinelConfig)) {\n      pool.getResource().close();\n    }\n  }\n\n  /**\n   * Tests both sentinel and Redis master with SSL.\n   */\n  @Test\n  public void sentinelWithSslConnectsToRedisWithSsl() {\n    DefaultJedisClientConfig masterConfig = aclTlsEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = sentinelTlsEndpoint.getClientConfigBuilder()\n        .clientName(\"sentinel-client\").hostAndPortMapper(SENTINEL_SSL_PORT_MAPPER).build();\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, masterConfig,\n        sentinelConfig)) {\n      pool.getResource().close();\n    }\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, POOL_CONFIG,\n        masterConfig, sentinelConfig)) {\n      pool.getResource().close();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/JedisTlsTestBase.java",
    "content": "package redis.clients.jedis.tls;\n\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.provider.Arguments;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.SslVerifyMode;\nimport redis.clients.jedis.util.TlsUtil;\n\n/**\n * Abstract base class for SSL/TLS tests for {@link redis.clients.jedis.Jedis}.\n */\npublic abstract class JedisTlsTestBase {\n\n  private static final String TRUSTSTORE_PASSWORD = \"changeit\";\n\n  protected static EndpointConfig endpoint;\n  protected static EndpointConfig aclEndpoint;\n  protected static Path trustStorePath;\n  protected static SslOptions sslOptions;\n\n  @BeforeAll\n  public static void setUpTrustStore() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0-tls\");\n    aclEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl-tls\");\n\n    List<Path> trustedCertLocation = Arrays.asList(endpoint.getCertificatesLocation(),\n      aclEndpoint.getCertificatesLocation());\n    trustStorePath = TlsUtil.createAndSaveTestTruststore(JedisTlsTestBase.class.getSimpleName(),\n      trustedCertLocation, TRUSTSTORE_PASSWORD);\n\n    TlsUtil.setCustomTrustStore(trustStorePath, TRUSTSTORE_PASSWORD);\n    sslOptions = createTruststoreSslOptions();\n  }\n\n  @AfterAll\n  public static void tearDownTrustStore() {\n    TlsUtil.restoreOriginalTrustStore();\n  }\n\n  protected static SslOptions createTruststoreSslOptions() {\n    return SslOptions.builder().truststore(trustStorePath.toFile()).trustStoreType(\"jceks\")\n        .sslVerifyMode(SslVerifyMode.CA).build();\n  }\n\n  protected static Stream<Arguments> sslOptionsProvider() {\n    return Stream.of(Arguments.of(\"truststore\", createTruststoreSslOptions()),\n      Arguments.of(\"insecure\", SslOptions.builder().sslVerifyMode(SslVerifyMode.INSECURE).build()),\n      Arguments.of(\"ssl-protocol\",\n        SslOptions.builder().sslProtocol(\"SSL\").truststore(trustStorePath.toFile())\n            .trustStoreType(\"jceks\").sslVerifyMode(SslVerifyMode.CA).build()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/RedisClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.RedisClient;\n\n/**\n * SSL/TLS tests for {@link RedisClient} with basic authentication (password-only, no ACL).\n * <p>\n * Uses the system truststore (ssl=true flag) for SSL connections.\n */\npublic class RedisClientIT extends RedisClientTlsTestBase {\n\n  @Test\n  public void connectWithSsl() {\n    try (RedisClient client = RedisClient.builder()\n        .hostAndPort(endpoint.getHost(), endpoint.getPort())\n        .clientConfig(\n          DefaultJedisClientConfig.builder().ssl(true).password(endpoint.getPassword()).build())\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  @Test\n  public void connectWithConfig() {\n    try (RedisClient client = RedisClient.builder().hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(\n          DefaultJedisClientConfig.builder().ssl(true).password(endpoint.getPassword()).build())\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests opening a default SSL/TLS connection to redis using \"rediss://\" scheme url.\n   */\n  @Test\n  public void connectWithUrl() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    // URI includes credentials via defaultCredentials()\n    try (RedisClient client = RedisClient\n        .create(endpoint.getURIBuilder().defaultCredentials().build().toString())) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests opening a default SSL/TLS connection to redis.\n   */\n  @Test\n  public void connectWithUri() {\n    // The \"rediss\" scheme instructs jedis to open a SSL/TLS connection.\n    // URI includes credentials via defaultCredentials()\n    try (RedisClient client = RedisClient\n        .create(endpoint.getURIBuilder().defaultCredentials().build())) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/RedisClientTlsTestBase.java",
    "content": "package redis.clients.jedis.tls;\n\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.provider.Arguments;\n\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.SslVerifyMode;\nimport redis.clients.jedis.util.TlsUtil;\n\n/**\n * Abstract base class for SSL/TLS tests for {@link redis.clients.jedis.RedisClient}.\n */\npublic abstract class RedisClientTlsTestBase {\n\n  private static final String TRUSTSTORE_PASSWORD = \"changeit\";\n\n  protected static EndpointConfig endpoint;\n  protected static EndpointConfig aclEndpoint;\n  protected static Path trustStorePath;\n  protected static SslOptions sslOptions;\n\n  @BeforeAll\n  public static void setUpTrustStore() {\n    endpoint = Endpoints.getRedisEndpoint(\"standalone0-tls\");\n    aclEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl-tls\");\n\n    List<Path> trustedCertLocation = Arrays.asList(endpoint.getCertificatesLocation(),\n      aclEndpoint.getCertificatesLocation());\n    trustStorePath = TlsUtil.createAndSaveTestTruststore(\n      RedisClientTlsTestBase.class.getSimpleName(), trustedCertLocation, TRUSTSTORE_PASSWORD);\n\n    TlsUtil.setCustomTrustStore(trustStorePath, TRUSTSTORE_PASSWORD);\n    sslOptions = createTruststoreSslOptions();\n  }\n\n  @AfterAll\n  public static void tearDownTrustStore() {\n    TlsUtil.restoreOriginalTrustStore();\n  }\n\n  protected static SslOptions createTruststoreSslOptions() {\n    return SslOptions.builder().truststore(trustStorePath.toFile()).trustStoreType(\"jceks\")\n        .sslVerifyMode(SslVerifyMode.CA).build();\n  }\n\n  protected static Stream<Arguments> sslOptionsProvider() {\n    return Stream.of(Arguments.of(\"truststore\", createTruststoreSslOptions()),\n      Arguments.of(\"insecure\", SslOptions.builder().sslVerifyMode(SslVerifyMode.INSECURE).build()),\n      Arguments.of(\"ssl-protocol\",\n        SslOptions.builder().sslProtocol(\"SSL\").truststore(trustStorePath.toFile())\n            .trustStoreType(\"jceks\").sslVerifyMode(SslVerifyMode.CA).build()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/RedisClusterClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static redis.clients.jedis.util.TlsUtil.*;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.stream.Stream;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLParameters;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.TlsUtil;\nimport redis.clients.jedis.exceptions.JedisClusterOperationException;\n\npublic class RedisClusterClientIT extends RedisClusterTestBase {\n\n  private static final int DEFAULT_REDIRECTIONS = 5;\n  private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig();\n\n  /**\n   * Provides different SslOptions configurations for parametrized tests.\n   */\n  protected static Stream<Arguments> sslOptionsProvider() {\n    return Stream.of(Arguments.of(\"truststore\", createSslOptions()),\n      Arguments.of(\"insecure\", SslOptions.builder().sslVerifyMode(SslVerifyMode.INSECURE).build()),\n      Arguments.of(\"ssl-protocol\",\n        SslOptions.builder().sslProtocol(\"SSL\").truststore(trustStorePath.toFile())\n            .trustStoreType(\"jceks\").sslVerifyMode(SslVerifyMode.CA).build()));\n  }\n\n  /**\n   * Tests SSL discover nodes with various SSL configurations.\n   */\n  @ParameterizedTest(name = \"testSSLDiscoverNodesAutomatically_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void testSSLDiscoverNodesAutomatically(String testName, SslOptions ssl) {\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))\n        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())\n            .sslOptions(ssl).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      Map<String, ?> clusterNodes = jc.getClusterNodes();\n      assertTrue(clusterNodes.containsKey(tlsEndpoint.getHostAndPort(0).toString()));\n      assertTrue(clusterNodes.containsKey(tlsEndpoint.getHostAndPort(1).toString()));\n      assertTrue(clusterNodes.containsKey(tlsEndpoint.getHostAndPort(2).toString()));\n      assertEquals(\"PONG\", jc.ping());\n    }\n  }\n\n  /**\n   * Tests that connecting with ssl=true flag (system truststore) works.\n   */\n  @Test\n  void connectWithSslFlag() {\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))\n        .clientConfig(\n          DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword()).ssl(true).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      assertEquals(\"PONG\", jc.ping());\n    }\n  }\n\n  /**\n   * Tests that connecting to nodes succeeds with SSL parameters and hostname verification.\n   */\n  @ParameterizedTest(name = \"connectToNodesSucceedsWithSSLParametersAndHostMapping_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void connectToNodesSucceedsWithSSLParametersAndHostMapping(String testName, SslOptions ssl) {\n    final SSLParameters sslParameters = new SSLParameters();\n    sslParameters.setEndpointIdentificationAlgorithm(\"HTTPS\");\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))\n        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())\n            .sslOptions(ssl).sslParameters(sslParameters).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      assertEquals(\"PONG\", jc.ping());\n    }\n  }\n\n  /**\n   * Tests connecting with custom hostname verifier and SslOptions (from\n   * SSLOptionsRedisClusterClientIT).\n   */\n  @Test\n  public void connectWithCustomHostNameVerifierAndSslOptions() {\n    HostnameVerifier hostnameVerifier = new TlsUtil.BasicHostnameVerifier();\n\n    SslOptions sslOptions = SslOptions.builder().truststore(trustStorePath.toFile())\n        .trustStoreType(\"jceks\").sslVerifyMode(SslVerifyMode.CA).build();\n\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))\n        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())\n            .sslOptions(sslOptions).hostnameVerifier(hostnameVerifier).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      assertEquals(\"PONG\", jc.ping());\n    }\n  }\n\n  /**\n   * Tests connecting with custom SSL socket factory.\n   */\n  @Test\n  void connectWithCustomSocketFactory() {\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))\n        .clientConfig(\n          DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword()).ssl(true)\n              .sslSocketFactory(sslSocketFactoryForEnv(tlsEndpoint.getCertificatesLocation()))\n              .build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      assertEquals(\"PONG\", jc.ping());\n    }\n  }\n\n  /**\n   * Tests that connecting with an empty trust store fails.\n   */\n  @Test\n  void connectWithEmptyTrustStore() throws Exception {\n    try (RedisClusterClient jc = RedisClusterClient.builder()\n        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))\n        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())\n            .ssl(true).sslSocketFactory(createTrustNoOneSslSocketFactory()).build())\n        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {\n      jc.get(\"foo\");\n      fail(\"Should have thrown an exception\");\n    } catch (JedisClusterOperationException e) {\n      assertEquals(\"Could not initialize cluster slots cache.\", e.getMessage());\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/RedisClusterTestBase.java",
    "content": "package redis.clients.jedis.tls;\n\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.util.*;\n\n/**\n * Abstract base class for SSL/TLS Redis cluster tests.\n * <p>\n * This class provides common setup and teardown for TLS-enabled Redis cluster tests, including\n * truststore initialization and cluster client configuration.\n * <p>\n * Uses the {@code cluster-stable-tls} endpoint for stable integration tests.\n * <p>\n * Note: The {@link RedisVersionCondition} and {@link EnabledOnCommandCondition} extensions use the\n * non-TLS {@code cluster-stable} endpoint for version/command checks because JUnit 5 extensions run\n * before {@code @BeforeAll} methods where the truststore is configured.\n */\n@ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_SOURCE, enabled = false)\npublic abstract class RedisClusterTestBase {\n\n  private static final String ENDPOINT_NAME = \"cluster-stable-tls\";\n  /**\n   * Non-TLS endpoint used for version and command checks. Extensions run before @BeforeAll, so we\n   * can't use TLS endpoints for these checks since the truststore isn't configured yet.\n   */\n  private static final String VERSION_CHECK_ENDPOINT_NAME = \"cluster-stable\";\n  private static final String TRUSTSTORE_PASSWORD = \"changeit\";\n\n  @RegisterExtension\n  public static EnvCondition envCondition = new EnvCondition();\n\n  @RegisterExtension\n  public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(\n      () -> Endpoints.getRedisEndpoint(VERSION_CHECK_ENDPOINT_NAME));\n\n  protected static EndpointConfig tlsEndpoint;\n  protected static Path trustStorePath;\n\n  protected RedisClusterClient cluster;\n\n  /**\n   * HostAndPortMapper that only maps localhost, leaving IP addresses unchanged. Useful for testing\n   * hostname verification failures.\n   */\n  protected final HostAndPortMapper portMap = (HostAndPort hostAndPort) -> {\n    if (\"localhost\".equals(hostAndPort.getHost())) {\n      return hostAndPort;\n    }\n    return new HostAndPort(hostAndPort.getHost(), hostAndPort.getPort());\n  };\n\n  @BeforeAll\n  public static void prepareEndpointAndTrustStore() {\n    tlsEndpoint = Endpoints.getRedisEndpoint(ENDPOINT_NAME);\n    List<Path> trustedCertLocation = Collections\n        .singletonList(tlsEndpoint.getCertificatesLocation());\n    trustStorePath = TlsUtil.createAndSaveTestTruststore(RedisClusterTestBase.class.getSimpleName(),\n      trustedCertLocation, TRUSTSTORE_PASSWORD);\n    TlsUtil.setCustomTrustStore(trustStorePath, TRUSTSTORE_PASSWORD);\n  }\n\n  @AfterAll\n  public static void teardownTrustStore() {\n    TlsUtil.restoreOriginalTrustStore();\n  }\n\n  @BeforeEach\n  public void setUp() {\n    SslOptions sslOptions = SslOptions.builder().truststore(trustStorePath.toFile())\n        .trustStoreType(\"jceks\").sslVerifyMode(SslVerifyMode.CA).build();\n\n    cluster = RedisClusterClient.builder().nodes(new HashSet<>(tlsEndpoint.getHostsAndPorts()))\n        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())\n            .sslOptions(sslOptions).build())\n        .build();\n    cluster.flushAll();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (cluster != null) {\n      cluster.flushAll();\n      cluster.close();\n    }\n  }\n\n  protected static SslOptions createSslOptions() {\n    return SslOptions.builder().truststore(trustStorePath.toFile()).trustStoreType(\"jceks\").build();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/RedisSentinelClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisSentinelClient;\nimport redis.clients.jedis.SslOptions;\n\n/**\n * SSL/TLS tests for {@link RedisSentinelClient} with basic authentication (password-only, no ACL).\n * <p>\n * Tests various SSL/TLS connection configurations including:\n * <ul>\n * <li>Basic SSL connection with truststore</li>\n * <li>Insecure SSL mode (no certificate verification)</li>\n * <li>Custom SSL protocol</li>\n * </ul>\n * <p>\n * This test class uses the default user with password authentication instead of ACL user. The\n * sentinel connection does not use SSL, only the master connection uses SSL.\n */\npublic class RedisSentinelClientIT extends RedisSentinelTlsTestBase {\n\n  // Endpoint for master with default user (password-only, no ACL)\n  private static EndpointConfig masterEndpoint;\n\n  @BeforeAll\n  public static void setUp() {\n    masterEndpoint = Endpoints.getRedisEndpoint(\"standalone0-tls\");\n  }\n\n  @ParameterizedTest(name = \"connectWithSsl_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void connectWithSsl(String testName, SslOptions ssl) {\n    DefaultJedisClientConfig masterConfig = DefaultJedisClientConfig.builder()\n        .clientName(\"master-client\").sslOptions(ssl).password(masterEndpoint.getPassword())\n        .hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    // Sentinel requires authentication but does not use SSL\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithoutSsl();\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/RedisSentinelTlsTestBase.java",
    "content": "package redis.clients.jedis.tls;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.provider.Arguments;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.HostAndPortMapper;\nimport redis.clients.jedis.SslOptions;\nimport redis.clients.jedis.SslVerifyMode;\nimport redis.clients.jedis.util.TlsUtil;\n\n/**\n * Abstract base class for Redis Sentinel TLS integration tests.\n */\npublic abstract class RedisSentinelTlsTestBase {\n\n  protected static final String MASTER_NAME = \"aclmaster\";\n  private static final String TRUSTSTORE_PASSWORD = \"changeit\";\n  private static final String TRUSTSTORE_TYPE = \"jceks\";\n\n  protected static EndpointConfig sentinel;\n  protected static Set<HostAndPort> sentinels = new HashSet<>();\n  protected static Path trustStorePath;\n  protected static SslOptions sslOptions;\n\n  protected static final HostAndPortMapper SENTINEL_SSL_PORT_MAPPER = (\n      HostAndPort hap) -> new HostAndPort(hap.getHost(), hap.getPort() + 10000);\n\n  protected static final HostAndPortMapper PRIMARY_SSL_PORT_MAPPER = (\n      HostAndPort hap) -> new HostAndPort(hap.getHost(), hap.getPort() + 11);\n\n  @BeforeAll\n  public static void setupSentinelTls() {\n    sentinel = Endpoints.getRedisEndpoint(\"sentinel-standalone0\");\n    sentinels.add(sentinel.getHostAndPort());\n\n    List<Path> trustedCertLocation = Collections\n        .singletonList(Paths.get(\"redis1-2-5-8-sentinel/work/tls\"));\n    trustStorePath = TlsUtil.createAndSaveTestTruststore(\n      RedisSentinelTlsTestBase.class.getSimpleName(), trustedCertLocation, TRUSTSTORE_PASSWORD);\n    sslOptions = createTruststoreSslOptions();\n\n    TlsUtil.setCustomTrustStore(trustStorePath, TRUSTSTORE_PASSWORD);\n  }\n\n  @AfterAll\n  public static void teardownTrustStore() {\n    TlsUtil.restoreOriginalTrustStore();\n  }\n\n  protected static SslOptions createTruststoreSslOptions() {\n    return SslOptions.builder().truststore(trustStorePath.toFile()).trustStoreType(TRUSTSTORE_TYPE)\n        .sslVerifyMode(SslVerifyMode.CA).build();\n  }\n\n  protected static DefaultJedisClientConfig createSentinelConfigWithSsl(SslOptions ssl) {\n    return Endpoints.getRedisEndpoint(\"sentinel-standalone0-tls\").getClientConfigBuilder()\n        .clientName(\"sentinel-client\").sslOptions(ssl).hostAndPortMapper(SENTINEL_SSL_PORT_MAPPER)\n        .build();\n  }\n\n  protected static DefaultJedisClientConfig createSentinelConfigWithoutSsl() {\n    return sentinel.getClientConfigBuilder().clientName(\"sentinel-client\").build();\n  }\n\n  protected static Stream<Arguments> sslOptionsProvider() {\n    return Stream.of(Arguments.of(\"truststore\", createTruststoreSslOptions()),\n      Arguments.of(\"insecure\", SslOptions.builder().sslVerifyMode(SslVerifyMode.INSECURE).build()),\n      Arguments.of(\"ssl-protocol\",\n        SslOptions.builder().sslProtocol(\"SSL\").truststore(trustStorePath.toFile())\n            .trustStoreType(TRUSTSTORE_TYPE).sslVerifyMode(SslVerifyMode.CA).build()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/SSLOptionsJedisIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.SslOptions;\n\n/**\n * SSL/TLS tests for {@link Jedis} using SslOptions builder pattern.\n * <p>\n * Tests various SSL/TLS connection configurations including:\n * <ul>\n * <li>Basic SSL connection with truststore</li>\n * <li>Insecure SSL mode (no certificate verification)</li>\n * <li>Custom SSL protocol</li>\n * <li>ACL authentication over SSL</li>\n * </ul>\n */\npublic class SSLOptionsJedisIT extends JedisTlsTestBase {\n\n  /**\n   * Tests connecting to Redis with various SSL configurations using DefaultJedisClientConfig.\n   */\n  @ParameterizedTest(name = \"connectWithSsl_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void connectWithSsl(String testName, SslOptions ssl) {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        DefaultJedisClientConfig.builder().sslOptions(ssl).build())) {\n      jedis.auth(endpoint.getPassword());\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests connecting to Redis with various SSL configurations using endpoint's client config.\n   */\n  @ParameterizedTest(name = \"connectWithClientConfig_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void connectWithClientConfig(String testName, SslOptions ssl) {\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().sslOptions(ssl).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n\n  /**\n   * Tests ACL authentication over SSL.\n   */\n  @Test\n  public void connectWithAcl() {\n    try (Jedis jedis = new Jedis(aclEndpoint.getHostAndPort(),\n        aclEndpoint.getClientConfigBuilder().sslOptions(sslOptions).build())) {\n      assertEquals(\"PONG\", jedis.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/SSLOptionsJedisSentinelPoolIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisSentinelPool;\n\n/**\n * SSL/TLS tests for {@link JedisSentinelPool} using SslOptions builder pattern.\n * <p>\n * Tests various combinations of SSL on master and sentinel connections:\n * <ul>\n * <li>Sentinel without SSL, Redis master with SSL (using SslOptions)</li>\n * <li>Sentinel with SSL (using SslOptions), Redis master without SSL</li>\n * <li>Both sentinel and Redis master with SSL (using SslOptions)</li>\n * </ul>\n */\npublic class SSLOptionsJedisSentinelPoolIT extends RedisSentinelTlsTestBase {\n\n  private static final GenericObjectPoolConfig<Jedis> POOL_CONFIG = new GenericObjectPoolConfig<>();\n\n  // Endpoints for different SSL configurations\n  private static EndpointConfig aclTlsEndpoint;\n  private static EndpointConfig aclEndpoint;\n  private static EndpointConfig sentinelTlsEndpoint;\n\n  @BeforeAll\n  public static void setUp() {\n    aclTlsEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl-tls\");\n    aclEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl\");\n    sentinelTlsEndpoint = Endpoints.getRedisEndpoint(\"sentinel-standalone0-tls\");\n  }\n\n  /**\n   * Tests sentinel without SSL connecting to Redis master with SSL using SslOptions.\n   */\n  @Test\n  public void sentinelWithoutSslConnectsToRedisWithSsl() {\n    DefaultJedisClientConfig masterConfig = aclTlsEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").sslOptions(sslOptions)\n        .hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithoutSsl();\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, masterConfig,\n        sentinelConfig)) {\n      pool.getResource().close();\n    }\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, POOL_CONFIG,\n        masterConfig, sentinelConfig)) {\n      pool.getResource().close();\n    }\n  }\n\n  /**\n   * Tests sentinel with SSL (using SslOptions) connecting to Redis master without SSL.\n   */\n  @Test\n  public void sentinelWithSslConnectsToRedisWithoutSsl() {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").build();\n\n    DefaultJedisClientConfig sentinelConfig = sentinelTlsEndpoint.getClientConfigBuilder()\n        .sslOptions(sslOptions).hostAndPortMapper(SENTINEL_SSL_PORT_MAPPER).build();\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, masterConfig,\n        sentinelConfig)) {\n      pool.getResource().close();\n    }\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, POOL_CONFIG,\n        masterConfig, sentinelConfig)) {\n      pool.getResource().close();\n    }\n  }\n\n  /**\n   * Tests both sentinel and Redis master with SSL using SslOptions.\n   */\n  @Test\n  public void sentinelWithSslConnectsToRedisWithSsl() {\n    DefaultJedisClientConfig masterConfig = aclTlsEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").sslOptions(sslOptions)\n        .hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithSsl(sslOptions);\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, masterConfig,\n        sentinelConfig)) {\n      pool.getResource().close();\n    }\n\n    try (JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, POOL_CONFIG,\n        masterConfig, sentinelConfig)) {\n      pool.getResource().close();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/SSLOptionsRedisClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.RedisClient;\nimport redis.clients.jedis.SslOptions;\n\n/**\n * SSL/TLS tests for {@link RedisClient} using SslOptions builder pattern.\n * <p>\n * Tests various SSL/TLS connection configurations including:\n * <ul>\n * <li>Basic SSL connection with truststore</li>\n * <li>Insecure SSL mode (no certificate verification)</li>\n * <li>Custom SSL protocol</li>\n * <li>ACL authentication over SSL</li>\n * </ul>\n */\npublic class SSLOptionsRedisClientIT extends RedisClientTlsTestBase {\n\n  @ParameterizedTest(name = \"connectWithSsl_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void connectWithSsl(String testName, SslOptions ssl) {\n    try (RedisClient client = RedisClient.builder().hostAndPort(endpoint.getHostAndPort())\n        .clientConfig(endpoint.getClientConfigBuilder().sslOptions(ssl).build()).build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests ACL authentication over SSL.\n   */\n  @Test\n  public void connectWithAcl() {\n    try (RedisClient client = RedisClient.builder().hostAndPort(aclEndpoint.getHostAndPort())\n        .clientConfig(aclEndpoint.getClientConfigBuilder().sslOptions(sslOptions).build())\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/tls/SSLOptionsRedisSentinelClientIT.java",
    "content": "package redis.clients.jedis.tls;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport redis.clients.jedis.DefaultJedisClientConfig;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.Endpoints;\nimport redis.clients.jedis.RedisSentinelClient;\nimport redis.clients.jedis.SslOptions;\n\n/**\n * SSL/TLS tests for RedisSentinelClient using SslOptions builder pattern.\n * <p>\n * Tests various SSL/TLS connection configurations including:\n * <ul>\n * <li>Basic SSL connection with truststore</li>\n * <li>Insecure SSL mode (no certificate verification)</li>\n * <li>Custom SSL protocol</li>\n * <li>ACL authentication over SSL</li>\n * </ul>\n * <p>\n * Both master and sentinel connections use SSL in these tests.\n */\npublic class SSLOptionsRedisSentinelClientIT extends RedisSentinelTlsTestBase {\n\n  // Endpoint for master with ACL authentication\n  private static EndpointConfig aclEndpoint;\n\n  @BeforeAll\n  public static void setUp() {\n    aclEndpoint = Endpoints.getRedisEndpoint(\"standalone0-acl-tls\");\n  }\n\n  /**\n   * Tests connecting to Redis master and sentinel with various SSL configurations. Both master and\n   * sentinel connections use SSL.\n   */\n  @ParameterizedTest(name = \"connectWithSsl_{0}\")\n  @MethodSource(\"sslOptionsProvider\")\n  void connectWithSsl(String testName, SslOptions ssl) {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").sslOptions(ssl).hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER)\n        .build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithSsl(ssl);\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests ACL authentication over SSL (same as truststore test but explicitly named).\n   */\n  @Test\n  public void connectWithAcl() {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").sslOptions(sslOptions)\n        .hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithSsl(sslOptions);\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n  /**\n   * Tests that sentinel without SSL can connect to Redis master with SSL.\n   */\n  @Test\n  public void sentinelWithoutSslConnectsToRedisWithSsl() {\n    DefaultJedisClientConfig masterConfig = aclEndpoint.getClientConfigBuilder()\n        .clientName(\"master-client\").sslOptions(sslOptions)\n        .hostAndPortMapper(PRIMARY_SSL_PORT_MAPPER).build();\n\n    DefaultJedisClientConfig sentinelConfig = createSentinelConfigWithoutSsl();\n\n    try (RedisSentinelClient client = RedisSentinelClient.builder().masterName(MASTER_NAME)\n        .sentinels(sentinels).clientConfig(masterConfig).sentinelClientConfig(sentinelConfig)\n        .build()) {\n      assertEquals(\"PONG\", client.ping());\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ACLTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport redis.clients.jedis.BuilderFactory;\nimport redis.clients.jedis.resps.AccessControlLogEntry;\n\n/**\n * Utility class for ACL testing operations.\n */\npublic final class ACLTestUtil {\n\n  private ACLTestUtil() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  /**\n   * Filters a list of ACL log entries by client ID.\n   * @param entries the list of ACL log entries to filter\n   * @param clientId the client ID to filter by\n   * @return a new list containing only entries matching the specified client ID\n   */\n  public static List<AccessControlLogEntry> filterByClientId(List<AccessControlLogEntry> entries,\n      long clientId) {\n    String clientIdStr = String.valueOf(clientId);\n    return entries.stream().filter(entry -> {\n      Map<String, String> clientInfo = entry.getClientInfo();\n      return clientInfo != null && clientIdStr.equals(clientInfo.get(\"id\"));\n    }).collect(Collectors.toList());\n  }\n\n  /**\n   * Filters a list of binary ACL log entries by client ID.\n   * <p>\n   * This method converts the binary ACL log entries to AccessControlLogEntry objects, filters them\n   * by client ID, and returns the filtered binary entries.\n   * @param binaryEntries the list of binary ACL log entries to filter (raw Redis response)\n   * @param clientId the client ID to filter by\n   * @return a new list containing only binary entries matching the specified client ID\n   */\n  public static List<byte[]> filterBinaryByClientId(List<byte[]> binaryEntries, long clientId) {\n    if (binaryEntries == null || binaryEntries.isEmpty()) {\n      return new ArrayList<>();\n    }\n\n    // Build the structured entries from binary data\n    List<AccessControlLogEntry> entries = BuilderFactory.ACCESS_CONTROL_LOG_ENTRY_LIST\n        .build(binaryEntries);\n    if (entries == null || entries.isEmpty()) {\n      return new ArrayList<>();\n    }\n\n    // Filter by client ID\n    String clientIdStr = String.valueOf(clientId);\n    List<Integer> matchingIndices = new ArrayList<>();\n    for (int i = 0; i < entries.size(); i++) {\n      AccessControlLogEntry entry = entries.get(i);\n      Map<String, String> clientInfo = entry.getClientInfo();\n      if (clientInfo != null && clientIdStr.equals(clientInfo.get(\"id\"))) {\n        matchingIndices.add(i);\n      }\n    }\n\n    // Return the corresponding binary entries\n    List<byte[]> result = new ArrayList<>();\n    for (Integer index : matchingIndices) {\n      result.add(binaryEntries.get(index));\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/AssertUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport org.opentest4j.AssertionFailedError;\nimport redis.clients.jedis.RedisProtocol;\n\npublic class AssertUtil {\n\n  public static void assertOK(String str) {\n    assertEquals(\"OK\", str);\n  }\n\n  public static void assertEqualsByProtocol(RedisProtocol protocol, Object expectedResp2, Object expectedResp3, Object actual) {\n    if (protocol != RedisProtocol.RESP3) {\n      assertEquals(expectedResp2, actual);\n    } else {\n      assertEquals(expectedResp3, actual);\n    }\n  }\n\n  public static <T> boolean assertCollectionContains(Collection<T> array, T expected) {\n    for (T element : array) {\n      if (Objects.equals(element, expected)) {\n        return true;\n      }\n    }\n    throw new AssertionFailedError(\"element is missing\", Objects.toString(expected), array.toString());\n  }\n\n  public static boolean assertByteArrayCollectionContains(Collection<byte[]> array, byte[] expected) {\n    for (byte[] bytes : array) {\n      if (Arrays.equals(bytes, expected)) {\n        return true;\n      }\n    }\n    throw new AssertionFailedError(\"element is missing\", Arrays.toString(expected), array.toString());\n  }\n\n  public static void assertByteArrayListEquals(List<byte[]> expected, List<byte[]> actual) {\n    assertEquals(expected.size(), actual.size());\n    for (int n = 0; n < expected.size(); n++) {\n      assertArrayEquals(expected.get(n), actual.get(n), n + \"'th elements don't match\");\n    }\n  }\n\n  public static void assertByteArraySetEquals(Set<byte[]> expected, Set<byte[]> actual) {\n    assertEquals(expected.size(), actual.size());\n    Iterator<byte[]> e = expected.iterator();\n    while (e.hasNext()) {\n      byte[] next = e.next();\n      boolean contained = false;\n      for (byte[] element : actual) {\n        if (Arrays.equals(next, element)) {\n          contained = true;\n          break;\n        }\n      }\n      if (!contained) {\n        throw new AssertionFailedError(\"element is missing\", Arrays.toString(next), actual.toString());\n      }\n    }\n  }\n\n  public static void assertCollectionContainsAll(Collection all, Collection few) {\n    Iterator fi = few.iterator();\n    while (fi.hasNext()) {\n      Object fo = fi.next();\n      boolean found = false;\n      for (Object ao : all) {\n        if (Objects.equals(fo, ao)) {\n          found = true;\n          break;\n        }\n      }\n      if (!found) {\n        throw new AssertionFailedError(\"element is missing\", Objects.toString(fo), all.toString());\n      }\n    }\n  }\n\n  public static void assertByteArrayCollectionContainsAll(Collection<byte[]> all,\n      Collection<byte[]> few) {\n    Iterator<byte[]> fi = few.iterator();\n    while (fi.hasNext()) {\n      byte[] fo = fi.next();\n      boolean found = false;\n      for (byte[] ao : all) {\n        if (Arrays.equals(fo, ao)) {\n          found = true;\n          break;\n        }\n      }\n      if (!found) {\n        throw new AssertionFailedError(\"element is missing\", Arrays.toString(fo), all.toString());\n      }\n    }\n  }\n\n  public static void assertPipelineSyncAll(List<Object> expected, List<Object> actual) {\n    assertEquals(expected.size(), actual.size());\n    for (int n = 0; n < expected.size(); n++) {\n      Object expObj = expected.get(n);\n      Object actObj = actual.get(n);\n      if (expObj instanceof List) {\n        if (!(actObj instanceof List)) {\n          throw new AssertionFailedError(n + \"'th element is not a list\",\n              expObj.getClass().toString(), actObj.getClass().toString());\n        }\n        assertPipelineSyncAll((List) expObj, (List) actObj);\n      } else if (expObj instanceof List) {\n        if (!(actObj instanceof List)) {\n          throw new AssertionFailedError(n + \"'th element is not a list\",\n              expObj.getClass().toString(), actObj.getClass().toString());\n        }\n        assertPipelineSyncAll((List) expObj, (List) actObj);\n      } else if (expObj instanceof Set) {\n        if (!(actObj instanceof Set)) {\n          throw new AssertionFailedError(n + \"'th element is not a set\",\n              expObj.getClass().toString(), actObj.getClass().toString());\n        }\n        assertPipelineSyncAllSet((Set) expObj, (Set) actObj);\n      } else if (expObj instanceof byte[]) {\n        if (!(actObj instanceof byte[])) {\n          throw new AssertionFailedError(n + \"'th element is not byte array\",\n              expObj.getClass().toString(), actObj.getClass().toString());\n        }\n        assertArrayEquals((byte[]) expObj, (byte[]) actObj);\n      } else {\n        assertEquals(expObj, actObj, n + \"'th element mismatched\");\n      }\n    }\n  }\n\n  private static void assertPipelineSyncAllSet(Set<?> expected, Set<?> actual) {\n    assertEquals(expected.size(), actual.size());\n    if (expected.iterator().next() instanceof byte[]) {\n      assertByteArraySetEquals((Set<byte[]>) expected, (Set<byte[]>) actual);\n    } else {\n      assertEquals(expected, actual);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ByteArrayComparatorTest.java",
    "content": "package redis.clients.jedis.util;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport org.junit.jupiter.api.Test;\n\npublic class ByteArrayComparatorTest {\n\n  @Test\n  public void test() {\n    byte[] foo = SafeEncoder.encode(\"foo\");\n    byte[] foo2 = SafeEncoder.encode(\"foo\");\n    byte[] bar = SafeEncoder.encode(\"bar\");\n\n    assertTrue(ByteArrayComparator.compare(foo, foo2) == 0);\n    assertTrue(ByteArrayComparator.compare(foo, bar) > 0);\n    assertTrue(ByteArrayComparator.compare(bar, foo) < 0);\n  }\n\n  @Test\n  public void testPrefix() {\n    byte[] foo = SafeEncoder.encode(\"foo\");\n    byte[] fooo = SafeEncoder.encode(\"fooo\");\n    assertTrue(ByteArrayComparator.compare(foo, fooo) < 0);\n    assertTrue(ByteArrayComparator.compare(fooo, foo) > 0);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ByteArrayMapMatcher.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.TypeSafeMatcher;\nimport java.util.Map;\nimport java.util.Arrays;\n\npublic class ByteArrayMapMatcher extends TypeSafeMatcher<Map<byte[], byte[]>> {\n\n  private final Map<byte[], byte[]> expected;\n\n  public ByteArrayMapMatcher(Map<byte[], byte[]> expected) {\n    this.expected = expected;\n  }\n\n  @Override\n  protected boolean matchesSafely(Map<byte[], byte[]> actual) {\n    if (actual == null) {\n      return expected == null;\n    }\n\n    if (actual.size() != expected.size()) return false;\n\n    // For each expected key, find the matching key in actual and verify the value matches\n    for (Map.Entry<byte[], byte[]> expectedEntry : expected.entrySet()) {\n      byte[] expectedKey = expectedEntry.getKey();\n      byte[] expectedValue = expectedEntry.getValue();\n\n      // Find the actual entry with matching key\n      boolean keyFound = false;\n      for (Map.Entry<byte[], byte[]> actualEntry : actual.entrySet()) {\n        if (Arrays.equals(expectedKey, actualEntry.getKey())) {\n          keyFound = true;\n          // Verify the value for this key matches\n          if (!Arrays.equals(expectedValue, actualEntry.getValue())) {\n            return false; // Key found but value doesn't match\n          }\n          break;\n        }\n      }\n\n      if (!keyFound) {\n        return false; // Expected key not found in actual\n      }\n    }\n\n    return true;\n  }\n\n  @Override\n  public void describeTo(Description description) {\n    description.appendText(\"maps to be equal by byte[] content\");\n  }\n\n  public static ByteArrayMapMatcher contentEquals(Map<byte[], byte[]> expected) {\n    return new ByteArrayMapMatcher(expected);\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ByteArrayUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\n\npublic class ByteArrayUtil {\n\n  public static boolean byteArrayCollectionRemove(Collection<byte[]> all, byte[] element) {\n    Iterator<byte[]> it = all.iterator();\n    while (it.hasNext()) {\n      if (Arrays.equals(it.next(), element)) {\n        it.remove();\n        return true;\n      }\n    }\n    return false;\n  }\n\n  public static boolean byteArrayCollectionRemoveAll(Collection<byte[]> all, Collection<byte[]> few) {\n    boolean modified = false;\n    for (byte[] e : few) {\n      modified |= byteArrayCollectionRemove(all, e);\n    }\n    return modified;\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ClientKillerUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport redis.clients.jedis.Jedis;\n\npublic class ClientKillerUtil {\n\n  public static void killClient(Jedis jedis, String clientName) {\n    for (String clientInfo : jedis.clientList().split(\"\\n\")) {\n      if (clientInfo.contains(\"name=\" + clientName)) {\n        // Ugly, but cmon, it's a test.\n        String hostAndPortString = clientInfo.split(\" \")[1].split(\"=\")[1];\n        // It would be better if we kill the client by Id as it's safer but jedis doesn't implement\n        // the command yet.\n        jedis.clientKill(hostAndPortString);\n      }\n    }\n  }\n\n  public static void tagClient(Jedis j, String name) {\n    j.clientSetname(name);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ClientTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.providers.ConnectionProvider;\n\npublic class ClientTestUtil {\n\n  public static <T extends ConnectionProvider> T getConnectionProvider(UnifiedJedis jedis) {\n    return ReflectionTestUtil.getField(jedis, \"provider\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/CommandArgumentsMatchers.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.TypeSafeMatcher;\nimport org.mockito.ArgumentMatcher;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.args.Rawable;\nimport redis.clients.jedis.commands.ProtocolCommand;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Utility class providing matchers for CommandArguments testing.\n * <p>\n * Provides both Mockito ArgumentMatchers (for mock verification) and Hamcrest Matchers (for\n * assertions).\n * </p>\n * <p>\n * Hamcrest matcher example usage:\n * </p>\n * \n * <pre>\n * {@code\n * assertThat(args, hasCommand(Protocol.Command.ZRANGE));\n * assertThat(args, hasArgumentCount(3));\n * assertThat(args, hasArgument(1, RawableFactory.from(100L)));\n * assertThat(args, hasArguments(\n *     Protocol.Command.ZRANGE,\n *     RawableFactory.from(0L),\n *     RawableFactory.from(100L)\n * ));\n * }\n * </pre>\n * <p>\n * Mockito matcher example usage:\n * </p>\n * \n * <pre>\n * {@code\n * verify(mock).someMethod(argThat(commandIs(Protocol.Command.GET)));\n * verify(mock).someMethod(argThat(commandWithArgs(Protocol.Command.SET, \"key\")));\n * }\n * </pre>\n */\npublic final class CommandArgumentsMatchers {\n\n  private CommandArgumentsMatchers() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  // ========== Mockito ArgumentMatchers ==========\n\n  /**\n   * Mockito matcher for CommandArguments with specific ProtocolCommand.\n   * @param command the expected protocol command\n   * @return an ArgumentMatcher that checks if the CommandArguments has the specified command\n   */\n  public static ArgumentMatcher<CommandArguments> commandIs(ProtocolCommand command) {\n    return args -> {\n      if (args == null || !(args instanceof CommandArguments)) {\n        return false;\n      }\n      return command.equals(args.getCommand());\n    };\n  }\n\n  /**\n   * Mockito matcher for CommandArguments containing a specific argument (as String).\n   * @param expectedArg the expected argument value (will be compared as String)\n   * @return an ArgumentMatcher that checks if the CommandArguments contains the argument\n   */\n  public static ArgumentMatcher<CommandArguments> hasArgument(String expectedArg) {\n    return args -> {\n      for (Rawable arg : args) {\n        if (expectedArg.equals(SafeEncoder.encode(arg.getRaw()))) {\n          return true;\n        }\n      }\n      return false;\n    };\n  }\n\n  /**\n   * Mockito matcher for CommandArguments with specific command and containing a specific argument.\n   * @param command the expected protocol command\n   * @param expectedArg the expected argument value (will be compared as String)\n   * @return an ArgumentMatcher that checks both command and argument\n   */\n  public static ArgumentMatcher<CommandArguments> commandWithArgs(ProtocolCommand command,\n      String expectedArg) {\n    return cmd -> commandIs(command).matches(cmd) && hasArgument(expectedArg).matches(cmd);\n  }\n\n  // ========== Hamcrest Matchers ==========\n\n  /**\n   * Matches CommandArguments with a specific command.\n   * @param expectedCommand the expected protocol command\n   * @return a matcher that checks if the CommandArguments has the specified command\n   */\n  public static Matcher<CommandArguments> hasCommand(ProtocolCommand expectedCommand) {\n    return new TypeSafeMatcher<CommandArguments>() {\n      @Override\n      protected boolean matchesSafely(CommandArguments args) {\n        return expectedCommand.equals(args.getCommand());\n      }\n\n      @Override\n      public void describeTo(Description description) {\n        description.appendText(\"CommandArguments with command \").appendValue(expectedCommand);\n      }\n\n      @Override\n      protected void describeMismatchSafely(CommandArguments args,\n          Description mismatchDescription) {\n        mismatchDescription.appendText(\"was CommandArguments with command \")\n            .appendValue(args.getCommand());\n      }\n    };\n  }\n\n  /**\n   * Matches CommandArguments with a specific argument count.\n   * @param expectedSize the expected number of arguments (including the command)\n   * @return a matcher that checks if the CommandArguments has the specified size\n   */\n  public static Matcher<CommandArguments> hasArgumentCount(int expectedSize) {\n    return new TypeSafeMatcher<CommandArguments>() {\n      @Override\n      protected boolean matchesSafely(CommandArguments args) {\n        return args.size() == expectedSize;\n      }\n\n      @Override\n      public void describeTo(Description description) {\n        description.appendText(\"CommandArguments with argument count \").appendValue(expectedSize);\n      }\n\n      @Override\n      protected void describeMismatchSafely(CommandArguments args,\n          Description mismatchDescription) {\n        mismatchDescription.appendText(\"was CommandArguments with argument count \")\n            .appendValue(args.size());\n      }\n    };\n  }\n\n  /**\n   * Matches CommandArguments with a specific argument at a specific index.\n   * @param index the index of the argument (0-based, where 0 is the command)\n   * @param expectedArg the expected Rawable argument\n   * @return a matcher that checks if the CommandArguments has the specified argument at the index\n   */\n  public static Matcher<CommandArguments> hasArgument(int index, Rawable expectedArg) {\n    return new TypeSafeMatcher<CommandArguments>() {\n      @Override\n      protected boolean matchesSafely(CommandArguments args) {\n        if (index < 0 || index >= args.size()) {\n          return false;\n        }\n        return expectedArg.equals(args.get(index));\n      }\n\n      @Override\n      public void describeTo(Description description) {\n        description.appendText(\"CommandArguments with argument at index \").appendValue(index)\n            .appendText(\" equal to \").appendValue(expectedArg);\n      }\n\n      @Override\n      protected void describeMismatchSafely(CommandArguments args,\n          Description mismatchDescription) {\n        if (index < 0 || index >= args.size()) {\n          mismatchDescription.appendText(\"index \").appendValue(index)\n              .appendText(\" is out of bounds (size: \").appendValue(args.size()).appendText(\")\");\n        } else {\n          mismatchDescription.appendText(\"argument at index \").appendValue(index)\n              .appendText(\" was \").appendValue(args.get(index));\n        }\n      }\n    };\n  }\n\n  /**\n   * Matches CommandArguments with a specific sequence of arguments.\n   * <p>\n   * The first argument should be the command, followed by the parameters.\n   * </p>\n   * @param expectedArgs the expected sequence of Rawable arguments (command + parameters)\n   * @return a matcher that checks if the CommandArguments matches the sequence\n   */\n  public static Matcher<CommandArguments> hasArguments(Rawable... expectedArgs) {\n    return new TypeSafeMatcher<CommandArguments>() {\n      @Override\n      protected boolean matchesSafely(CommandArguments args) {\n        if (args.size() != expectedArgs.length) {\n          return false;\n        }\n\n        Iterator<Rawable> iter = args.iterator();\n        for (Rawable expected : expectedArgs) {\n          if (!iter.hasNext() || !expected.equals(iter.next())) {\n            return false;\n          }\n        }\n        return true;\n      }\n\n      @Override\n      public void describeTo(Description description) {\n        List<String> decodedExpectedArgs = Arrays.stream(expectedArgs).map(Rawable::getRaw)\n            .map(SafeEncoder::encode).collect(Collectors.toList());\n        description.appendText(\"CommandArguments with arguments \").appendValue(decodedExpectedArgs);\n      }\n\n      @Override\n      protected void describeMismatchSafely(CommandArguments args,\n          Description mismatchDescription) {\n        List<Rawable> actualArgs = new ArrayList<>();\n        args.forEach(actualArgs::add);\n        List<String> decodedActualArgs = actualArgs.stream().map(Rawable::getRaw)\n            .map(SafeEncoder::encode).collect(Collectors.toList());\n        mismatchDescription.appendText(\"was CommandArguments with arguments \")\n            .appendValue(decodedActualArgs);\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/DelayTest.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.time.Duration;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class DelayTest {\n\n  @Test\n  public void testConstantDelay() {\n    Delay delay = Delay.constant(Duration.ofMillis(100));\n\n    // Constant delay should return the same value for all attempts\n    assertEquals(100, delay.delay(0).toMillis());\n    assertEquals(100, delay.delay(1).toMillis());\n    assertEquals(100, delay.delay(5).toMillis());\n    assertEquals(100, delay.delay(100).toMillis());\n  }\n\n  @Test\n  public void testExponentialWithJitterBounds() {\n    Duration lower = Duration.ofMillis(50);\n    Duration upper = Duration.ofSeconds(10);\n    Duration base = Duration.ofMillis(100);\n\n    Delay delay = Delay.exponentialWithJitter(lower, upper, base);\n\n    // Test multiple attempts to verify bounds\n    for (int attempt = 0; attempt < 20; attempt++) {\n      Duration result = delay.delay(attempt);\n      long millis = result.toMillis();\n\n      // Verify lower bound\n      assertTrue(millis >= lower.toMillis(), String.format(\n        \"Attempt %d: delay %d should be >= lower bound %d\", attempt, millis, lower.toMillis()));\n\n      // Verify upper bound\n      assertTrue(millis <= upper.toMillis(), String.format(\n        \"Attempt %d: delay %d should be <= upper bound %d\", attempt, millis, upper.toMillis()));\n    }\n  }\n\n  @Test\n  public void testExponentialWithJitterGrowth() {\n    Duration lower = Duration.ofMillis(10);\n    Duration upper = Duration.ofSeconds(60);\n    Duration base = Duration.ofMillis(100);\n\n    Delay delay = Delay.exponentialWithJitter(lower, upper, base);\n\n    // Collect multiple samples for each attempt to verify growth trend\n    int samples = 100;\n\n    // Attempt 0: base * 2^0 = 100ms, range [50-100]ms\n    long sum0 = 0;\n    for (int i = 0; i < samples; i++) {\n      sum0 += delay.delay(0).toMillis();\n    }\n    long avg0 = sum0 / samples;\n\n    // Attempt 1: base * 2^1 = 200ms, range [100-200]ms\n    long sum1 = 0;\n    for (int i = 0; i < samples; i++) {\n      sum1 += delay.delay(1).toMillis();\n    }\n    long avg1 = sum1 / samples;\n\n    // Attempt 2: base * 2^2 = 400ms, range [200-400]ms\n    long sum2 = 0;\n    for (int i = 0; i < samples; i++) {\n      sum2 += delay.delay(2).toMillis();\n    }\n    long avg2 = sum2 / samples;\n\n    // Verify exponential growth: avg1 should be roughly 2x avg0, avg2 should be roughly 2x avg1\n    assertTrue(avg1 > avg0, \"Average delay should increase with attempts\");\n    assertTrue(avg2 > avg1, \"Average delay should continue to increase\");\n  }\n\n  @Test\n  public void testExponentialWithJitterEqualJitterFormula() {\n    Duration lower = Duration.ofMillis(0);\n    Duration upper = Duration.ofSeconds(10);\n    Duration base = Duration.ofMillis(100);\n\n    Delay delay = Delay.exponentialWithJitter(lower, upper, base);\n\n    // For attempt 0: temp = min(10000, 100 * 2^0) = 100\n    // Equal jitter: delay = temp/2 + random[0, temp/2] = 50 + random[0, 50]\n    // Range should be [50, 100]\n    for (int i = 0; i < 50; i++) {\n      long millis = delay.delay(0).toMillis();\n      assertTrue(millis >= 50 && millis <= 100,\n        String.format(\"Attempt 0: delay %d should be in range [50, 100]\", millis));\n    }\n\n    // For attempt 1: temp = min(10000, 100 * 2^1) = 200\n    // Equal jitter: delay = 100 + random[0, 100]\n    // Range should be [100, 200]\n    for (int i = 0; i < 50; i++) {\n      long millis = delay.delay(1).toMillis();\n      assertTrue(millis >= 100 && millis <= 200,\n        String.format(\"Attempt 1: delay %d should be in range [100, 200]\", millis));\n    }\n  }\n\n  @Test\n  public void testExponentialWithJitterUpperBoundCapping() {\n    Duration lower = Duration.ofMillis(10);\n    Duration upper = Duration.ofMillis(500);\n    Duration base = Duration.ofMillis(100);\n\n    Delay delay = Delay.exponentialWithJitter(lower, upper, base);\n\n    // For high attempts, exponential should be capped at upper bound\n    // Attempt 10: base * 2^10 = 102400ms, but capped at 500ms\n    // Equal jitter: delay = 250 + random[0, 250]\n    // Range should be [250, 500]\n    for (int i = 0; i < 50; i++) {\n      long millis = delay.delay(10).toMillis();\n      assertTrue(millis >= 250 && millis <= 500,\n        String.format(\"Attempt 10: delay %d should be in range [250, 500] (capped)\", millis));\n    }\n  }\n\n  @Test\n  public void testExponentialWithJitterLowerBoundEnforcement() {\n    Duration lower = Duration.ofMillis(200);\n    Duration upper = Duration.ofSeconds(10);\n    Duration base = Duration.ofMillis(100);\n\n    Delay delay = Delay.exponentialWithJitter(lower, upper, base);\n\n    // For attempt 0: temp = 100, equal jitter would give [50, 100]\n    // But lower bound is 200, so all values should be >= 200\n    for (int i = 0; i < 50; i++) {\n      long millis = delay.delay(0).toMillis();\n      assertTrue(millis >= 200,\n        String.format(\"Attempt 0: delay %d should be >= lower bound 200\", millis));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/EnabledOnCommandCondition.java",
    "content": "package redis.clients.jedis.util;\n\nimport io.redis.test.annotations.EnabledOnCommand;\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExecutionCondition;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.platform.commons.util.AnnotationUtils;\nimport redis.clients.jedis.*;\nimport redis.clients.jedis.resps.CommandInfo;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\npublic class EnabledOnCommandCondition implements ExecutionCondition {\n\n  private final Supplier<EndpointConfig> endpointSupplier;\n  private HostAndPort hostPort;\n  private JedisClientConfig config;\n\n  public EnabledOnCommandCondition(HostAndPort hostPort, JedisClientConfig config) {\n    this.endpointSupplier = null;\n    this.hostPort = hostPort;\n    this.config = config;\n  }\n\n  public EnabledOnCommandCondition(Supplier<EndpointConfig> endpointSupplier) {\n    this.endpointSupplier = endpointSupplier;\n    this.hostPort = null;\n    this.config = null;\n  }\n\n  private void ensureInitialized() {\n    if (hostPort == null && endpointSupplier != null) {\n      EndpointConfig endpoint = endpointSupplier.get();\n      this.hostPort = endpoint.getHostAndPort();\n      this.config = endpoint.getClientConfigBuilder().build();\n    }\n  }\n\n  @Override\n  public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {\n    ensureInitialized();\n    try (Jedis jedisClient = new Jedis(hostPort, config)) {\n      String[] command = getCommandFromAnnotations(context);\n\n      if (command != null && !isCommandAvailable(jedisClient, command[0], command[1])) {\n        return ConditionEvaluationResult.disabled(\n            \"Test requires Redis command '\" + command[0] + \" \" + command[1]\n                + \"' to be available on \" + hostPort + \", but it was not found.\");\n      }\n    } catch (Exception e) {\n      return ConditionEvaluationResult.disabled(\n          \"Failed to check Redis command on \" + hostPort + \": \" + e.getMessage());\n    }\n    return ConditionEvaluationResult.enabled(\"Redis command is available\");\n  }\n\n  private String[] getCommandFromAnnotations(ExtensionContext context) {\n    Optional<EnabledOnCommand> methodAnnotation = AnnotationUtils.findAnnotation(\n        context.getRequiredTestMethod(), EnabledOnCommand.class);\n    if (methodAnnotation.isPresent()) {\n      return new String[] { methodAnnotation.get().value(), methodAnnotation.get().subCommand() };\n    }\n\n    Optional<EnabledOnCommand> classAnnotation = AnnotationUtils.findAnnotation(\n        context.getRequiredTestClass(), EnabledOnCommand.class);\n    if (classAnnotation.isPresent()) {\n      return new String[] { classAnnotation.get().value(), classAnnotation.get().subCommand() };\n    }\n\n    return null;\n  }\n\n  private boolean isCommandAvailable(Jedis jedisClient, String command, String subCommand) {\n    try {\n      Map<String, CommandInfo> commandInfoMap = jedisClient.commandInfo(command);\n      CommandInfo commandInfo = commandInfoMap.get(command.toLowerCase());\n      if (commandInfo != null) {\n        if (subCommand != null && !subCommand.isEmpty()) {\n          String replySubCommandName = command + '|' + subCommand;\n          for (CommandInfo supportedSubCommand : commandInfo.getSubcommands().values()) {\n            if (replySubCommandName.equalsIgnoreCase(supportedSubCommand.getName())) {\n              return true;\n            }\n          }\n          return false;\n        }\n        return true;\n      }\n      return false;\n    } catch (Exception e) {\n      throw new RuntimeException(\"Error found while EnableOnCommand for command '\" + command + \"'\",\n          e);\n    }\n  }\n\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/EnvCondition.java",
    "content": "package redis.clients.jedis.util;\n\nimport io.redis.test.annotations.ConditionalOnEnv;\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExecutionCondition;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.platform.commons.util.AnnotationUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Arrays;\nimport java.util.Optional;\n\n/**\n * JUnit 5 execution condition that enables or disables tests based on the test environment.\n * <p>\n * This condition works with the {@link ConditionalOnEnv} annotation to conditionally execute tests\n * depending on the current test environment (e.g., Docker, Redis Enterprise).\n * <p>\n * The environment is determined using {@link TestEnvUtil}.\n * <p>\n * Example usage with JUnit 5:\n *\n * <pre>\n * &#64;ExtendWith(EnvCondition.class)\n * &#64;ConditionalOnEnv(value = TestEnvUtil.ENV_OSS_DOCKER, enabled = true)\n * public class DockerOnlyTest {\n *   // Tests in this class only run in Docker environment\n * }\n *\n * &#64;ExtendWith(EnvCondition.class)\n * &#64;ConditionalOnEnv(value = TestEnvUtil.ENV_REDIS_ENTERPRISE, enabled = false)\n * public class NotOnEnterpriseTest {\n *   // Tests in this class are skipped in Redis Enterprise environment\n * }\n * </pre>\n */\npublic class EnvCondition implements ExecutionCondition {\n  private static final Logger logger = LoggerFactory.getLogger(EnvCondition.class);\n\n  @Override\n  public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {\n    String currentEnv = TestEnvUtil.getTestEnvProvider();\n\n    ConditionalOnEnv annotation = getConditionalOnEnvAnnotation(context);\n    if (annotation != null) {\n      return evaluateConditionalOnEnv(annotation, currentEnv);\n    }\n\n    return ConditionEvaluationResult.enabled(\"No @ConditionalOnEnv annotation found\");\n  }\n\n  private ConditionEvaluationResult evaluateConditionalOnEnv(ConditionalOnEnv annotation,\n      String currentEnv) {\n    String[] envs = annotation.value();\n    boolean enabled = annotation.enabled();\n\n    boolean matches = Arrays.stream(envs).anyMatch(env -> env.equalsIgnoreCase(currentEnv));\n\n    if (enabled) {\n      // enabled = true: test runs ONLY when environment matches\n      if (matches) {\n        logger.debug(\"Test enabled: current environment '{}' matches one of {}\", currentEnv,\n          Arrays.toString(envs));\n        return ConditionEvaluationResult\n            .enabled(\"Current environment '\" + currentEnv + \"' is in the enabled list\");\n      }\n\n      String message = annotation.message();\n      String disabledReason = message.isEmpty()\n          ? \"Test requires environment \" + Arrays.toString(envs) + \", but current environment is '\"\n              + currentEnv + \"'\"\n          : message;\n\n      logger.debug(\"Test disabled: {}\", disabledReason);\n      return ConditionEvaluationResult.disabled(disabledReason);\n    } else {\n      // enabled = false: test is SKIPPED when environment matches\n      if (matches) {\n        String message = annotation.message();\n        String disabledReason = message.isEmpty()\n            ? \"Test is disabled in environment '\" + currentEnv + \"'\"\n            : message;\n\n        logger.debug(\"Test disabled: {}\", disabledReason);\n        return ConditionEvaluationResult.disabled(disabledReason);\n      }\n\n      logger.debug(\"Test enabled: current environment '{}' is not in disabled list {}\", currentEnv,\n        Arrays.toString(envs));\n      return ConditionEvaluationResult\n          .enabled(\"Current environment '\" + currentEnv + \"' is not in the disabled list\");\n    }\n  }\n\n  /**\n   * Retrieves the {@link ConditionalOnEnv} annotation from the test method or class. Method-level\n   * annotations take precedence over class-level annotations.\n   * @param context the extension context\n   * @return the annotation, or null if not present\n   */\n  private ConditionalOnEnv getConditionalOnEnvAnnotation(ExtensionContext context) {\n    Optional<ConditionalOnEnv> methodAnnotation = AnnotationUtils\n        .findAnnotation(context.getTestMethod(), ConditionalOnEnv.class);\n    if (methodAnnotation.isPresent()) {\n      return methodAnnotation.get();\n    }\n\n    Optional<ConditionalOnEnv> classAnnotation = AnnotationUtils\n        .findAnnotation(context.getRequiredTestClass(), ConditionalOnEnv.class);\n    return classAnnotation.orElse(null);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/FragmentedByteArrayInputStream.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.io.ByteArrayInputStream;\n\n/**\n * Test class the fragment a byte array for testing purpose.\n */\npublic class FragmentedByteArrayInputStream extends ByteArrayInputStream {\n  private int readMethodCallCount = 0;\n\n  public FragmentedByteArrayInputStream(final byte[] buf) {\n    super(buf);\n  }\n\n  @Override\n  public synchronized int read(final byte[] b, final int off, final int len) {\n    readMethodCallCount++;\n    if (len <= 10) {\n      // if the len <= 10, return as usual ..\n      return super.read(b, off, len);\n    } else {\n      // else return the first half ..\n      return super.read(b, off, len / 2);\n    }\n  }\n\n  public int getReadMethodCallCount() {\n    return readMethodCallCount;\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/GeoCoordinateMatcher.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.TypeSafeMatcher;\nimport redis.clients.jedis.GeoCoordinate;\n\npublic class GeoCoordinateMatcher extends TypeSafeMatcher<GeoCoordinate> {\n\n  public static GeoCoordinateMatcher atCoordinates(double longitude, double latitude) {\n    return atCoordinates(new GeoCoordinate(longitude, latitude));\n  }\n\n  static GeoCoordinateMatcher atCoordinates(GeoCoordinate expected) {\n    return new GeoCoordinateMatcher(expected);\n  }\n\n  private static final double EPSILON = 1e-5;\n\n  private final GeoCoordinate expected;\n\n  public GeoCoordinateMatcher(GeoCoordinate expected) {\n    this.expected = expected;\n  }\n\n  @Override\n  protected boolean matchesSafely(GeoCoordinate actual) {\n    return Math.abs(actual.getLatitude() - expected.getLatitude()) < EPSILON &&\n            Math.abs(actual.getLongitude() - expected.getLongitude()) < EPSILON;\n  }\n\n  @Override\n  public void describeTo(Description description) {\n    description.appendText(\"a GeoCoordinate within \")\n            .appendValue(EPSILON)\n            .appendText(\" of \")\n            .appendValue(expected);\n  }\n\n  @Override\n  protected void describeMismatchSafely(GeoCoordinate actual, Description mismatchDescription) {\n    mismatchDescription.appendText(\"was \").appendValue(actual);\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/GeoRadiusResponseMatcher.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.Arrays;\nimport org.hamcrest.Description;\nimport org.hamcrest.TypeSafeMatcher;\nimport redis.clients.jedis.resps.GeoRadiusResponse;\n\npublic class GeoRadiusResponseMatcher extends TypeSafeMatcher<GeoRadiusResponse> {\n\n    public static GeoRadiusResponseMatcher ofResponse(GeoRadiusResponse expected) {\n        return new GeoRadiusResponseMatcher(expected);\n    }\n\n    private final GeoRadiusResponse expected;\n\n    public GeoRadiusResponseMatcher(GeoRadiusResponse expected) {\n        this.expected = expected;\n    }\n\n    @Override\n    protected boolean matchesSafely(GeoRadiusResponse actual) {\n        // Check if coordinates match within the tolerance\n        if (!GeoCoordinateMatcher.atCoordinates(expected.getCoordinate())\n                .matches(actual.getCoordinate())) {\n            return false;\n        }\n\n        // Check if other attributes match exactly\n        if (Double.compare(expected.getDistance(), actual.getDistance()) != 0) {\n            return false;\n        }\n        if (Long.compare(expected.getRawScore(), actual.getRawScore()) != 0) {\n            return false;\n        }\n        return Arrays.equals(expected.getMember(), actual.getMember());\n    }\n\n    @Override\n    public void describeTo(Description description) {\n        description.appendText(\"a GeoRadiusResponse with coordinate \")\n                .appendValue(expected.getCoordinate())\n                .appendText(\", distance \")\n                .appendValue(expected.getDistance())\n                .appendText(\", rawScore \")\n                .appendValue(expected.getRawScore())\n                .appendText(\"and member \")\n                .appendValue(expected.getMemberByString());\n    }\n\n    @Override\n    protected void describeMismatchSafely(GeoRadiusResponse actual, Description mismatchDescription) {\n        mismatchDescription.appendText(\"was \").appendValue(actual);\n    }\n\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/JedisByteMapMatcher.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.TypeSafeMatcher;\n\nimport java.util.Arrays;\nimport java.util.Map;\n\npublic class JedisByteMapMatcher<T> extends TypeSafeMatcher<Map<byte[], T>> {\n\n  private final Map<byte[], T> expected;\n\n  public JedisByteMapMatcher(Map<byte[], T> expected) {\n    this.expected = expected;\n  }\n\n  @Override\n  protected boolean matchesSafely(Map<byte[], T> actual) {\n    if (actual == null) {\n      return expected == null;\n    }\n\n    if (actual.size() != expected.size()) return false;\n\n    // For each expected key, find the matching key in actual and verify the value matches\n    for (Map.Entry<byte[], T> expectedEntry : expected.entrySet()) {\n      byte[] expectedKey = expectedEntry.getKey();\n      T expectedValue = expectedEntry.getValue();\n\n      // Find the actual entry with matching key\n      boolean keyFound = false;\n      for (Map.Entry<byte[], T> actualEntry : actual.entrySet()) {\n        if (Arrays.equals(expectedKey, actualEntry.getKey())) {\n          keyFound = true;\n          // Verify the value for this key matches\n          if (!expectedValue.equals(actualEntry.getValue())) {\n            return false; // Key found but value doesn't match\n          }\n          break;\n        }\n      }\n\n      if (!keyFound) {\n        return false; // Expected key not found in actual\n      }\n    }\n\n    return true;\n  }\n\n  @Override\n  public void describeTo(Description description) {\n    description.appendText(\"maps to be equal by byte[] content\");\n  }\n\n  public static <T> JedisByteMapMatcher<T> contentEquals(Map<byte[], T> expected) {\n    return new JedisByteMapMatcher<T>(expected);\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/JedisClusterCRC16Test.java",
    "content": "package redis.clients.jedis.util;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport org.junit.jupiter.api.Test;\n\npublic class JedisClusterCRC16Test {\n\n  @Test\n  public void testGetCRC16() {\n    Map<String, Integer> solutions = prepareSolutionSet();\n\n    for (Entry<String, Integer> entry : solutions.entrySet()) {\n      // string version\n      assertEquals(entry.getValue().intValue(), JedisClusterCRC16.getCRC16(entry.getKey()));\n\n      // byte array version\n      assertEquals(entry.getValue().intValue(),\n        JedisClusterCRC16.getCRC16(SafeEncoder.encode(entry.getKey())));\n    }\n  }\n\n  @Test\n  public void testGetSlot() {\n    assertEquals(7186, JedisClusterCRC16.getSlot(\"51\"));\n  }\n\n  private Map<String, Integer> prepareSolutionSet() {\n    Map<String, Integer> solutionMap = new HashMap<String, Integer>();\n    solutionMap.put(\"\", 0x0);\n    solutionMap.put(\"123456789\", 0x31C3);\n    solutionMap.put(\"sfger132515\", 0xA45C);\n    solutionMap.put(\"hae9Napahngaikeethievubaibogiech\", 0x58CE);\n    solutionMap.put(\"AAAAAAAAAAAAAAAAAAAAAA\", 0x92cd);\n    solutionMap.put(\"Hello, World!\", 0x4FD6);\n    return solutionMap;\n  }\n\n  @Test\n  public void testRedisHashtagGetSlot() {\n    assertEquals(JedisClusterCRC16.getSlot(\"{bar\"), JedisClusterCRC16.getSlot(\"foo{{bar}}zap\"));\n    assertEquals(JedisClusterCRC16.getSlot(\"{user1000}.following\"),\n      JedisClusterCRC16.getSlot(\"{user1000}.followers\"));\n    assertNotEquals(JedisClusterCRC16.getSlot(\"foo{}{bar}\"), JedisClusterCRC16.getSlot(\"bar\"));\n    assertEquals(JedisClusterCRC16.getSlot(\"foo{bar}{zap}\"), JedisClusterCRC16.getSlot(\"bar\"));\n  }\n\n  @Test\n  public void testBinaryHashtagGetSlot() {\n    assertEquals(JedisClusterCRC16.getSlot(\"{bar\".getBytes()),\n      JedisClusterCRC16.getSlot(\"{bar\".getBytes()));\n    assertEquals(JedisClusterCRC16.getSlot(\"{user1000}.following\".getBytes()),\n      JedisClusterCRC16.getSlot(\"{user1000}.followers\".getBytes()));\n    assertNotEquals(JedisClusterCRC16.getSlot(\"foo{}{bar}\".getBytes()),\n      JedisClusterCRC16.getSlot(\"bar\".getBytes()));\n    assertEquals(JedisClusterCRC16.getSlot(\"foo{bar}{zap}\".getBytes()),\n      JedisClusterCRC16.getSlot(\"bar\".getBytes()));\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/JedisClusterTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.exceptions.JedisException;\n\npublic class JedisClusterTestUtil {\n  public static void waitForClusterReady(Jedis... nodes) throws InterruptedException {\n    boolean clusterOk = false;\n    while (!clusterOk) {\n      boolean isOk = true;\n      for (Jedis node : nodes) {\n        if (!node.clusterInfo().split(\"\\n\")[0].contains(\"ok\")) {\n          isOk = false;\n          break;\n        }\n      }\n\n      if (isOk) {\n        clusterOk = true;\n      }\n\n      Thread.sleep(50);\n    }\n  }\n\n  public static String getNodeId(String infoOutput) {\n    for (String infoLine : infoOutput.split(\"\\n\")) {\n      if (infoLine.contains(\"myself\")) {\n        return infoLine.split(\" \")[0];\n      }\n    }\n    return \"\";\n  }\n\n  public static String getNodeId(String infoOutput, HostAndPort node) {\n\n    for (String infoLine : infoOutput.split(\"\\n\")) {\n      if (infoLine.contains(node.toString())) {\n        return infoLine.split(\" \")[0];\n      }\n    }\n    return \"\";\n  }\n\n  public static void assertNodeIsKnown(Jedis node, String targetNodeId, int timeoutMs) {\n    assertNodeRecognizedStatus(node, targetNodeId, true, timeoutMs);\n  }\n\n  public static void assertNodeIsUnknown(Jedis node, String targetNodeId, int timeoutMs) {\n    assertNodeRecognizedStatus(node, targetNodeId, false, timeoutMs);\n  }\n\n  private static void assertNodeRecognizedStatus(Jedis node, String targetNodeId,\n      boolean shouldRecognized, int timeoutMs) {\n    int sleepInterval = 100;\n    for (int sleepTime = 0; sleepTime <= timeoutMs; sleepTime += sleepInterval) {\n      boolean known = isKnownNode(node, targetNodeId);\n      if (shouldRecognized == known) return;\n\n      try {\n        Thread.sleep(sleepInterval);\n      } catch (InterruptedException e) {\n      }\n    }\n\n    throw new JedisException(\"Node recognize check error\");\n  }\n\n  private static boolean isKnownNode(Jedis node, String nodeId) {\n    String infoOutput = node.clusterNodes();\n    for (String infoLine : infoOutput.split(\"\\n\")) {\n      if (infoLine.contains(nodeId)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/JedisSentinelTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.JedisPubSub;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.exceptions.FailoverAbortedException;\n\npublic class JedisSentinelTestUtil {\n\n  public static HostAndPort waitForNewPromotedMaster(final String masterName,\n      final Jedis sentinelJedis, final Jedis commandJedis) throws InterruptedException {\n\n    final AtomicReference<String> newmaster = new AtomicReference<String>(\"\");\n\n    sentinelJedis.psubscribe(new JedisPubSub() {\n\n      @Override\n      public void onPMessage(String pattern, String channel, String message) {\n        if (channel.equals(\"+switch-master\")) {\n          newmaster.set(message);\n          punsubscribe();\n        } else if (channel.startsWith(\"-failover-abort\")) {\n          punsubscribe();\n          throw new FailoverAbortedException(\"Unfortunately sentinel cannot failover...\"\n              + \" reason(channel) : \" + channel + \" / message : \" + message);\n        }\n      }\n\n      @Override\n      public void onPSubscribe(String pattern, int subscribedChannels) {\n        commandJedis.sentinelFailover(masterName);\n      }\n    }, \"*\");\n\n    String[] chunks = newmaster.get().split(\" \");\n    HostAndPort newMaster = new HostAndPort(chunks[3], Integer.parseInt(chunks[4]));\n\n    return newMaster;\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/JedisURIHelperTest.java",
    "content": "package redis.clients.jedis.util;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static redis.clients.jedis.util.JedisURIHelper.*;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.RedisProtocol;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.emptyString;\n\npublic class JedisURIHelperTest {\n\n  @Test\n  public void shouldGetUserAndPasswordFromURIWithCredentials() throws URISyntaxException {\n    URI uri = new URI(\"redis://user:password@host:9000/0\");\n    assertEquals(\"user\", JedisURIHelper.getUser(uri));\n    assertEquals(\"password\", JedisURIHelper.getPassword(uri));\n  }\n\n  @Test\n  public void shouldGetNullUserFromURIWithCredentials() throws URISyntaxException {\n    URI uri = new URI(\"redis://:password@host:9000/0\");\n    assertNull(JedisURIHelper.getUser(uri));\n    assertEquals(\"password\", JedisURIHelper.getPassword(uri));\n  }\n\n  @Test\n  public void shouldReturnNullIfURIDoesNotHaveCredentials() throws URISyntaxException {\n    URI uri = new URI(\"redis://host:9000/0\");\n    assertNull(JedisURIHelper.getUser(uri));\n    assertNull(JedisURIHelper.getPassword(uri));\n  }\n\n  @Test\n  public void shouldGetDbFromURIWithCredentials() throws URISyntaxException {\n    URI uri = new URI(\"redis://user:password@host:9000/3\");\n    assertEquals(3, JedisURIHelper.getDBIndex(uri));\n  }\n\n  @Test\n  public void shouldGetDbFromURIWithoutCredentials() throws URISyntaxException {\n    URI uri = new URI(\"redis://host:9000/4\");\n    assertEquals(4, JedisURIHelper.getDBIndex(uri));\n  }\n\n  @Test\n  public void shouldGetDefaultDbFromURIIfNoDbWasSpecified() throws URISyntaxException {\n    URI uri = new URI(\"redis://host:9000\");\n    assertEquals(0, JedisURIHelper.getDBIndex(uri));\n  }\n\n  @Test\n  public void hasDbIndex_shouldReturnFalseIfURIDoesNotHaveDbIndex() throws URISyntaxException {\n    URI uri = new URI(\"redis://host:9000\");\n    assertFalse(JedisURIHelper.hasDbIndex(uri));\n  }\n\n  @Test\n  public void hasDbIndex_shouldReturnTrueIfURIDoesHaveDbIndex() throws URISyntaxException {\n    URI uri = new URI(\"redis://host:9000/3\");\n    assertTrue(JedisURIHelper.hasDbIndex(uri));\n  }\n\n  @Test\n  public void shouldValidateInvalidURIs() throws URISyntaxException {\n    assertFalse(JedisURIHelper.isValid(new URI(\"host:9000\")));\n    assertFalse(JedisURIHelper.isValid(new URI(\"user:password@host:9000/0\")));\n    assertFalse(JedisURIHelper.isValid(new URI(\"host:9000/0\")));\n    assertFalse(JedisURIHelper.isValid(new URI(\"redis://host/0\")));\n  }\n\n  @Test\n  public void shouldGetDefaultProtocolWhenNotDefined() {\n    assertNull(getRedisProtocol(URI.create(\"redis://host:1234\")));\n    assertNull(getRedisProtocol(URI.create(\"redis://host:1234/1\")));\n  }\n\n  @Test\n  public void shouldGetProtocolFromDefinition() {\n    assertEquals(RedisProtocol.RESP3, getRedisProtocol(URI.create(\"redis://host:1234?protocol=3\")));\n    assertEquals(RedisProtocol.RESP3,\n      getRedisProtocol(URI.create(\"redis://host:1234/?protocol=3\")));\n    assertEquals(RedisProtocol.RESP3,\n      getRedisProtocol(URI.create(\"redis://host:1234/1?protocol=3\")));\n    assertEquals(RedisProtocol.RESP3,\n      getRedisProtocol(URI.create(\"redis://host:1234/1/?protocol=3\")));\n  }\n\n  @Test\n  public void emptyPassword() {\n    // ensure we can provide an empty password for default user\n    assertThat(JedisURIHelper.getPassword(URI.create(\"redis://:@host:9000/0\")), emptyString());\n\n    // ensure we can provide an empty password for user\n    assertEquals(JedisURIHelper.getUser(URI.create(\"redis://username:@host:9000/0\")), \"username\");\n    assertThat(JedisURIHelper.getPassword(URI.create(\"redis://username:@host:9000/0\")),\n      emptyString());\n  }\n\n  @Test\n  public void shouldThrowIfNoPasswordInURI() throws URISyntaxException {\n    // ensure we throw if user is provided but password is missing in URI\n    URI uri = new URI(\"redis://user@host:9000/0\");\n    IllegalArgumentException illegalArgumentException = assertThrows(IllegalArgumentException.class,\n      () -> getPassword(uri));\n    assertEquals(\"Password not provided in uri.\", illegalArgumentException.getMessage());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/JsonObjectMapperTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializationFeature;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport com.google.gson.*;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.json.JsonObjectMapper;\n\nimport java.lang.reflect.Type;\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.TemporalAccessor;\n\npublic class JsonObjectMapperTestUtil {\n  public static CustomJacksonObjectMapper getCustomJacksonObjectMapper() {\n    ObjectMapper om = new ObjectMapper();\n    om.registerModule(new JavaTimeModule());\n    om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);\n    return new CustomJacksonObjectMapper(om);\n  }\n\n  public static CustomGsonObjectMapper getCustomGsonObjectMapper() {\n    final class InstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {\n      DateTimeFormatter format = DateTimeFormatter.ISO_INSTANT;\n\n      @Override\n      public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)\n          throws JsonParseException {\n        JsonPrimitive primitive = json.getAsJsonPrimitive();\n        if (!primitive.isJsonNull()) {\n          String asString = primitive.getAsString();\n          TemporalAccessor temporalAccessor = format.parse(asString);\n          return Instant.from(temporalAccessor);\n        }\n        return null;\n      }\n\n      @Override\n      public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {\n        return new JsonPrimitive(format.format(src));\n      }\n    }\n    return new CustomGsonObjectMapper(\n        new GsonBuilder().registerTypeAdapter(Instant.class, new InstantAdapter()).create());\n  }\n\n  public static class CustomJacksonObjectMapper implements JsonObjectMapper {\n    private final ObjectMapper om;\n\n    CustomJacksonObjectMapper(ObjectMapper om) {\n      this.om = om;\n    }\n\n    @Override\n    public <T> T fromJson(String value, Class<T> valueType) {\n      try {\n        return om.readValue(value, valueType);\n      } catch (JsonProcessingException e) {\n        throw new JedisException(e);\n      }\n    }\n\n    @Override\n    public String toJson(Object value) {\n      try {\n        return om.writeValueAsString(value);\n      } catch (JsonProcessingException e) {\n        throw new JedisException(e);\n      }\n    }\n  }\n\n  public static class CustomGsonObjectMapper implements JsonObjectMapper {\n    private final Gson gson;\n\n    public CustomGsonObjectMapper(Gson gson) {\n      this.gson = gson;\n    }\n\n    @Override\n    public <T> T fromJson(String value, Class<T> valueType) {\n      return gson.fromJson(value, valueType);\n    }\n\n    @Override\n    public String toJson(Object value) {\n      return gson.toJson(value);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ProtocolTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.Protocol;\n\n/**\n * Utility class for testing Redis protocol commands and serialization.\n * <p>\n * Provides helper methods to capture and inspect the RESP protocol output that would be sent to\n * Redis servers.\n * </p>\n */\npublic final class ProtocolTestUtil {\n\n  private ProtocolTestUtil() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  /**\n   * Captures the RESP protocol output that would be sent to Redis.\n   * <p>\n   * This method serializes the CommandArguments using the actual Protocol.sendCommand() method and\n   * returns the raw RESP protocol string that would be sent over the wire.\n   * </p>\n   * @param args the command arguments to serialize\n   * @return the RESP protocol string (e.g., \"*3\\r\\n$6\\r\\nZRANGE\\r\\n$10\\r\\n3000000000\\r\\n...\")\n   * @throws AssertionError if an I/O error occurs during serialization (should never happen with\n   *           ByteArrayOutputStream)\n   */\n  public static String captureCommandOutput(CommandArguments args) {\n    ByteArrayOutputStream baos = new ByteArrayOutputStream();\n    RedisOutputStream ros = new RedisOutputStream(baos);\n\n    try {\n      Protocol.sendCommand(ros, args);\n      ros.flush();\n    } catch (IOException e) {\n      // This should never happen with ByteArrayOutputStream, but if it does, it's a test setup\n      // error\n      throw new AssertionError(\"Failed to serialize command arguments\", e);\n    }\n\n    return baos.toString();\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/RedisConditions.java",
    "content": "package redis.clients.jedis.util;\n\nimport io.redis.test.utils.RedisVersion;\nimport redis.clients.jedis.CommandArguments;\nimport redis.clients.jedis.CommandObject;\nimport redis.clients.jedis.Module;\nimport redis.clients.jedis.UnifiedJedis;\nimport redis.clients.jedis.resps.CommandInfo;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static redis.clients.jedis.BuilderFactory.MODULE_LIST;\nimport static redis.clients.jedis.Protocol.Command.COMMAND;\nimport static redis.clients.jedis.Protocol.Command.MODULE;\nimport static redis.clients.jedis.Protocol.Keyword.LIST;\n\npublic class RedisConditions {\n\n  public enum ModuleVersion {\n\n    SEARCH_MOD_VER_80M3(\"SEARCH\", 79903),\n    SEARCH_MOD_VER_84RC1(\"SEARCH\", 80390);\n\n    private final String moduleName;\n    private final int version;\n\n    ModuleVersion(String moduleName, int version) {\n      this.moduleName = moduleName;\n      this.version = version;\n    }\n\n    public String getModuleName() {\n      return moduleName;\n    }\n\n    public int getVersion() {\n      return version;\n    }\n  }\n\n  private final RedisVersion version;\n  private final Map<String, Integer> modules;\n  private final Map<String, CommandInfo> commands;\n\n  private RedisConditions(RedisVersion version, Map<String, CommandInfo> commands,\n      Map<String, Integer> modules) {\n    this.version = version;\n    this.commands = commands;\n    this.modules = modules;\n  }\n\n  public static RedisConditions of(UnifiedJedis jedis) {\n    RedisVersion version = RedisVersionUtil.getRedisVersion(jedis);\n\n    CommandObject<Map<String, CommandInfo>> commandInfoCmd = new CommandObject<>(\n        new CommandArguments(COMMAND), CommandInfo.COMMAND_INFO_RESPONSE);\n    Map<String, CommandInfo> commands = jedis.executeCommand(commandInfoCmd);\n\n    CommandObject<List<Module>> moduleListCmd = new CommandObject<>(\n        new CommandArguments(MODULE).add(LIST), MODULE_LIST);\n\n    Map<String, Integer> modules = jedis.executeCommand(moduleListCmd).stream()\n        .collect(Collectors.toMap((m) -> m.getName().toUpperCase(), Module::getVersion));\n\n    return new RedisConditions(version, commands, modules);\n  }\n\n  public RedisVersion getVersion() {\n    return version;\n  }\n\n  /**\n   * @param command\n   * @return {@code true} if the command is present.\n   */\n  public boolean hasCommand(String command) {\n    return commands.containsKey(command.toUpperCase());\n  }\n\n  /**\n   * @param module\n   * @return {@code true} if the module is present.\n   */\n  public boolean hasModule(String module) {\n    return modules.containsKey(module.toUpperCase());\n  }\n\n  /**\n   * @param module\n   * @param version\n   * @return {@code true} if the module with the requested minimum version is present.\n   */\n  public boolean moduleVersionIsGreaterThanOrEqual(String module, int version) {\n    Integer moduleVersion = modules.get(module.toUpperCase());\n    return moduleVersion != null && moduleVersion >= version;\n  }\n\n  /**\n   * @param moduleVersion\n   * @return {@code true} if the module version is greater than or equal to the specified version.\n   */\n  public boolean moduleVersionIsGreaterThanOrEqual(ModuleVersion moduleVersion) {\n    return moduleVersionIsGreaterThanOrEqual(moduleVersion.getModuleName(),\n        moduleVersion.getVersion());\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/RedisVersionCondition.java",
    "content": "package redis.clients.jedis.util;\n\nimport io.redis.test.annotations.SinceRedisVersion;\nimport io.redis.test.utils.RedisInfo;\nimport io.redis.test.utils.RedisVersion;\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExecutionCondition;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.platform.commons.util.AnnotationUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport redis.clients.jedis.EndpointConfig;\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisClientConfig;\n\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\nimport static redis.clients.jedis.util.RedisVersionUtil.forcedVersion;\n\npublic class RedisVersionCondition implements ExecutionCondition {\n  private static final Logger logger = LoggerFactory.getLogger(RedisVersionCondition.class);\n\n  private final Supplier<EndpointConfig> endpointSupplier;\n  private HostAndPort hostPort;\n  private JedisClientConfig config;\n\n  public RedisVersionCondition(Supplier<EndpointConfig> endpointSupplier) {\n    this.endpointSupplier = endpointSupplier;\n    this.hostPort = null;\n    this.config = null;\n  }\n\n  public RedisVersionCondition(HostAndPort hostPort, JedisClientConfig config) {\n    this.endpointSupplier = null;\n    this.hostPort = hostPort;\n    this.config = config;\n  }\n\n  private void ensureInitialized() {\n    if (hostPort == null && endpointSupplier != null) {\n      EndpointConfig endpoint = endpointSupplier.get();\n      this.hostPort = endpoint.getHostAndPort();\n      this.config = endpoint.getClientConfigBuilder().build();\n    }\n  }\n\n  @Override\n  public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {\n    ensureInitialized();\n    try (Jedis jedisClient = new Jedis(hostPort, config)) {\n      SinceRedisVersion versionAnnotation = getAnnotation(context);\n      if (versionAnnotation != null) {\n        RedisVersion currentVersion;\n\n        if (forcedVersion != null) {\n          logger.info(\"Using forced Redis server version from environment variable: \" + forcedVersion);\n          currentVersion = forcedVersion;\n        } else {\n          RedisInfo info = RedisInfo.parseInfoServer(jedisClient.info(\"server\"));\n          currentVersion = RedisVersion.of(info.getRedisVersion());\n        }\n\n        RedisVersion minRequiredVersion = RedisVersion.of(versionAnnotation.value());\n        if (currentVersion.isLessThan(minRequiredVersion)) {\n          return ConditionEvaluationResult.disabled(\"Test requires Redis version \" + minRequiredVersion + \" or later, but found \" + currentVersion);\n        }\n      }\n    } catch (Exception e) {\n      return ConditionEvaluationResult.disabled(\"Failed to check Redis version: \" + e.getMessage());\n    }\n    return ConditionEvaluationResult.enabled(\"Redis version is sufficient\");\n  }\n\n  private SinceRedisVersion getAnnotation(ExtensionContext context) {\n    Optional<SinceRedisVersion> methodAnnotation = AnnotationUtils.findAnnotation(context.getTestMethod(), SinceRedisVersion.class);\n    if (methodAnnotation.isPresent()) {\n      return methodAnnotation.get();\n    }\n\n    Optional<SinceRedisVersion> classAnnotation = AnnotationUtils.findAnnotation(context.getRequiredTestClass(), SinceRedisVersion.class);\n    return classAnnotation.orElse(null);\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/RedisVersionUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport io.redis.test.utils.RedisInfo;\nimport io.redis.test.utils.RedisVersion;\nimport redis.clients.jedis.*;\n\npublic class RedisVersionUtil {\n\n  static final String FORCE_REDIS_SERVER_VERSION_ENV = \"forceRedisServerVersion\";\n\n  static final RedisVersion forcedVersion = System.getenv(FORCE_REDIS_SERVER_VERSION_ENV) != null\n          ? RedisVersion.of(System.getenv(FORCE_REDIS_SERVER_VERSION_ENV))\n          : null;\n\n  public static RedisVersion getRedisVersion(Connection conn) {\n    if (forcedVersion != null) {\n      return forcedVersion;\n    }\n\n    try (Jedis jedis = new Jedis(conn)) {\n      return getRedisVersion(jedis);\n    }\n  }\n\n  public static RedisVersion getRedisVersion(UnifiedJedis jedis) {\n    if (forcedVersion != null) {\n      return forcedVersion;\n    }\n\n    Object response = SafeEncoder.encodeObject(jedis.sendCommand(Protocol.Command.INFO, \"server\"));\n    RedisInfo info = RedisInfo.parseInfoServer(response.toString());\n    return RedisVersion.of(info.getRedisVersion());\n  }\n\n  public static RedisVersion getRedisVersion(Jedis jedis) {\n    if (forcedVersion != null) {\n      return forcedVersion;\n    }\n\n    RedisInfo info = RedisInfo.parseInfoServer(jedis.info(\"server\"));\n    return RedisVersion.of(info.getRedisVersion());\n  }\n\n  public static RedisVersion getRedisVersion(EndpointConfig endpoint) {\n    if (forcedVersion != null) {\n      return forcedVersion;\n    }\n\n    try (Jedis jedis = new Jedis(endpoint.getHostAndPort(),\n        endpoint.getClientConfigBuilder().build())) {\n      return getRedisVersion(jedis);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/ReflectionTestUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * Simple utility for accessing private fields in tests using reflection.\n * <p>\n * This utility is intended for testing purposes only to access internal state that is not exposed\n * through public APIs.\n * </p>\n */\npublic class ReflectionTestUtil {\n\n  /**\n   * Gets the value of a private field from an object.\n   * @param target the object containing the field\n   * @param fieldName the name of the field to access\n   * @param <T> the expected type of the field value\n   * @return the value of the field\n   * @throws RuntimeException if the field cannot be accessed\n   */\n  @SuppressWarnings(\"unchecked\")\n  public static <T> T getField(Object target, String fieldName) {\n    if (target == null) {\n      throw new IllegalArgumentException(\"Target object cannot be null\");\n    }\n    if (fieldName == null || fieldName.isEmpty()) {\n      throw new IllegalArgumentException(\"Field name cannot be null or empty\");\n    }\n\n    try {\n      Field field = findField(target.getClass(), fieldName);\n      field.setAccessible(true);\n      return (T) field.get(target);\n    } catch (NoSuchFieldException e) {\n      throw new RuntimeException(\n          \"Field '\" + fieldName + \"' not found in class \" + target.getClass().getName(), e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(\n          \"Cannot access field '\" + fieldName + \"' in class \" + target.getClass().getName(), e);\n    }\n  }\n\n  /**\n   * Sets the value of a private field in an object.\n   * @param target the object containing the field\n   * @param fieldName the name of the field to set\n   * @param value the value to set\n   * @throws RuntimeException if the field cannot be accessed\n   */\n  public static void setField(Object target, String fieldName, Object value) {\n    if (target == null) {\n      throw new IllegalArgumentException(\"Target object cannot be null\");\n    }\n    if (fieldName == null || fieldName.isEmpty()) {\n      throw new IllegalArgumentException(\"Field name cannot be null or empty\");\n    }\n\n    try {\n      Field field = findField(target.getClass(), fieldName);\n      field.setAccessible(true);\n      field.set(target, value);\n    } catch (NoSuchFieldException e) {\n      throw new RuntimeException(\n          \"Field '\" + fieldName + \"' not found in class \" + target.getClass().getName(), e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(\n          \"Cannot access field '\" + fieldName + \"' in class \" + target.getClass().getName(), e);\n    }\n  }\n\n  /**\n   * Finds a field in the class hierarchy.\n   * @param clazz the class to search\n   * @param fieldName the name of the field\n   * @return the field\n   * @throws NoSuchFieldException if the field is not found\n   */\n  private static Field findField(Class<?> clazz, String fieldName) throws NoSuchFieldException {\n    Class<?> current = clazz;\n    while (current != null) {\n      try {\n        return current.getDeclaredField(fieldName);\n      } catch (NoSuchFieldException e) {\n        current = current.getSuperclass();\n      }\n    }\n    throw new NoSuchFieldException(\n        \"Field '\" + fieldName + \"' not found in class hierarchy of \" + clazz.getName());\n  }\n\n  /**\n   * Invokes a private method on an object.\n   * @param target the object on which to invoke the method\n   * @param methodName the name of the method to invoke\n   * @param parameterTypes the parameter types of the method\n   * @param args the arguments to pass to the method\n   * @param <T> the expected return type\n   * @return the result of the method invocation\n   * @throws RuntimeException if the method cannot be invoked\n   */\n  @SuppressWarnings(\"unchecked\")\n  public static <T> T invokeMethod(Object target, String methodName, Class<?>[] parameterTypes,\n      Object... args) {\n    if (target == null) {\n      throw new IllegalArgumentException(\"Target object cannot be null\");\n    }\n    if (methodName == null || methodName.isEmpty()) {\n      throw new IllegalArgumentException(\"Method name cannot be null or empty\");\n    }\n\n    try {\n      Method method = findMethod(target.getClass(), methodName, parameterTypes);\n      method.setAccessible(true);\n      return (T) method.invoke(target, args);\n    } catch (NoSuchMethodException e) {\n      throw new RuntimeException(\n          \"Method '\" + methodName + \"' not found in class \" + target.getClass().getName(), e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(\n          \"Cannot access method '\" + methodName + \"' in class \" + target.getClass().getName(), e);\n    } catch (InvocationTargetException e) {\n      Throwable cause = e.getCause();\n      if (cause instanceof RuntimeException) {\n        throw (RuntimeException) cause;\n      }\n      throw new RuntimeException(\n          \"Exception thrown by method '\" + methodName + \"' in class \" + target.getClass().getName(),\n          cause);\n    }\n  }\n\n  /**\n   * Finds a method in the class hierarchy.\n   * @param clazz the class to search\n   * @param methodName the name of the method\n   * @param parameterTypes the parameter types of the method\n   * @return the method\n   * @throws NoSuchMethodException if the method is not found\n   */\n  private static Method findMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes)\n      throws NoSuchMethodException {\n    Class<?> current = clazz;\n    while (current != null) {\n      try {\n        return current.getDeclaredMethod(methodName, parameterTypes);\n      } catch (NoSuchMethodException e) {\n        current = current.getSuperclass();\n      }\n    }\n    throw new NoSuchMethodException(\n        \"Method '\" + methodName + \"' not found in class hierarchy of \" + clazz.getName());\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/StreamEntryBinaryListMatcher.java",
    "content": "package redis.clients.jedis.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.TypeSafeMatcher;\nimport redis.clients.jedis.resps.StreamEntryBinary;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Arrays;\n\npublic class StreamEntryBinaryListMatcher extends TypeSafeMatcher<List<StreamEntryBinary>> {\n\n  private final List<StreamEntryBinary> expected;\n\n  public StreamEntryBinaryListMatcher(List<StreamEntryBinary> expected) {\n    this.expected = expected;\n  }\n\n  @Override\n  protected boolean matchesSafely(List<StreamEntryBinary> actual) {\n    if (actual.size() != expected.size()) return false;\n\n    for (int i = 0; i < expected.size(); i++) {\n      StreamEntryBinary e = expected.get(i);\n      StreamEntryBinary a = actual.get(i);\n\n      if (!e.getID().equals(a.getID())) return false;\n      if (!mapsEqual(e.getFields(), a.getFields())) return false;\n    }\n\n    return true;\n  }\n\n  private boolean mapsEqual(Map<byte[], byte[]> m1, Map<byte[], byte[]> m2) {\n    if (m1.size() != m2.size()) return false;\n\n    outer:\n    for (Map.Entry<byte[], byte[]> e1 : m1.entrySet()) {\n      for (Map.Entry<byte[], byte[]> e2 : m2.entrySet()) {\n        if (Arrays.equals(e1.getKey(), e2.getKey()) &&\n            Arrays.equals(e1.getValue(), e2.getValue())) {\n          continue outer;\n        }\n      }\n      return false;\n    }\n\n    return true;\n  }\n\n  @Override\n  public void describeTo(Description description) {\n    description.appendText(\"StreamEntryBinary lists to match by ID and field content\");\n  }\n\n  public static StreamEntryBinaryListMatcher equalsStreamEntries(List<StreamEntryBinary> expected) {\n    return new StreamEntryBinaryListMatcher(expected);\n  }\n}"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/TestDataUtil.java",
    "content": "package redis.clients.jedis.util;\n\n/**\n * Utility class for generating test data.\n */\npublic class TestDataUtil {\n\n  private TestDataUtil() {\n    throw new InstantiationError(\"Must not instantiate this class\");\n  }\n\n  /**\n   * Generates a string of a specific size filled with a repeated character.\n   * @param size The desired size in characters\n   * @param fillChar The character to fill the string with\n   * @return A string of the specified size\n   */\n  public static String generateString(int size, char fillChar) {\n    StringBuilder value = new StringBuilder(size);\n    for (int i = 0; i < size; i++) {\n      value.append(fillChar);\n    }\n    return value.toString();\n  }\n\n  /**\n   * Generates a string of a specific size filled with 'x' characters.\n   * @param size The desired size in characters\n   * @return A string of the specified size filled with 'x'\n   */\n  public static String generateString(int size) {\n    return generateString(size, 'x');\n  }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/TestEnvUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.util.Optional;\n\npublic class TestEnvUtil {\n    // Redis servers running inside docker\n    public static final String ENV_OSS_DOCKER = \"oss-docker\";\n\n    public static final String ENV_OSS_SOURCE = \"oss-source\";\n\n    public static final String ENV_REDIS_ENTERPRISE = \"re\";\n\n    private static final String TEST_ENV_PROVIDER = System.getenv().getOrDefault(\"TEST_ENV_PROVIDER\",\n        ENV_OSS_DOCKER);\n\n    private static final String TESTMODULE_SO_PATH = Optional.ofNullable(System.getenv(\"TESTMODULE_SO\"))\n            .orElseGet(() -> isContainerEnv()\n                    ? \"/redis/work/modules/testmodule.so\"\n                    : \"/tmp/testmodule.so\");\n\n    private static final String ENDPOINTS_CONFIG_PATH = Optional.ofNullable(System.getenv(\"REDIS_ENDPOINTS_CONFIG_PATH\"))\n      .orElseGet(() -> TEST_ENV_PROVIDER.equals(ENV_OSS_SOURCE)\n          ? \"src/test/resources/endpoints_source.json\"\n          : \"src/test/resources/endpoints.json\");\n\n    public static boolean isContainerEnv() {\n        return TEST_ENV_PROVIDER.equals(ENV_OSS_DOCKER);\n    }\n\n    public static String getTestEnvProvider() {\n        return TEST_ENV_PROVIDER;\n    }\n\n    public static String testModuleSoPath() {\n        return TESTMODULE_SO_PATH;\n    }\n\n    public static String getEndpointsConfigPath() {\n        return ENDPOINTS_CONFIG_PATH;\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/TlsUtil.java",
    "content": "package redis.clients.jedis.util;\n\nimport javax.net.ssl.*;\nimport java.io.*;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.*;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class TlsUtil {\n\n    private static final String TRUST_STORE_PROPERTY = \"javax.net.ssl.trustStore\";\n    private static final String TRUST_STORE_PASSWORD_PROPERTY = \"javax.net.ssl.trustStorePassword\";\n    private static final String TRUST_STORE_TYPE_PROPERTY = \"javax.net.ssl.trustStoreType\";\n\n    private static String originalTrustStore;\n    private static String originalTrustStoreType;\n    private static String originalTrustStorePassword;\n\n    private static final String TRUST_STORE_TYPE = \"JCEKS\";\n    private static final String CERTIFICATE_TYPE = \"X.509\";\n\n    private static final String TEST_WORK_FOLDER = System.getenv().getOrDefault(\"TEST_WORK_FOLDER\", \"/tmp/redis-env-work\");\n    private static final String TEST_TRUSTSTORE = System.getenv().getOrDefault(\"TEST_TRUSTSTORE\", \"truststore.jceks\");\n    private static final String TEST_CA_CERT = System.getenv().getOrDefault(\"TEST_CA_CERT\", \"ca.crt\");\n    private static final String TEST_SERVER_CERT = System.getenv().getOrDefault(\"TEST_SERVER_CERT\", \"redis.crt\");\n\n    public static void setCustomTrustStore(Path customTrustStorePath, String customTrustStorePassword) {\n        // Store original properties\n        originalTrustStore = System.getProperty(TRUST_STORE_PROPERTY);\n        originalTrustStorePassword = System.getProperty(TRUST_STORE_PASSWORD_PROPERTY);\n        originalTrustStoreType = System.getProperty(TRUST_STORE_TYPE_PROPERTY);\n        // Set new properties for the custom truststore\n        System.setProperty(TRUST_STORE_PROPERTY, customTrustStorePath.toAbsolutePath().toString());\n        System.setProperty(TRUST_STORE_TYPE_PROPERTY, TRUST_STORE_TYPE);\n        if (customTrustStorePassword != null) {\n            System.setProperty(TRUST_STORE_PASSWORD_PROPERTY, customTrustStorePassword);\n        } else {\n            System.clearProperty(TRUST_STORE_PASSWORD_PROPERTY);\n        }\n\n        reinitializeDefaultSSLContext();\n    }\n\n    public static void reinitializeDefaultSSLContext(){\n        String trustStorePath = System.getProperty(TRUST_STORE_PROPERTY);\n        String trustStorePassword =  System.getProperty(TRUST_STORE_PASSWORD_PROPERTY);\n        String trustStoreType = System.getProperty(TRUST_STORE_TYPE_PROPERTY, KeyStore.getDefaultType());\n        // Load the new truststore\n        KeyStore trustStore = null;\n        try {\n            trustStore = KeyStore.getInstance(trustStoreType);\n            try (java.io.FileInputStream trustStoreStream = new java.io.FileInputStream(trustStorePath)) {\n                trustStore.load(trustStoreStream, trustStorePassword.toCharArray());\n            } catch (CertificateException | IOException | NoSuchAlgorithmException e) {\n                throw new RuntimeException(e);\n            }\n\n            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            tmf.init(trustStore);\n\n            SSLContext newSslContext = SSLContext.getInstance(\"TLS\");\n            newSslContext.init(null, tmf.getTrustManagers(), null);\n            SSLContext.setDefault(newSslContext);\n            } catch (KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) {\n                throw new RuntimeException(e);\n            }\n    }\n\n    public static void restoreOriginalTrustStore() {\n        // Restore original properties\n        if (originalTrustStore != null) {\n            System.setProperty(TRUST_STORE_PROPERTY, originalTrustStore);\n        } else {\n            System.clearProperty(TRUST_STORE_PROPERTY);\n        }\n\n        if ( originalTrustStoreType != null) {\n            System.setProperty(TRUST_STORE_TYPE_PROPERTY, originalTrustStoreType);\n        } else {\n            System.clearProperty(TRUST_STORE_TYPE_PROPERTY);\n        }\n\n        if (originalTrustStorePassword != null) {\n            System.setProperty(TRUST_STORE_PASSWORD_PROPERTY, originalTrustStorePassword);\n        } else {\n            System.clearProperty(TRUST_STORE_PASSWORD_PROPERTY);\n        }\n    }\n\n    private static Path envCa(Path certLocation) {\n        if (certLocation.isAbsolute()) {\n            return certLocation.resolve(TEST_CA_CERT);\n        }\n        return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), TEST_CA_CERT);\n    }\n\n    private static Path envServerCert(Path certLocation) {\n        if (certLocation.isAbsolute()) {\n          return certLocation.resolve(TEST_SERVER_CERT);\n        }\n        return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), TEST_SERVER_CERT);\n    }\n\n    /**\n     * Resolves the path to a pre-generated PKCS12 keystore for mTLS client authentication.\n     * The Docker container generates .p12 files for each TLS_CLIENT_CNS entry.\n     *\n     * @param certLocation the certificate location from EndpointConfig\n     * @param clientName the name of the client certificate (without extension)\n     * @return the absolute path to the .p12 keystore file\n     */\n    public static Path clientKeystorePath(Path certLocation, String clientName) {\n        if (certLocation.isAbsolute()) {\n            return certLocation.resolve(clientName + \".p12\");\n        }\n        return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), clientName + \".p12\");\n    }\n\n    public static Path testTruststorePath(String name) {\n        return Paths.get(TEST_WORK_FOLDER, name  + '-' + TEST_TRUSTSTORE);\n    }\n\n    public static Path createAndSaveTestTruststore(String trustStoreName, List<Path> certificateLocations, String truststorePassword) {\n        List<Path> trustedCertPaths = new ArrayList<>();\n\n        // Traverse each location in certificateLocations and add both CA and Server certificates\n        for (Path location : certificateLocations) {\n            trustedCertPaths.add(envCa(location).toAbsolutePath());\n            trustedCertPaths.add(envServerCert(location).toAbsolutePath());\n        }\n\n        Path trustStorePath = testTruststorePath(trustStoreName).toAbsolutePath();\n\n        return createAndSaveTruststore(trustedCertPaths, trustStorePath, truststorePassword);\n    }\n\n    /**\n     * Creates an empty truststore.\n     *\n     * @return An empty KeyStore object.\n     * @throws KeyStoreException If there's an error initializing the truststore.\n     * @throws IOException        If there's an error loading the truststore.\n     * @throws NoSuchAlgorithmException If the algorithm used to check the integrity of the truststore cannot be found.\n     * @throws CertificateException If any of the certificates in the truststore could not be loaded.\n     */\n    private static KeyStore createTruststore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {\n        KeyStore trustStore = KeyStore.getInstance(TRUST_STORE_TYPE);\n        trustStore.load(null, null);\n        return trustStore;\n    }\n\n    /**\n     * Adds a trusted certificate to the given truststore.\n     *\n     * @param trustStore The KeyStore object.\n     * @param alias      Alias for the certificate.\n     * @param certPath   Path to the certificate file.\n     * @throws Exception If there's an error adding the certificate.\n     */\n    private static void addTrustedCertificate(KeyStore trustStore, String alias, Path certPath) throws Exception {\n        X509Certificate cert = loadCertificate(certPath);\n        trustStore.setCertificateEntry(alias, cert);\n    }\n\n    /**\n     * Loads an X.509 certificate from the given file path.\n     *\n     * @param certPath Path to the certificate file.\n     * @return An X509Certificate object.\n     * @throws Exception If there's an error loading the certificate.\n     */\n    private static X509Certificate loadCertificate(Path certPath) throws Exception {\n        try (FileInputStream fis = new FileInputStream(certPath.toFile())) {\n            CertificateFactory certFactory = CertificateFactory.getInstance(\"X.509\");\n            return (X509Certificate) certFactory.generateCertificate(fis);\n        }\n    }\n\n    /**\n     * Creates a truststore, adds multiple trusted certificates, and saves it to the specified path.\n     *\n     * @param trustedCertPaths   List of certificate file paths to add to the truststore.\n     * @param truststorePath     Path to save the generated truststore.\n     * @param truststorePassword Password for the truststore.\n     * @return Path to the saved truststore file.\n     */\n    public static Path createAndSaveTruststore(List<Path> trustedCertPaths, Path truststorePath, String truststorePassword) {\n        try {\n            KeyStore trustStore = createTruststore();\n\n            for (Path certPath : trustedCertPaths) {\n                addTrustedCertificate(trustStore, \"trusted-cert-\" + UUID.randomUUID(), certPath);\n            }\n\n            try (FileOutputStream fos = new FileOutputStream(truststorePath.toFile())) {\n                trustStore.store(fos, truststorePassword.toCharArray());\n            } catch (IOException e) {\n                throw new RuntimeException(\"Failed to save truststore to \" + truststorePath + \": \" + e.getMessage(), e);\n            }\n        } catch (Exception e) {\n            throw new RuntimeException(\"Failed to create and save truststore: \" + e.getMessage(), e);\n        }\n\n        return truststorePath;\n    }\n\n\n    /**\n     * Creates an SSLSocketFactory that trusts all certificates in truststore.jceks.\n     * for given test environment\n     */\n    public static SSLSocketFactory sslSocketFactoryForEnv(Path certLocations){\n       return sslSocketFactory(envCa(certLocations));\n    }\n\n    /**\n     * Returns SSLSocketFactory configured with Truststore containing provided CA cert\n     */\n    private static SSLSocketFactory sslSocketFactory(Path trustedCertPath) {\n\n        KeyStore trustStore = null;\n        try {\n            trustStore = createTruststore();\n            addTrustedCertificate(trustStore, \"trusted-cert-\" + UUID.randomUUID(), trustedCertPath.toAbsolutePath());\n\n            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(\"PKIX\");\n            trustManagerFactory.init(trustStore);\n            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();\n\n            SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n            sslContext.init(null, trustManagers, new SecureRandom());\n            return sslContext.getSocketFactory();\n        } catch (Exception e) {\n            throw new RuntimeException(\"Failed to initialise SslSocketFactory for \" + trustedCertPath , e);\n        }\n\n    }\n\n    /**\n     * Creates an SSLSocketFactory with a trust manager that does not trust any certificates.\n     */\n    public static SSLSocketFactory createTrustNoOneSslSocketFactory() throws Exception {\n        TrustManager[] unTrustManagers = new TrustManager[]{new X509TrustManager() {\n            public X509Certificate[] getAcceptedIssuers() {\n                return new X509Certificate[0];\n            }\n\n            public void checkClientTrusted(X509Certificate[] chain, String authType)\n                throws CertificateException {\n                throw new CertificateException(\"Using a trust manager that does not trust any certificates for test purposes!\",new InvalidAlgorithmParameterException());\n            }\n\n            public void checkServerTrusted(X509Certificate[] chain, String authType)\n                throws CertificateException {\n                throw new CertificateException(\"Using a trust manager that does not trust any certificates for test purposes!\", new InvalidAlgorithmParameterException());\n            }\n        }};\n        SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n        sslContext.init(null, unTrustManagers, new SecureRandom());\n        return sslContext.getSocketFactory();\n    }\n\n    public static void createEmptyTruststore(Path emptyTrustStore, char[] trustStorePassword) throws Exception {\n      // Create empty truststore\n      KeyStore ks = KeyStore.getInstance(\"JKS\");\n      ks.load(null, trustStorePassword);\n\n      // Save truststore\n      try (FileOutputStream fos = new FileOutputStream(emptyTrustStore.toFile())) {\n        ks.store(fos, trustStorePassword);\n      }\n    }\n\n    /**\n     * Very basic hostname verifier implementation for testing. NOT recommended for production.\n     */\n    public static class BasicHostnameVerifier implements HostnameVerifier {\n\n        private static final String COMMON_NAME_RDN_PREFIX = \"CN=\";\n\n        @Override\n        public boolean verify(String hostname, SSLSession session) {\n            X509Certificate peerCertificate;\n            try {\n                peerCertificate = (X509Certificate) session.getPeerCertificates()[0];\n            } catch (SSLPeerUnverifiedException e) {\n                throw new IllegalStateException(\"The session does not contain a peer X.509 certificate.\", e);\n            }\n            String peerCertificateCN = getCommonName(peerCertificate);\n            return hostname.equals(peerCertificateCN);\n        }\n\n        private String getCommonName(X509Certificate peerCertificate) {\n            String subjectDN = peerCertificate.getSubjectDN().getName();\n            String[] dnComponents = subjectDN.split(\",\");\n            for (String dnComponent : dnComponents) {\n                dnComponent = dnComponent.trim();\n                if (dnComponent.startsWith(COMMON_NAME_RDN_PREFIX)) {\n                    return dnComponent.substring(COMMON_NAME_RDN_PREFIX.length());\n                }\n            }\n            throw new IllegalArgumentException(\"The certificate has no common name.\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/redis/clients/jedis/util/VectorTestUtils.java",
    "content": "package redis.clients.jedis.util;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Utility class for vector-related test operations. Provides methods for converting between float\n * arrays and FP32 byte representations.\n */\npublic class VectorTestUtils {\n\n  /**\n   * Convert float array to FP32 byte blob (IEEE 754 format). Each float is converted to 4 bytes in\n   * little-endian order.\n   * @param floats the float array to convert\n   * @return byte array containing FP32 representation\n   */\n  public static byte[] floatArrayToFP32Bytes(float[] floats) {\n    byte[] bytes = new byte[floats.length * 4]; // 4 bytes per float\n    for (int i = 0; i < floats.length; i++) {\n      int bits = Float.floatToIntBits(floats[i]);\n      bytes[i * 4] = (byte) (bits & 0xFF);\n      bytes[i * 4 + 1] = (byte) ((bits >> 8) & 0xFF);\n      bytes[i * 4 + 2] = (byte) ((bits >> 16) & 0xFF);\n      bytes[i * 4 + 3] = (byte) ((bits >> 24) & 0xFF);\n    }\n    return bytes;\n  }\n\n  /**\n   * Convert FP32 byte blob back to float array (IEEE 754 format). Uses ByteBuffer for clean and\n   * readable conversion from little-endian bytes.\n   * @param fp32Bytes the FP32 byte array to convert\n   * @return List of Float values reconstructed from the byte data\n   */\n  public static List<Float> fp32BytesToFloatArray(byte[] fp32Bytes) {\n    ByteBuffer buffer = ByteBuffer.wrap(fp32Bytes).order(ByteOrder.LITTLE_ENDIAN);\n    List<Float> floats = new ArrayList<>();\n    while (buffer.remaining() >= 4) {\n      floats.add(buffer.getFloat());\n    }\n    return floats;\n  }\n\n}\n"
  },
  {
    "path": "src/test/resources/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFYTCCA0mgAwIBAgIJAKFpzRp2tUTDMA0GCSqGSIb3DQEBCwUAMEcxDjAMBgNVBAoMBWplZGlz\nMRMwEQYDVQQIDApTb21lLVN0YXRlMQswCQYDVQQGEwJBUjETMBEGA1UEAwwKamVkaXMtdGVzdDAe\nFw0xNjAxMTExOTIyMzRaFw0yMTAxMTAxOTIyMzRaMEcxDjAMBgNVBAoMBWplZGlzMRMwEQYDVQQI\nDApTb21lLVN0YXRlMQswCQYDVQQGEwJBUjETMBEGA1UEAwwKamVkaXMtdGVzdDCCAiIwDQYJKoZI\nhvcNAQEBBQADggIPADCCAgoCggIBAMrnt94Om3LEhQd8AA8OaIst38px5JlOYDcUAiifh8ONlGrd\nOTDvgsNQpF1eB9gbEwsbxQEeF1C8zr+O2+xl/XLgC1aeVYX05+tIUZxuncrTNqdB5ke721myS+TE\nQ2j4Q0zNm2qOPHboTwBOSpCZfP62e0RjGh4s+ghf4rmY254/H1OMO0jZ0Vlova5NaiK7FLZu5xp4\nTQEGTTtkHVmFIZK+5KJ49KItFfrSaqJddnUvP2tt6mhZjW1TvnvyOtyhYWStzsS15TTzppJyU/Fj\ni4ETokVhB4S7xln7D+/OK3Y7VUKyNsejI4F2N+hdmF33WLz9TyZEVR6PiUWytBWjSF0OqfCxWsHJ\nIi3JVs4Z7Dx2xcARAgP4HhjotW2KiLD043dE3vzaPQRKFGO3qpXkOULqoMAxl5EJHRj27twPzhtC\n1xDyZtoQv8gi1dXtt0CuR/HcwuGPFw+28JBcaaPeXmnTIMerrrq6de7/ITZbrWM3GOQNhflJ0Gve\nLANibReQv0Ms0dZ6FoOdcaQBuzN1X2lSIWkQ4ZLKGW5eNjK1KrSd9MGGhydGI7k+xyEyq6uo/nYA\np/vZpevGx8bR9G+R9Tet0AIxnL4VsoVG/OciSxCgcMTJf58Q1pJS86LRK17iZ3X9mN+Q905jVbuj\nLs4n2E/Hl6akJINRU8odt6KiBPEHAgMBAAGjUDBOMB0GA1UdDgQWBBTH4N06+osTbe7M5GQiVMis\ntQ990TAfBgNVHSMEGDAWgBTH4N06+osTbe7M5GQiVMistQ990TAMBgNVHRMEBTADAQH/MA0GCSqG\nSIb3DQEBCwUAA4ICAQA3n4GzRWXyNC5SF0Pp2OZeWRe2Ms44pSIvqM6PijP84dX1h2XH9Zk9L8gz\nGDS1KWhAaF5L5UMSUqHPLiMHxmNowU5zmXKDnmUaaQB1EX+yqV/U8GQ/xfTwM+Zu/K1fWiAQqx2g\nATIze8rIOrZW5rPEJO7Www84WV5QNyH7nq1wRyqqDiy7e8tqrCWHGJ9jiF94lhUH2AcqUilZOA8F\npdmYDTFceZ/Eavdy+SSkIUAlLTj7Ncqz3cT1FnZdwHGMidDqnnD85CDdfOvlQ1qeKION1GGsteG8\ntGtGJPnVt6QEMVJXCHUl5mC+lgbg85+8KDzJ+jWLXhACm+yaKHKRESk0ycYHEtWoGnsqowor30FZ\nmm5BH6U7TQJanHWsu/X/ZPRyjcZrw+Feo+7YlQWXh94dZDj6p8kRcvT6Juk5MyLYCFNSlhD7bDwa\nW8Gb5tU1DWbBiMLeuyF8sqo2Cw1wksYXcHFzotPjupaWSt19R/l4YxIwGfe3vQXNiEdKrK3Bi70c\ne8mjTawsZvxNE3hAsUCCrAyD7We4wI1WmYw44Ss4awqjA6R0aJZyUrVr+r5Xdpq40ZLS9M3rxdWO\nG19j+IqqdQSRCLnVVjrKLfkAI42t3edNmAP3cGIU12F2Y5WKSH71kjZUbeIJRqd1wu8l2zYHLzJ6\nN0Muc2vs4WZhBC7wSg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/endpoints.json",
    "content": "{\n  \"sentinel-standalone0\": {\n    \"tls\": false,\n    \"username\": \"sentinel\",\n    \"password\": \"foobared\",\n    \"endpoints\": [\n      \"redis://127.0.0.1:26379\"\n    ]\n  },\n  \"sentinel-standalone0-tls\": {\n    \"tls\": true,\n    \"username\": \"sentinel\",\n    \"password\": \"foobared\",\n    \"endpoints\": [\n      \"redis://127.0.0.1:36379\"\n    ]\n  },\n  \"sentinel-standalone2-1\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:26380\"\n    ]\n  },\n  \"sentinel-failover\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:26381\"\n    ]\n  },\n  \"sentinel-standalone2-3\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:26382\"\n    ]\n  },\n  \"redis-failover-1\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:29379\"\n    ]\n  },\n  \"redis-failover-2\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:29380\"\n    ]\n  },\n  \"standalone0\": {\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6379\"\n    ]\n  },\n  \"standalone0-tls\": {\n    \"username\": \"default\",\n    \"password\": \"foobared\",\n    \"tls\": true,\n    \"tls_cert_path\": \"redis1-2-5-8-sentinel/work/tls\",\n    \"endpoints\": [\n      \"rediss://localhost:6390\"\n    ]\n  },\n  \"standalone0-acl\": {\n    \"username\": \"acljedis\",\n    \"password\": \"fizzbuzz\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6379\"\n    ]\n  },\n  \"standalone0-acl-tls\": {\n    \"username\": \"acljedis\",\n    \"password\": \"fizzbuzz\",\n    \"tls\": true,\n    \"tls_cert_path\": \"redis1-2-5-8-sentinel/work/tls\",\n    \"endpoints\": [\n      \"rediss://localhost:6390\"\n    ]\n  },\n  \"standalone1\": {\n    \"username\": \"default\",\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6380\"\n    ]\n  },\n  \"standalone2-primary\": {\n    \"username\": \"default\",\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:6381\"\n    ]\n  },\n  \"standalone3-replica-of-standalone2\": {\n    \"username\": \"default\",\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6382\"\n    ]\n  },\n  \"standalone4-replica-of-standalone1\": {\n    \"username\": \"default\",\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6383\"\n    ]\n  },\n  \"standalone7-with-lfu-policy\": {\n    \"username\": \"default\",\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6386\"\n    ]\n  },\n  \"standalone9-failover\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6388\"\n    ]\n  },\n  \"standalone10-replica-of-standalone9\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6389\"\n    ]\n  },\n  \"modules-docker\": {\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6479\"\n    ]\n  },\n  \"cluster-unbound\": {\n    \"password\": \"cluster\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:7379\",\n      \"redis://127.0.0.1:7380\",\n      \"redis://127.0.0.1:7381\",\n      \"redis://127.0.0.1:7382\",\n      \"redis://127.0.0.1:7383\",\n      \"redis://127.0.0.1:7384\"\n    ]\n  },\n  \"cluster-stable\": {\n    \"password\": \"cluster\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:7479\",\n      \"redis://127.0.0.1:7480\",\n      \"redis://127.0.0.1:7481\"\n    ]\n  },\n  \"cluster-stable-tls\": {\n    \"password\": \"cluster\",\n    \"tls\": true,\n    \"tls_cert_path\": \"cluster-stable/work/tls\",\n    \"endpoints\": [\n      \"rediss://localhost:8479\",\n      \"rediss://localhost:8480\",\n      \"rediss://localhost:8481\"\n    ]\n  },\n  \"cluster-stack\": {\n    \"password\": \"cluster\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://127.0.0.1:16479\",\n      \"redis://127.0.0.1:16480\",\n      \"redis://127.0.0.1:16481\"\n    ]\n  },\n  \"standalone-mtls\": {\n    \"tls\": true,\n    \"tls_cert_path\": \"standalone-mtls/work/tls\",\n    \"endpoints\": [\n      \"rediss://localhost:6790\"\n    ]\n  },\n  \"cluster-mtls\": {\n    \"tls\": true,\n    \"tls_cert_path\": \"cluster-mtls/work/tls\",\n    \"endpoints\": [\n      \"rediss://localhost:8579\",\n      \"rediss://localhost:8580\",\n      \"rediss://localhost:8581\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/test/resources/endpoints_source.json",
    "content": "{\n  \"standalone0\": {\n    \"password\": \"foobared\",\n    \"tls\": false,\n    \"endpoints\": [\n      \"redis://localhost:6379\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/test/resources/env/cluster-unbound/config/node-7379-8379/redis.conf",
    "content": "bind 0.0.0.0\nport 7379\nrequirepass cluster\ncluster-node-timeout 150\nsave \"\"\nappendonly no\ncluster-enabled yes"
  },
  {
    "path": "src/test/resources/env/cluster-unbound/config/node-7380-8380/redis.conf",
    "content": "bind 0.0.0.0\nport 7380\nrequirepass cluster\ncluster-node-timeout 150\nsave \"\"\nappendonly no\ncluster-enabled yes"
  },
  {
    "path": "src/test/resources/env/cluster-unbound/config/node-7381-8381/redis.conf",
    "content": "bind 0.0.0.0\nport 7381\nrequirepass cluster\ncluster-node-timeout 150\nsave \"\"\nappendonly no\ncluster-enabled yes"
  },
  {
    "path": "src/test/resources/env/cluster-unbound/config/node-7382-8382/redis.conf",
    "content": "bind 0.0.0.0\nrequirepass cluster\nport 7382\ncluster-node-timeout 150\nsave \"\"\nappendonly no\ncluster-enabled yes"
  },
  {
    "path": "src/test/resources/env/cluster-unbound/config/node-7383-8383/redis.conf",
    "content": "bind 0.0.0.0\nport 7383\nrequirepass cluster\ncluster-node-timeout 150\nsave \"\"\nappendonly no\ncluster-enabled yes"
  },
  {
    "path": "src/test/resources/env/config/redis6-7/node-sentinel-26381-36381/redis.conf",
    "content": "port 26380\ntls-port 36380\ntls-auth-clients no\nuser deploy on allcommands allkeys >verify\nsentinel monitor mymaster 127.0.0.1 6381 1\nsentinel auth-pass mymaster foobared\nsentinel down-after-milliseconds mymaster 2000\nsentinel parallel-syncs mymaster 1\nsentinel failover-timeout mymaster 120000"
  },
  {
    "path": "src/test/resources/env/docker-compose.yml",
    "content": "x-client-libs-image: &client-libs-image\n  image: \"${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_VERSION:-}}\"\nx-client-libs-stack-image: &client-libs-stack-image\n  image: \"${CLIENT_LIBS_TEST_IMAGE}:${CLIENT_LIBS_TEST_IMAGE_TAG:-${REDIS_STACK_VERSION:-${REDIS_VERSION:-}}}\"\n\nservices:\n  toxiproxy:\n    image: ghcr.io/shopify/toxiproxy:2.8.0\n    ports:\n      - \"8474:8474\"   # Admin API\n      - \"29379:29379\" # redis-failover-1 proxy\n      - \"29380:29380\" # redis-failover-2 proxy\n  redis-failover-1:\n    <<: *client-libs-image\n    container_name: redis-failover-1\n    environment:\n      - PORT=9379\n    ports:\n      - 9379:9379\n  redis-failover-2:\n    <<: *client-libs-image\n    container_name: redis-failover-2\n    environment:\n      - PORT=9380\n    ports:\n      - 9380:9380\n  redis1-2-5-8-sentinel:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: redis1-2-5-8-sentinel\n    #network_mode: host\n    environment:\n      - REDIS_CLUSTER=no\n      - REDIS_CLIENT_USER=deploy\n      - REDIS_CLIENT_PASSWORD=verify\n      - TLS_ENABLED=yes\n    ports:\n      - \"6379:6379\"\n      - \"6380:6380\"\n      - \"6383:6383\"\n      - \"6386:6386\"\n      - \"6390:6390\"\n      - \"6391:6391\"\n      - \"26379:26379\" # sentinel\n      - \"36379:36379\" # sentinel tls\n    command: ${ENABLE_MODULE_COMMAND_DIRECTIVE}\n    volumes:\n      -  ${REDIS_ENV_CONF_DIR}/redis1-2-5-8-sentinel/config:/redis/config:r\n      -  ${REDIS_ENV_WORK_DIR}/redis1-2-5-8-sentinel/work:/redis/work:rw\n  standalone2-sentinel:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: standalone2-sentinel\n    #network_mode: host\n    environment:\n      - REDIS_CLUSTER=no\n      - TLS_ENABLED=yes\n      - REDIS_PASSWORD=foobared\n    ports:\n      - \"6381:6381\"\n      - \"16381:16381\"\n      - \"6382:6382\"\n      - \"16382:16382\"\n      - \"26380:26380\" # sentinel-standalone2-1\n      - \"36380:36380\" # sentinel tls\n      - \"26382:26382\" # sentinel-standalone2-3\n      - \"36382:36382\" # sentinel tls\n    volumes:\n      -  ${REDIS_ENV_CONF_DIR}/standalone2-sentinel/config:/redis/config:r\n      -  ${REDIS_ENV_WORK_DIR}/standalone2-sentinel/work:/redis/work:rw\n\n  sentinel-failover:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: sentinel-standalone2-failover\n    #network_mode: host\n    environment:\n      - REDIS_CLUSTER=no\n      - REDIS_PASSWORD=foobared\n    ports:\n      - \"6384:6384\"\n      - \"6385:6385\"\n      - \"26381:26381\" # sentinel\n      - \"36381:36381\" # sentinel tls\n    volumes:\n      -  ${REDIS_ENV_CONF_DIR}/sentinel-standalone2-failover/config:/redis/config:r\n      -  ${REDIS_ENV_WORK_DIR}/sentinel-standalone2-failover/work:/redis/work:rw\n\n  redis9-10:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: redis9-10\n    #network_mode: host\n    environment:\n      - REDIS_CLUSTER=no\n    ports:\n      - \"6388:6388\"\n      - \"6389:6389\"\n    volumes:\n      - ${REDIS_ENV_CONF_DIR}/redis9-10/config:/redis/config:r\n      - ${REDIS_ENV_WORK_DIR}/redis9-10/work:/redis/work:rw\n  redis-unavailable:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: redis-unavailable-1\n    #network_mode: host\n    environment:\n      - REDIS_CLUSTER=no\n      - PORT=6400\n    ports:\n      - \"6400:6400\"\n    volumes:\n      -  ${REDIS_ENV_WORK_DIR}/redis-unavailable/work:/redis/work:rw\n\n  cluster-unbound:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: cluster-unbound-1\n    environment:\n      - REDIS_PASSWORD=cluster\n    ports:\n      - \"7379-7383:7379-7383\"\n    volumes:\n      - ${REDIS_ENV_CONF_DIR}/cluster-unbound/config:/redis/config:r\n      - ${REDIS_ENV_WORK_DIR}/cluster-unbound/work:/redis/work:rw\n\n  #TLS endpoints of Cluster stable are used for TLS tests that do not require client authentication\n  cluster-stable:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: cluster-stable-1\n    #network_mode: host\n    command: --cluster-preferred-endpoint-type hostname --cluster-announce-hostname \"localhost\" --cluster-node-timeout 150 --tls-auth-clients no --save \"\"\n    environment:\n      - REDIS_CLUSTER=yes\n      - REDIS_PASSWORD=cluster\n      - PORT=7479\n      - TLS_PORT=8479\n      - NODES=6\n      - REPLICAS=1\n      - TLS_ENABLED=yes\n      - TLS_AUTH_CLIENTS_USER=off\n    ports:\n      - \"7479-7484:7479-7484\"\n      - \"8479-8484:8479-8484\"\n    volumes:\n      - ${REDIS_ENV_CONF_DIR}/cluster-stable/config:/redis/config:r\n      - ${REDIS_ENV_WORK_DIR}/cluster-stable/work:/redis/work:rw\n\n  jedis-stack:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-stack-image\n    container_name: jedis-stack\n    command: --save \"\"\n    environment:\n      - REDIS_CLUSTER=no\n      - PORT=6479\n    ports:\n      - \"6479:6479\"\n    volumes:\n      -  ${REDIS_ENV_WORK_DIR}/jedis-stack/work:/redis/work:rw\n#todo find a way to connect from mac os host to exposed unix socket in container\n#  redis_uds:\n#    <<: *client-libs-image\n#    container_name: redis_uds\n#    #network_mode: host\n#    command: redis-server /etc/redis.conf\n#    volumes:\n#      - \"./redis_uds/config/node-0/redis.conf:/etc/redis.conf\"\n#      - \"./work/redis_uds/work:/tmp/docker/\"\n\n  # Standalone Redis instance with mTLS (mutual TLS) authentication\n  # Uses TLS_CLIENT_CNS to generate client certificates for mTLS users\n  # TLS_AUTH_CLIENTS_USER=CN (default) extracts username from client certificate CN\n  # ACL users matching client certificate CNs are configured via --user directives\n  standalone-mtls:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: standalone-mtls\n    command:\n      - \"--save\"\n      - \"\"\n      - \"--user\"\n      - \"mtls-user1\"\n      - \"on\"\n      - \"nopass\"\n      - \"~*\"\n      - \"&*\"\n      - \"+@all\"\n      - \"--user\"\n      - \"mtls-user2\"\n      - \"on\"\n      - \"nopass\"\n      - \"~*\"\n      - \"&*\"\n      - \"+@all\"\n    environment:\n      - REDIS_CLUSTER=no\n      - TLS_ENABLED=yes\n      - TLS_PORT=6790\n      - PORT=6789\n      - TLS_CLIENT_CNS=mtls-user1 mtls-user2 mtls-user-without-acl\n    ports:\n      # We don't expose non-TLS port on purpose\n      - \"6790:6790\"\n    volumes:\n      - ${REDIS_ENV_WORK_DIR}/standalone-mtls/work:/redis/work:rw\n\n  # Redis cluster with mTLS (mutual TLS) authentication\n  # 3 nodes without replicas to minimize memory footprint\n  # TLS_AUTH_CLIENTS_USER=CN (default) extracts username from client certificate CN\n  # ACL users matching client certificate CNs are configured via --user directives\n  cluster-mtls:\n    sysctls:\n      - net.ipv6.conf.all.disable_ipv6=1\n    <<: *client-libs-image\n    container_name: cluster-mtls\n    command:\n      - \"--cluster-preferred-endpoint-type\"\n      - \"hostname\"\n      - \"--cluster-announce-hostname\"\n      - \"localhost\"\n      - \"--cluster-node-timeout\"\n      - \"150\"\n      - \"--save\"\n      - \"\"\n      - \"--user\"\n      - \"mtls-user1\"\n      - \"on\"\n      - \"nopass\"\n      - \"~*\"\n      - \"&*\"\n      - \"+@all\"\n      - \"--user\"\n      - \"mtls-user2\"\n      - \"on\"\n      - \"nopass\"\n      - \"~*\"\n      - \"&*\"\n      - \"+@all\"\n    environment:\n      - REDIS_CLUSTER=yes\n      - TLS_ENABLED=yes\n      - TLS_PORT=8579\n      - PORT=7579\n      - NODES=3\n      - REPLICAS=0\n      - TLS_CLIENT_CNS=mtls-user1 mtls-user2 mtls-user-without-acl\n    ports:\n      # We don't expose non-TLS ports on purpose\n      - \"8579-8581:8579-8581\"\n    volumes:\n      - ${REDIS_ENV_WORK_DIR}/cluster-mtls/work:/redis/work:rw\n"
  },
  {
    "path": "src/test/resources/env/redis-uds/config/node-0/redis.conf",
    "content": "unixsocket /tmp/docker/redis.sock\nunixsocketperm 777"
  },
  {
    "path": "src/test/resources/env/redis1-2-5-8-sentinel/config/node-6379-6390/redis.conf",
    "content": "port 6379\ntls-port 6390\nrequirepass foobared\nuser deploy on allcommands allkeys >verify\nuser acljedis on allcommands allkeys >fizzbuzz\nsave \"\"\nappendonly no\ntls-auth-clients no\n # Not supported on v6. provided as argument on node start\n# enable-module-command yes\nclient-output-buffer-limit pubsub 256k 128k 5"
  },
  {
    "path": "src/test/resources/env/redis1-2-5-8-sentinel/config/node-6380/redis.conf",
    "content": "protected-mode no\nport 6380\nuser deploy on allcommands allkeys >verify\nrequirepass foobared\npidfile /tmp/redis2.pid\nlogfile /tmp/redis2.log\nsave \"\"\nappendonly no"
  },
  {
    "path": "src/test/resources/env/redis1-2-5-8-sentinel/config/node-6383-6391/redis.conf",
    "content": "port 6383\ntls-port 6391\nuser deploy on allcommands allkeys >verify\nrequirepass foobared\nmasterauth foobared\ntls-auth-clients no\nsave \"\"\nappendonly no\nslaveof localhost 6379"
  },
  {
    "path": "src/test/resources/env/redis1-2-5-8-sentinel/config/node-6386/redis.conf",
    "content": "protected-mode no\nport 6386\nuser deploy on allcommands allkeys >verify\nsave \"\"\nappendonly no\nmaxmemory-policy allkeys-lfu"
  },
  {
    "path": "src/test/resources/env/redis1-2-5-8-sentinel/config/node-sentinel-26379-36379/redis.conf",
    "content": "port 26379\ntls-port 36379\ntls-auth-clients no\nuser default off\nuser deploy on allcommands allkeys >verify\nuser sentinel on allcommands allkeys allchannels >foobared\nsentinel monitor aclmaster 127.0.0.1 6379 1\nsentinel auth-user aclmaster acljedis\nsentinel auth-pass aclmaster fizzbuzz\nsentinel down-after-milliseconds aclmaster 2000\nsentinel failover-timeout aclmaster 120000\nsentinel parallel-syncs aclmaster 1"
  },
  {
    "path": "src/test/resources/env/redis9-10/config/node-6388/redis.conf",
    "content": "port 6388\nsave \"\"\nappendonly no"
  },
  {
    "path": "src/test/resources/env/redis9-10/config/node-6389/redis.conf",
    "content": "port 6389\nsave \"\"\nappendonly no\nreplicaof localhost 6388"
  },
  {
    "path": "src/test/resources/env/sentinel-standalone2-failover/config/node-6384/redis.conf",
    "content": "port 6384\nrequirepass foobared\nmasterauth foobared\nsave \"\"\nappendonly no"
  },
  {
    "path": "src/test/resources/env/sentinel-standalone2-failover/config/node-6385/redis.conf",
    "content": "port 6385\nrequirepass foobared\nmasterauth foobared\nsave \"\"\nappendonly no\nslaveof localhost 6384"
  },
  {
    "path": "src/test/resources/env/sentinel-standalone2-failover/config/node-sentinel-26381-36381/redis.conf",
    "content": "port 26381\nprotected-mode no\nsentinel monitor mymasterfailover 127.0.0.1 6384 1\nsentinel auth-pass mymasterfailover foobared\nsentinel down-after-milliseconds mymasterfailover 2000\nsentinel failover-timeout mymasterfailover 120000\nsentinel parallel-syncs mymasterfailover 1"
  },
  {
    "path": "src/test/resources/env/standalone2-sentinel/config/node-6381-16381/redis.conf",
    "content": "port 6381\ntls-port 16381\nrequirepass foobared\nmasterauth foobared\nsave \"\"\nappendonly no"
  },
  {
    "path": "src/test/resources/env/standalone2-sentinel/config/node-6382-16382/redis.conf",
    "content": "port 6382\ntls-port 16382\nrequirepass foobared\nmasterauth foobared\nsave \"\"\nappendonly no\nslaveof localhost 6381"
  },
  {
    "path": "src/test/resources/env/standalone2-sentinel/config/node-sentinel-26380-36380/redis.conf",
    "content": "port 26380\ntls-port 36380\ntls-auth-clients no\nsentinel monitor mymaster 127.0.0.1 6381 1\nsentinel auth-pass mymaster foobared\nsentinel down-after-milliseconds mymaster 2000\nsentinel parallel-syncs mymaster 1\nsentinel failover-timeout mymaster 120000"
  },
  {
    "path": "src/test/resources/env/standalone2-sentinel/config/node-sentinel-26382-36382/redis.conf",
    "content": "port 26382\ntls-port 36382\ntls-auth-clients no\nsentinel monitor mymaster 127.0.0.1 6381 1\nsentinel auth-pass mymaster foobared\nsentinel down-after-milliseconds mymaster 2000\nsentinel parallel-syncs mymaster 1\nsentinel failover-timeout mymaster 120000"
  },
  {
    "path": "src/test/resources/functions/keyspaceTriggers.js",
    "content": "#!js api_version=1.0 name=keyspaceTriggers\n\nredis.registerKeySpaceTrigger(\"consumer\", \"\", function(client, data){\n    if (client.call(\"type\", data.key) != \"hash\") {\n        // key is not a hash, do not touch it.\n        return;\n    }\n    // get the current time in ms\n    var curr_time = client.call(\"time\")[0];\n    // set '__last_updated__' with the current time value\n    client.call('hset', data.key, '__last_updated__', curr_time);\n});"
  },
  {
    "path": "src/test/resources/functions/pingpong.js",
    "content": "#!js api_version=1.0 name=pingpong\n\nfunction answer(client, data) {\n    return client.call('ping');\n}\n\nredis.registerFunction('playPingPong', answer, {description: 'You PING, we PONG'});"
  },
  {
    "path": "src/test/resources/functions/streamTriggers.js",
    "content": "#!js api_version=1.0 name=streamTriggers\n\nredis.registerStreamTrigger(\n    \"consumer\", // consumer name\n    \"stream\", // streams prefix\n    function(c, data) {\n        // callback to run on each element added to the stream\n        redis.log(JSON.stringify(data, (key, value) =>\n            typeof value === 'bigint'\n                ? value.toString()\n                : value // return everything else unchanged\n        ));\n    }\n);"
  },
  {
    "path": "src/test/resources/functions/withConfig.js",
    "content": "#!js api_version=1.0 name=withConfig\n\nvar last_modified_field_name = \"__last_modified__\"\n\nif (redis.config.last_modified_field_name !== undefined) {\n    if (typeof redis.config.last_modified_field_name != 'string') {\n        throw \"last_modified_field_name must be a string\";\n    }\n    last_modified_field_name = redis.config.last_modified_field_name\n}\n\nredis.registerFunction(\"hset\", function(client, key, field, val){\n    // get the current time in ms\n    var curr_time = client.call(\"time\")[0];\n    return client.call('hset', key, field, val, last_modified_field_name, curr_time);\n});"
  },
  {
    "path": "src/test/resources/functions/withFlags.js",
    "content": "#!js api_version=1.0 name=withFlags\nredis.registerFunction(\"my_set\",\n    (c, key, val) => {\n        return c.call(\"set\", key, val);\n    },\n    {\n        flags: [redis.functionFlags.RAW_ARGUMENTS]\n    }\n);"
  },
  {
    "path": "src/test/resources/functions/workingWIthHashes.js",
    "content": "#!js api_version=1.0 name=hashitout\n\nredis.registerFunction('hashy', function(client, key_name){\n    if (client.call('type', key_name) == 'hash') {\n        return client.call('hgetall', key_name);\n    }\n    throw \"Oops, that wasn't a Hash!\";\n});"
  },
  {
    "path": "src/test/resources/junit-platform.properties",
    "content": "# JUnit 5 Platform Configuration\n\n# Global timeout for all test methods (5 minutes = 300 seconds)\n# Any test method that exceeds this timeout will automatically fail\njunit.jupiter.execution.timeout.default = 300s\n\n"
  },
  {
    "path": "src/test/resources/logback-test.xml",
    "content": "<configuration>\n  <!-- Console appender with timestamps -->\n  <appender name=\"CONSOLE\" class=\"ch.qos.logback.core.ConsoleAppender\">\n    <encoder>\n      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n    </encoder>\n  </appender>\n  \n  <!-- File appender with timestamps -->\n  <appender name=\"FILE\" class=\"ch.qos.logback.core.FileAppender\">\n    <file>target/jedis-test.log</file>\n    <append>true</append>\n    <encoder>\n      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n    </encoder>\n  </appender>\n\n  <!-- Root logger configuration -->\n  <root level=\"INFO\">\n    <appender-ref ref=\"CONSOLE\" />\n    <appender-ref ref=\"FILE\" />\n  </root>\n  \n  <!-- Set Redis client logging to DEBUG level -->\n  <logger name=\"redis.clients.jedis\" level=\"DEBUG\" />\n  <logger name=\"redis.clients.jedis.mcf.HealthCheck\" level=\"ERROR\" />\n  \n  <!-- Set other libraries to appropriate levels -->\n  <logger name=\"org.apache.http\" level=\"INFO\" />\n  <logger name=\"io.netty\" level=\"INFO\" />\n</configuration>"
  },
  {
    "path": "src/test/resources/redismodule.h",
    "content": "#ifndef REDISMODULE_H\n#define REDISMODULE_H\n\n#include <sys/types.h>\n#include <stdint.h>\n#include <stdio.h>\n\n/* ---------------- Defines common between core and modules --------------- */\n\n/* Error status return values. */\n#define REDISMODULE_OK 0\n#define REDISMODULE_ERR 1\n\n/* API versions. */\n#define REDISMODULE_APIVER_1 1\n\n/* API flags and constants */\n#define REDISMODULE_READ (1<<0)\n#define REDISMODULE_WRITE (1<<1)\n\n#define REDISMODULE_LIST_HEAD 0\n#define REDISMODULE_LIST_TAIL 1\n\n/* Key types. */\n#define REDISMODULE_KEYTYPE_EMPTY 0\n#define REDISMODULE_KEYTYPE_STRING 1\n#define REDISMODULE_KEYTYPE_LIST 2\n#define REDISMODULE_KEYTYPE_HASH 3\n#define REDISMODULE_KEYTYPE_SET 4\n#define REDISMODULE_KEYTYPE_ZSET 5\n\n/* Reply types. */\n#define REDISMODULE_REPLY_UNKNOWN -1\n#define REDISMODULE_REPLY_STRING 0\n#define REDISMODULE_REPLY_ERROR 1\n#define REDISMODULE_REPLY_INTEGER 2\n#define REDISMODULE_REPLY_ARRAY 3\n#define REDISMODULE_REPLY_NULL 4\n\n/* Postponed array length. */\n#define REDISMODULE_POSTPONED_ARRAY_LEN -1\n\n/* Expire */\n#define REDISMODULE_NO_EXPIRE -1\n\n/* Sorted set API flags. */\n#define REDISMODULE_ZADD_XX      (1<<0)\n#define REDISMODULE_ZADD_NX      (1<<1)\n#define REDISMODULE_ZADD_ADDED   (1<<2)\n#define REDISMODULE_ZADD_UPDATED (1<<3)\n#define REDISMODULE_ZADD_NOP     (1<<4)\n\n/* Hash API flags. */\n#define REDISMODULE_HASH_NONE       0\n#define REDISMODULE_HASH_NX         (1<<0)\n#define REDISMODULE_HASH_XX         (1<<1)\n#define REDISMODULE_HASH_CFIELDS    (1<<2)\n#define REDISMODULE_HASH_EXISTS     (1<<3)\n\n/* A special pointer that we can use between the core and the module to signal\n * field deletion, and that is impossible to be a valid pointer. */\n#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1)\n\n/* Error messages. */\n#define REDISMODULE_ERRORMSG_WRONGTYPE \"WRONGTYPE Operation against a key holding the wrong kind of value\"\n\n#define REDISMODULE_POSITIVE_INFINITE (1.0/0.0)\n#define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0)\n\n/* ------------------------- End of common defines ------------------------ */\n\n#ifndef REDISMODULE_CORE\n\ntypedef long long mstime_t;\n\n/* Incomplete structures for compiler checks but opaque access. */\ntypedef struct RedisModuleCtx RedisModuleCtx;\ntypedef struct RedisModuleKey RedisModuleKey;\ntypedef struct RedisModuleString RedisModuleString;\ntypedef struct RedisModuleCallReply RedisModuleCallReply;\n\ntypedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);\n\n#define REDISMODULE_GET_API(name) \\\n    RedisModule_GetApi(\"RedisModule_\" #name, ((void **)&RedisModule_ ## name))\n\n#define REDISMODULE_API_FUNC(x) (*x)\n\nint REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);\nint REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);\nint REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);\nint REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);\nint REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);\nint REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);\nvoid *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);\nvoid REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);\nint REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);\nsize_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);\nint REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);\nRedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);\nRedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);\nconst char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);\nvoid REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);\nint REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);\nlong long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);\nsize_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);\nRedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);\nRedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);\nRedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);\nvoid REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);\nconst char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(RedisModuleString *str, size_t *len);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);\nvoid REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);\nint REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);\nint REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(RedisModuleString *str, long long *ll);\nint REDISMODULE_API_FUNC(RedisModule_StringToDouble)(RedisModuleString *str, double *d);\nvoid REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);\nint REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);\nint REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);\nconst char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);\nRedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);\nint REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);\nint REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);\nchar *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);\nint REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);\nmstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);\nint REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);\nint REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);\nint REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);\nint REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);\nint REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted);\nvoid REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key);\nint REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);\nint REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);\nint REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);\nint REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);\nRedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score);\nint REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key);\nint REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key);\nint REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key);\nint REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...);\nint REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...);\nint REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);\nvoid REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);\nunsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);\n\n/* This is included inline inside each Redis module. */\nstatic int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {\n    void *getapifuncptr = ((void**)ctx)[0];\n    RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr;\n    REDISMODULE_GET_API(CreateCommand);\n    REDISMODULE_GET_API(SetModuleAttribs);\n    REDISMODULE_GET_API(WrongArity);\n    REDISMODULE_GET_API(ReplyWithLongLong);\n    REDISMODULE_GET_API(ReplyWithError);\n    REDISMODULE_GET_API(ReplyWithSimpleString);\n    REDISMODULE_GET_API(ReplyWithArray);\n    REDISMODULE_GET_API(ReplySetArrayLength);\n    REDISMODULE_GET_API(ReplyWithStringBuffer);\n    REDISMODULE_GET_API(ReplyWithString);\n    REDISMODULE_GET_API(ReplyWithNull);\n    REDISMODULE_GET_API(ReplyWithCallReply);\n    REDISMODULE_GET_API(ReplyWithDouble);\n    REDISMODULE_GET_API(ReplySetArrayLength);\n    REDISMODULE_GET_API(GetSelectedDb);\n    REDISMODULE_GET_API(SelectDb);\n    REDISMODULE_GET_API(OpenKey);\n    REDISMODULE_GET_API(CloseKey);\n    REDISMODULE_GET_API(KeyType);\n    REDISMODULE_GET_API(ValueLength);\n    REDISMODULE_GET_API(ListPush);\n    REDISMODULE_GET_API(ListPop);\n    REDISMODULE_GET_API(StringToLongLong);\n    REDISMODULE_GET_API(StringToDouble);\n    REDISMODULE_GET_API(Call);\n    REDISMODULE_GET_API(CallReplyProto);\n    REDISMODULE_GET_API(FreeCallReply);\n    REDISMODULE_GET_API(CallReplyInteger);\n    REDISMODULE_GET_API(CallReplyType);\n    REDISMODULE_GET_API(CallReplyLength);\n    REDISMODULE_GET_API(CallReplyArrayElement);\n    REDISMODULE_GET_API(CallReplyStringPtr);\n    REDISMODULE_GET_API(CreateStringFromCallReply);\n    REDISMODULE_GET_API(CreateString);\n    REDISMODULE_GET_API(CreateStringFromLongLong);\n    REDISMODULE_GET_API(FreeString);\n    REDISMODULE_GET_API(StringPtrLen);\n    REDISMODULE_GET_API(AutoMemory);\n    REDISMODULE_GET_API(Replicate);\n    REDISMODULE_GET_API(ReplicateVerbatim);\n    REDISMODULE_GET_API(DeleteKey);\n    REDISMODULE_GET_API(StringSet);\n    REDISMODULE_GET_API(StringDMA);\n    REDISMODULE_GET_API(StringTruncate);\n    REDISMODULE_GET_API(GetExpire);\n    REDISMODULE_GET_API(SetExpire);\n    REDISMODULE_GET_API(ZsetAdd);\n    REDISMODULE_GET_API(ZsetIncrby);\n    REDISMODULE_GET_API(ZsetScore);\n    REDISMODULE_GET_API(ZsetRem);\n    REDISMODULE_GET_API(ZsetRangeStop);\n    REDISMODULE_GET_API(ZsetFirstInScoreRange);\n    REDISMODULE_GET_API(ZsetLastInScoreRange);\n    REDISMODULE_GET_API(ZsetFirstInLexRange);\n    REDISMODULE_GET_API(ZsetLastInLexRange);\n    REDISMODULE_GET_API(ZsetRangeCurrentElement);\n    REDISMODULE_GET_API(ZsetRangeNext);\n    REDISMODULE_GET_API(ZsetRangePrev);\n    REDISMODULE_GET_API(ZsetRangeEndReached);\n    REDISMODULE_GET_API(HashSet);\n    REDISMODULE_GET_API(HashGet);\n    REDISMODULE_GET_API(IsKeysPositionRequest);\n    REDISMODULE_GET_API(KeyAtPos);\n    REDISMODULE_GET_API(GetClientId);\n\n    RedisModule_SetModuleAttribs(ctx,name,ver,apiver);\n    return REDISMODULE_OK;\n}\n\n#else\n\n/* Things only defined for the modules core, not exported to modules\n * including this file. */\n#define RedisModuleString robj\n\n#endif /* REDISMODULE_CORE */\n#endif /* REDISMOUDLE_H */\n"
  },
  {
    "path": "src/test/resources/testmodule.c",
    "content": "#include \"redismodule.h\"\n#include <stdlib.h>\n\nint HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {\n    RedisModule_ReplyWithLongLong(ctx,rand());\n    return REDISMODULE_OK;\n}\n\nint RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {\n if (RedisModule_Init(ctx,\"testmodule\",1,REDISMODULE_APIVER_1)\n        == REDISMODULE_ERR) return REDISMODULE_ERR;\n\n    if (RedisModule_CreateCommand(ctx,\"testmodule.simple\",\n        HelloworldRand_RedisCommand,\n        \"readonly\",0,0,0) == REDISMODULE_ERR)\n        return REDISMODULE_ERR;\n}"
  }
]